Why doesn't the program wait for the function result? [duplicate] - python-3.x

I have a tkinter class:
class DBCreatorWin():
def closeWindow(self):
tkMessageBox.showinfo("Ilmiont SQLite Database Manager", "This window cannot be closed.\nEnter a database name and press Continue.")
def returnName(self):
dbName = self.entry.get()
self.window.destroy()
return dbName
def __init__(self):
self.window = Toplevel()
self.window.transient(tkRoot)
self.window.grab_set()
self.window.resizable(width=False, height=False)
self.window.title("Ilmiont SQLite Database Manager")
self.window.protocol("WM_DELETE_WINDOW", self.closeWindow)
self.label = Label(self.window, text="Enter the name of the database to be created: ")
self.entry = Entry(self.window, width=30)
self.button = Button(self.window, text="Continue", command=self.returnName)
self.label.grid(row=0, column=0)
self.entry.grid(row=0, column=1)
self.button.grid(row=1, column=0, columnspan=2)
I want to create an instance of this class within my main code and wait for the return value. The user types a name into the entry field and presses the Continue button. At that point, the value should be returned to where the class was originally instantiated. How do I go about this? I can't seem to make it work in a normal way and am new to tkinter.
Thanks in advance,
Ilmiont

There are a couple of ways to do this. The basic idea is to use a tkinter method to wait for a specific event before returning. Tkinter provides two methods to do just that: wait_window and wait_variable. The most common method is to open a window and then wait for it to be destroyed. Some good examples can be found on the effbot site, on a page titled Dialog Windows.
Here's a simple illustration. It's not production-ready, but illustrates the general idea. At the very least you'll want to add a grab on the dialog so that you can't interact with the main window while the dialog is open, since you said you want the dialog to be modal.
import Tkinter as tk
class MyDialog(object):
def __init__(self, parent):
self.toplevel = tk.Toplevel(parent)
self.var = tk.StringVar()
label = tk.Label(self.toplevel, text="Pick something:")
om = tk.OptionMenu(self.toplevel, self.var, "one", "two","three")
button = tk.Button(self.toplevel, text="OK", command=self.toplevel.destroy)
label.pack(side="top", fill="x")
om.pack(side="top", fill="x")
button.pack()
def show(self):
self.toplevel.deiconify()
self.toplevel.wait_window()
value = self.var.get()
return value
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.button = tk.Button(self, text="Click me!", command=self.on_click)
self.label = tk.Label(self, width=80)
self.label.pack(side="top", fill="x")
self.button.pack(pady=20)
def on_click(self):
result = MyDialog(self).show()
self.label.configure(text="your result: %s" % result)
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()

You can't.
The whole way the tkinter works is with callbacks. The command that you're using is the callback and you'll have to use the value inside the class. Here is an example:
def do_stuf(self):
tkMessageBox.showinfo("Foo", returnName())
....................
self.button = Button(self.window, text="Continue", command=self.do_stuff)

Related

packing using tkinter in_ keyword doesn't work with widgets created outside of a frame subclass

My understanding is that the in_ keyword argument to pack/grid should allow me to specify the managing widget. I want to pack arbitrary widgets inside a Frame subclass, so I passed the widgets and packed them during intialization, but the widgets didn't appear (although space in the window appears to have been allocated...). If I create the widget internally using master which is root, there is no issue and the widgets are displayed as expected.
The following working example and its output demonstrate the issue:
import tkinter as tk
from tkinter import ttk
class ItemContainerExternal(ttk.Frame):
def __init__(self, master, input_label, input_object):
ttk.Frame.__init__(self, master)
self.label = input_label
self.label.pack(side=tk.LEFT, padx=5, pady=3, fill=tk.X, in_=self)
self.input_object = input_object
self.input_object.pack(side=tk.LEFT, padx=5, pady=3, fill=tk.X, in_=self)
def get(self):
return variable.get()
class ItemContainerInternal(ttk.Frame):
def __init__(self, master):
ttk.Frame.__init__(self, master)
ttk.Label(master, text='internal').pack(side=tk.LEFT, padx=5, pady=3, fill=tk.X, in_=self)
self.input_object = ttk.Entry(master)
self.input_object.pack(side=tk.LEFT, padx=5, pady=3, fill=tk.X, in_=self)
def get(self):
return variable.get()
if __name__ == '__main__':
root = tk.Tk()
inputobj = ttk.Entry(root)
inputlabel = ttk.Label(root, text='external')
ItemContainerExternal(root, inputlabel, inputobj).grid(row=0, column=0)
ItemContainerInternal(root).grid(row=1, column=0)
root.mainloop()
The problem is that you're creating the entry and label before you're creating the frame, so they have a lower stacking order. That means the frame will be on top of the entry and label and thus, obscuring them from view.
A simple fix is to call lift() on the entry and label:
class ItemContainerExternal(tk.Frame):
def __init__(self, master, input_label, input_object):
...
self.input_object.lift()
self.label.lift()
The order in which widgets get created matters. Newer widgets are "on top of" previous widgets.
Call .lower() on the Frame after you create it, assuming it's created after all the widgets that you will pack into it. If not, you'll need to either call .lower() again on the Frame after creating a new widget to go inside it, or you'll have to raise the new widget via .lift() as per Bryan's answer.

How to access a button inside of a class in tkinter

I'm using Python 3.8.0 and Tkinter 8.6.
I'm trying to access button one through the button.config method but I don't know how to access the button outside the class.
I tried assigning a name to button one and then trying app.name.config() but it didn't work and Python didn't recognize the name.
import tkinter as tk
from tkinter import font
class Application(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.grid()
self.theWidgets()
def theWidgets(self):
self.one = tk.Button(self, text='New Game',command=onePressed())
self.one.grid(row=0,column=0,padx=100)
self.two = tk.Button(self, text='Load Game')
self.two.grid(row=1,column=0,padx=100,pady=10)
self.three = tk.Button(self, text='Quit', command=self.quit,anchor=tk.W,font='Helvetica 18 bold')
self.three.grid(row=2, column=0, padx=100, pady=10)
app = Application()
def onePressed():
#change state of button one to tk.DISABLED
app.mainloop(
)
Code that controls the internal state of a class member (e.g. a button) normally belongs inside the class.
The actions of a button press should be defined as part of your class.
(As with anything, there are occasionally exceptions but for now follow the above).
For more details, see en.wikipedia.org/wiki/Encapsulation_(computer_programming).
All you need to do in your example is move the button press function inside your class and then associate it with your button (in the same way you associated quit with button 3).
import tkinter as tk
from tkinter import font
class Application(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.grid()
self.theWidgets()
def theWidgets(self):
self.one = tk.Button(self, text='New Game', command=self.onePressed)
self.one.grid(row=0,column=0,padx=100)
self.two = tk.Button(self, text='Load Game')
self.two.grid(row=1,column=0,padx=100,pady=10)
self.three = tk.Button(self, text='Quit', command=self.quit, anchor=tk.W, font='Helvetica 18 bold')
self.three.grid(row=2, column=0, padx=100, pady=10)
def onePressed(self):
self.one.config(state="disabled")
app = Application()
app.mainloop()

Refresh text in tkinter message box

I have a Python code that has many steps. Every step I make is a print that says that I have finished this step. I am looking for a way to open a message window and every step that ends then the print will appear in the same window until the end. How can I do this?
This might be helpful.
from tkinter import *
class Application(Frame):
t = None
def say_hi(self):
self.t.message.insert(END, "hi there!! \n")
def createWidgets(self):
self.QUIT = Button(self)
self.QUIT["text"] = "QUIT"
self.QUIT["fg"] = "red"
self.QUIT["command"] = self.quit
self.QUIT.pack({"side": "left"})
self.hi_there = Button(self)
self.hi_there["text"] = "Launch",
self.hi_there["command"] = self.create_window
self.hi_there.pack({"side": "left"})
def create_window(self):
if(self.t):
self.say_hi()
else:
self.t = Toplevel(self)
self.t.wm_title("Message Window")
self.t.message = Text(self.t, height=20, width=30)
self.t.message.pack()
self.say_hi()
def __init__(self, master=None):
Frame.__init__(self, master)
self.pack()
self.createWidgets()
root = Tk()
app = Application(master=root)
app.mainloop()
root.destroy()
You have the main application which opens a TopLevel Window with a textarea on it.
From the function say_hi you can write directly on the textarea from the created message window.
I hope this is what you were looking for.

Python3 tkinter - New window with class is blank + new window contents in original window

I am dabbling in tkinter's possibilities to make a simple application that shows a "Enter password" little window upon startup. But the weirdest behaviour started to happen...
mainWindow.py
import tkinter as tk
import password
class mainWindow(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.title("mainWindow")
self.geometry("{0}x{1}+20+20".format(50,50))
if __name__ == "__main__":
mainW = mainWindow()
passW = password.passwordWindow()
passW.resizable(False, False)
passW.attributes("-topmost", True)
passW.mainloop()
password.py
import tkinter as tk
import mainWindow
class passwordWindow(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.title("Password")
self.frame = tk.Frame(height=2, bd=1, relief=tk.SUNKEN)
self.frame.pack(fill=tk.X, padx=5, pady=5)
self.label = tk.Label(self.frame, text="This Label is packed\nin the Password's Frame.")
self.label.pack(fill=tk.BOTH, expand=1)
Result:
Needless to say, it's not the desired effect. The "Label" part should be on the password window! Any clue why am I getting this result? Thanks in advance!!
The 1st porblem I can see is you are using Tk() twice here. Instead of using Tk() for a new window use Toplevel() instead. Toplevel is meant to be used to create new windows after the main window has been generated.
Next we need to pass the root window to the password class so we can use it as the top level of the main windows instance.
So in short your code should look like this:
mainWindow.py
import tkinter as tk
import password
class mainWindow(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.title("mainWindow")
self.geometry("{0}x{1}+20+20".format(50,50))
if __name__ == "__main__":
mainW = mainWindow()
passW = password.passwordWindow(mainW)
passW.resizable(False, False)
passW.attributes("-topmost", True)
mainW.mainloop()
password.py
import tkinter as tk
import mainWindow
class passwordWindow(tk.Toplevel):
def __init__(self, controller):
tk.Toplevel.__init__(self, controller)
self.title("Password")
self.frame = tk.Frame(self, height=2, bd=1, relief=tk.SUNKEN)
self.frame.pack(fill=tk.X, padx=5, pady=5)
self.label = tk.Label(self, text="This Label is packed\nin the Password's Frame.")
self.label.pack(fill=tk.BOTH, expand=1)
Results:

python tkinter.Radiobutton can't get value

I'm writing a python code with tkinter (python3) but I have some problems. I have two classes _MainScreen and _RegisterScreen (this last is nested in _MainScreen). In _RegisterScreen I had implemented a simple question with tkinter.Radiobutton (choose your sex: male, female). The idea is to catch the user selection, but when I run the script, the value assigned to the variable is empty (""). However, if I run the class _RegisterScreen alone, it works. I hope you can show me where is my error. Thanks in advance.
Here is an abstraction (32 lines) of my code (250 lines):
import tkinter
class _MainScreen(tkinter.Frame):
def __init__(self):
self.root = tkinter.Tk()
self.new_account(self.root)
self.root.mainloop()
def new_account(self, frame):
tkinter.Button(frame, text="Create new account",
command=self.create_new_account).pack(anchor="center", pady=(0,15))
def create_new_account(self):
_RegisterScreen()
class _RegisterScreen(tkinter.Frame):
def __init__(self):
self.root = tkinter.Tk()
tkinter.Label(self.root, text="Sex").grid(row=1, padx=(0,10), sticky="w")
self.sex_option = tkinter.StringVar()
tkinter.Radiobutton(self.root, text="Male", variable=self.sex_option,
value="Male", command=self._selected).grid(row=1, column=1)
tkinter.Radiobutton(self.root, text="Female", variable=self.sex_option,
value="Female", command=self._selected).grid(row=1, column=2)
tkinter.Button(self.root, text="Submit",
command=self._login_btn_clickked).grid(row=3, columnspan=4, pady=20)
self.root.mainloop()
def _login_btn_clickked(self):
sex = self._selected()
print(sex)
def _selected(self):
return self.sex_option.get()
_MainScreen()
#_RegisterScreen() # comment the above line and uncomment this line
# to test the _RegisterScreen object alone.
After doing some research on how tkinter's RadioButton widget works, I believe I have a solution to your problem:
Here's your new _RegisterScreen function:
class _RegisterScreen(tkinter.Frame):
def __init__(self):
self.gender = "NA" #Variable to be changed upon user selection
self.root = tkinter.Tk()
tkinter.Label(self.root, text="Sex").grid(row=1, padx=(0,10), sticky="w")
self.sex_option = tkinter.StringVar()
#This Radiobutton runs the setMale function when pressed
tkinter.Radiobutton(self.root, text="Male", variable=self.sex_option,
value="Male", command=self.setMale).grid(row=1, column=1)
#This Radiobutton runs the setFemale function when pressed
tkinter.Radiobutton(self.root, text="Female", variable=self.sex_option,
value="Female", command=self.setFemale).grid(row=1, column=2)
tkinter.Button(self.root, text="Submit",
command=self._login_btn_clickked).grid(row=3, columnspan=4, pady=20)
self.root.mainloop()
def _login_btn_clickked(self):
sex = self.gender #gets the value stored in gender and assigns it to sex
print(sex)
def setMale(self):
self.gender="Male" #sets gender to Male
def setFemale(self):
self.gender="Female" #sets gender to Female
Ultimately, you want to run 2 separate functions for either RadioButton.
When the Male Radiobutton gets clicked, it runs the setMale function.
When the Female Radiobutton gets clicked, it runs the setFemale function.
I believe you were confused about what RadioButton's variable and value attributes actually are (as was I before looking further into this).
I learned more about what those those do in this video: https://www.youtube.com/watch?v=XNF-y0QFNcM
I hope this helps! ~Gunner

Resources