Is there a way to dynamically change the widgets displayed using Tkinter? - python

I am building a GUI with Tkinter in which I would like to give users the option of changing an entry widget to a label widget (and vice-versa) by clicking a button.
I have tried a few different approaches but can't get it to work. Here is one of the ways I've tried to approach this problem:
import tkinter as tk
show_label = False
class App(tk.Tk):
def __init__(self):
super().__init__()
label = tk.Label(self, text="This is a label")
entry = tk.Entry(self)
button = tk.Button(self, text="Label/Entry",
command=self.change)
if show_label:
label.pack()
else:
entry.pack()
button.pack()
def change(self):
global show_label
show_label = not show_label
self.update()
if __name__ == '__main__':
app = App()
app.mainloop()
Aside from the above, I have also tried:
Updating the instance of app inside the mainloop, i.e. after instantiating app = App()
Making show_label a class variable and the change() a class method
Any help on the matter is greatly appreciated!
Thanks

It appears that the mistake you're making is thinking that the code in __init__ runs more than once. It only runs once when you create an instance of App.
To fix your code, move the logic for displaying the entry or label into the code that runs when you click the button. Also, you need to use instance variables to hold references to the widgets so that you can refer to them in other functions.
import tkinter as tk
class App(tk.Tk):
def __init__(self):
super().__init__()
self.label = tk.Label(self, text="This is a label")
self.entry = tk.Entry(self)
self.button = tk.Button(self, text="Label/Entry",
command=self.change)
self.button.pack()
self.show_label = False
def change(self):
self.show_label = not self.show_label
if self.show_label:
self.entry.pack_forget()
self.label.pack()
else:
self.label.pack_forget()
self.entry.pack()
if __name__ == '__main__':
app = App()
app.mainloop()

Related

How to run a function as a button command in tkinter from a 2nd window

Hi I am pretty new to tkinter and have being trying to create a button that opens a window then a have a button in the new window the gives a message when pressed. I ran into the problem that the only whay I could get it to recognise the function I wrote was to write it inside the function that opens the second window. I don't know if I have being searching for the wrong things but I can't find how to do this properly. Can someone help me out Here is my code
from tkinter import *
master = Tk()
master.title("frame control")
def win():
window2 = Toplevel()
def open():
stamp = Label(window2, text="Staped").pack()
lab2 = Button(window2,text = "yo ",command = open).pack()
lab1 = Button(master,text = " open a new window" , command = win).pack()
mainloop()
This is your code but with best practises:
import tkinter as tk
def create_stamp():
stamp = tk.Label(window2, text="Stamp")
stamp.pack()
def create_second_win():
global window2
window2 = tk.Toplevel(root)
lab2 = tk.Button(window2, text="Click me", command=create_stamp)
lab2.pack()
root = tk.Tk()
root.title("Frame control")
button = tk.Button(root, text="Open a new window", command=create_second_win)
button.pack()
root.mainloop()
I made window2 a global variable so that I can access it from create_stamp. Generally it is discouraged to use from ... import *. As #Matiiss said, sometimes you can have problems with global variables if you don't keep track of the variable names that you used.
If you want to avoid using global variables and want to use classes, look at this:
import tkinter as tk
class App:
def __init__(self):
self.stamps = []
self.root = tk.Tk()
self.root.title("Frame control")
self.button = tk.Button(self.root, text="Open a new window", command=self.create_second_win)
self.button.pack()
def create_stamp(self):
stamp = tk.Label(self.window2, text="Stamp")
stamp.pack()
self.stamps.append(stamp)
def create_second_win(self):
self.window2 = tk.Toplevel(self.root)
self.lab2 = tk.Button(self.window2, text="Click me", command=self.create_stamp)
self.lab2.pack()
def mainloop(self):
self.root.mainloop()
if __name__ == "__main__":
app = App()
app.mainloop()
As #Matiiss mentioned it would be more organised if you move the second window to its own class. For bigger projects it is a must but in this case you don't have to.

python toplevel runs even after root is killed

I've included some basic code below, which generates a frame, and then a toplevel made to destroy itself. A second is created after the first is destroyed.
When this application is run, while the first toplevel is waiting, if the 'X' on the main window is clicked, it kills itself and the toplevel, but then the second toplevel is created along with a generic Tk(). When that is closed I get an error: _tkinter.TclError: can't invoke "wm" command: application has been destroyed
I've tried using root.destroy(), quit() and os._exit(), but none of these completely stops the application. What can be done to completely stop any script from running after the root window is destroyed?
from tkinter import *
class Application(Frame):
def __init__(self,master):
Frame.__init__(self,master)
self.L1 = Label(root,text='Hi!')
self.L1.pack()
def Window1():
Wind1 = Toplevel()
Wind1.geometry('100x100+100+100')
Wind1.B1 = Button(Wind1,text='Close',command=Wind1.destroy)
Wind1.B1.pack()
Wind1.lift(aboveThis=root)
Wind1.wait_window()
def Window2():
Wind2 = Toplevel()
Wind2.geometry('100x100+100+100')
Wind2.B2 = Button(Wind2,text='Close',command=Wind2.destroy)
Wind2.B2.pack()
Wind2.lift(aboveThis=root)
Wind2.wait_window()
def Close_Window():
root.destroy()
root = Tk()
root.geometry('100x100+50+50')
root.protocol('WM_DELETE_WINDOW',Close_Window)
app = Application(root)
Window1()
Window2()
root.mainloop()
The exact reason for your error is caused by 2 problems. One is that both windows are not being created at start up due to the wait_window() method. The other problem is the lack of a parent being defined for your Toplevel() windows.
Take a look at the below modified code. (Note this code needs some work still but is what you need to change to fix the error)
from tkinter import *
class Application(Frame):
def __init__(self,master):
Frame.__init__(self,master)
self.L1 = Label(root, text='Hi!')
self.L1.pack()
def Window1():
Wind1 = Toplevel(root)
Wind1.geometry('100x100+100+100')
Wind1.B1 = Button(Wind1,text='Close',command=Wind1.destroy)
Wind1.B1.pack()
Wind1.lift(aboveThis=root)
#Wind1.wait_window()
def Window2():
Wind2 = Toplevel(root)
Wind2.geometry('100x100+100+100')
Wind2.B2 = Button(Wind2,text='Close',command=Wind2.destroy)
Wind2.B2.pack()
Wind2.lift(aboveThis=root)
#Wind2.wait_window()
def Close_Window():
root.destroy()
root = Tk()
root.geometry('100x100+50+50')
root.protocol('WM_DELETE_WINDOW',Close_Window)
app = Application(root)
Window1()
Window2()
root.mainloop()
I think you would benifit more from moving everything into a class. This way you can use class attributes to manage all data within the application including those you get from Toplevel() widgets.
import tkinter as tk
class Application(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.geometry('100x100+50+50')
self.protocol('WM_DELETE_WINDOW', self.close_window)
self.L1 = tk.Label(self, text='Hi!')
self.L1.pack()
tk.Button(self, text="Window 1", command=self.window1).pack()
tk.Button(self, text="Window 2", command=self.window2).pack()
def window1(self):
wind1 = tk.Toplevel(self)
wind1.geometry('100x100+100+100')
wind1.B1 = tk.Button(wind1, text='Close', command=wind1.destroy).pack()
def window2(self):
wind2 = tk.Toplevel(self)
wind2.geometry('100x100+100+100')
wind2.B2 = tk.Button(wind2, text='Close', command=wind2.destroy).pack()
def close_window(self):
self.destroy()
app = Application()
app.mainloop()

Accessing child widgets of a window by their names?

Is there any way in Python/tkinter to access child elements referring by their variable names, but from an other function?
For example in VBA, it is possible to directly refer to an element of an other window by its name.
For example if I have two windows, UserForm1 and UserForm2 I can change the text value of Label1 of UserForm2 by clicking a button on UserForm1.
Private Sub CommandButton1_Click()
UserForm2.Label1.Caption = "Changed"
End Sub
In tkinter I have found the winfo_children() to access child elements. Is there any way to access them by their names?
See my sample code below:
import tkinter
from tkinter import *
#----------------------------------------------------------------------
def new(parent_window):
""""""
parent_window.withdraw()
global main_window
global new_window
new_window = tkinter.Tk()
new_window.title("My App - New")
label1 = tkinter.Label(new_window, text="NEW")
label1.grid(row=0,column=0,columnspan=3,pady=10,padx=10, sticky="nsw")
b1 = tkinter.Button(new_window, text="Change It", command=lambda: showdashboard(new_window))
b1.grid(row=4,column=1,padx=20,pady=10,sticky="nwse")
b2 = tkinter.Button(new_window, text="Quit", command=lambda: quit())
b2.grid(row=5,column=1,padx=20,pady=10,sticky="nwse")
#----------------------------------------------------------------------
def dashboard(parent_window):
""""""
parent_window.withdraw()
global main_window
global dashboard_window
dashboard_window = tkinter.Tk()
dashboard_window.title("My App - Dashboard")
label1 = tkinter.Label(dashboard_window, text="Dashboard")
label1.grid(row=0,column=0,columnspan=3,pady=10,padx=10, sticky="nsw")
b1 = tkinter.Button(dashboard_window, text="New", command=lambda: new(dashboard_window))
b1.grid(row=4,column=1,padx=20,pady=10,sticky="nwse")
#----------------------------------------------------------------------
def showdashboard(parent_window):
""""""
parent_window.withdraw()
dashboard_window.update()
dashboard_window.deiconify()
#This way it works <<<<<<<<<<<<<<------!!!!!!!
byID=dashboard_window.winfo_children()
byID[0].config(text="change the value")
#But I am looking for somethin like this <<<<<<<<<<<<<<------????
dashboard_window.label1.config(text="changed the value")
#----------------------------------------------------------------------
main_window=tkinter.Tk()
main_window.title("MyApp")
label = tkinter.Label(main_window, text="My App")
label.grid(row=0,column=0,pady=10,padx=10,sticky="nwse")
b1 = tkinter.Button(main_window, text="Dashboard", command=lambda:dashboard(main_window))
b1.grid(row=1,column=0,padx=20,pady=10,sticky="nwse")
main_window.mainloop()
winfo_children() returns an instance of the class associated with the type of widget along with the name that tkinter assigned to the actual tk object.
This means that yes, we can refer to the name of widget, although I'm not sure what advantage this would really give you other than not needing to assign the label to a variable.
from tkinter import *
root = Tk()
Label(root, text="Label1").pack()
label2 = Label(root, name="name", text="Label2")
label2.pack()
print(root.winfo_children())
print(root.nametowidget('.!label'))
print(str(label2))
Button(root, text="Delete label2", command=lambda: root.nametowidget(".name").destroy()).pack()
The above will result in two Label widgets and a Button widget appearing in the window. The first Label is not stored in a variable and yet we can quite happily call it inside the print statement. The second is stored in a variable but you can see that in the command of the Button we don't refer to the variable but the name attribute of the Label.
Bryan Oakley has a fantastic answer here explaining this.
I'm not sure what you mean by names in:
"Is there any way in Python/tkinter to access child elements referring by their names?"
You can access widgets simply by their object references:
# Procedural
import tkinter as tk
def change():
object_reference['text'] = "Changed!"
root = tk.Tk()
object_reference = tk.Label(root, text="This is a label for root-window")
object_reference.pack()
another_window = tk.Toplevel(root)
btn_in_other_window = tk.Button(another_window, text="Change")
btn_in_other_window['command'] = change
btn_in_other_window.pack()
root.mainloop()
or if they were to be defined with more of an object-oriented approach, you can make use of the .(dot) notation:
#oop
import tkinter as tk
class App1(tk.Toplevel):
def __init__(self, master):
super().__init__()
self.label = tk.Label(self, text="This is App1's label")
self.label.pack()
class App2(tk.Toplevel):
def __init__(self, master):
super().__init__(master)
self.button = tk.Button(self, text="This is a App2's button")
self.button.pack()
def change(label):
label['text'] = "Changed!"
if __name__ == '__main__':
root = tk.Tk()
root.withdraw()
app1 = App1(root)
app2 = App2(root)
app2.button['command'] = lambda label=app1.label: change(label)
root.mainloop()
As an alternative, it is possible to get a list of the children widgets of the element window with:
root.update()
lista = list(root.children.values())
Then it is possible to refer to the list elements and do whatever with the widget itself. For example to get the width of the first widget do print(lista[0].winfo_width()).
Warning: I am not sure that the list contains the elements in the same order that the widgets appear in the script, although for me it worked in this order. Hopping someone will write in the comments.

Blocking Input Dialog Box

How can I get a blocking modal input dialog box in standard Python?
I need the user to input a value before the code can proceed.
Here is some not-working test code, but the idea is that I should be able to call MyDialog from anywhere in the script, so this is just a simplified example.
import tkinter
class MyDialog:
def __init__(self, prompt):
self.top = tkinter.Toplevel()
tkinter.Label(self.top, text=prompt).pack()
self.e = tkinter.Entry(self.top)
self.e.pack(padx=5)
tkinter.Button(self.top, text="OK", command=self.ok).pack(pady=5)
def ok(self):
self.top.destroy()
return self.e.get()
root = tkinter.Tk()
userName = MyDialog('Enter your name')
tkinter.Label(root, text="Hello {}".format(userName)).pack()
root.mainloop()
The dialog should not only disable the master window, but block whatever code called it. And it should be able to pass the value back to the calling code.
The solution requires two critical pieces. First, use grab_set to block all events in the other window (or, more correctly, send all events to the dialog window). Second, use wait_window to prevent the method from returning until the dialog has been destroyed.
That being said, you shouldn't be using it like in your example. You need to have the mainloop running before you create the window. It might work OK on some platforms, but in general you shouldn't expect your GUI to behave properly until mainloop is running.
Here's a simple example:
import Tkinter as tk
class MyDialog(object):
def __init__(self, parent, prompt):
self.toplevel = tk.Toplevel(parent)
self.var = tk.StringVar()
label = tk.Label(self.toplevel, text=prompt)
entry = tk.Entry(self.toplevel, width=40, textvariable=self.var)
button = tk.Button(self.toplevel, text="OK", command=self.toplevel.destroy)
label.pack(side="top", fill="x")
entry.pack(side="top", fill="x")
button.pack(side="bottom", anchor="e", padx=4, pady=4)
def show(self):
self.toplevel.grab_set()
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, text="", width=40)
self.label.pack(side="top", fill="x")
self.button.pack(padx=20, pady=20)
def on_click(self):
result = MyDialog(self, "Enter your name:").show()
self.label.configure(text="your result: '%s'" % result)
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()

Python: Tkinter OK button callback function

I'm working on my very first Python GUI and I'm trying to modify this tkinter example, but I simply cannot figure out how to write a callback function for the OK button that will pass on the entered value to the main program.
#!/usr/bin/python
# -*- coding: utf-8 -*-
from Tkinter import Tk, BOTH, StringVar, IntVar
from ttk import Frame, Button, Style, Label, Entry
class Example(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.initUI()
def initUI(self):
self.parent.title("Get Value")
self.style = Style()
self.style.theme_use("default")
self.pack(fill=BOTH, expand=1)
valueLabel = Label(self, text="Value: ")
valueLabel.place(x=10, y=10)
value=StringVar(None)
value.set("this is the default value")
valueEntry=Entry(self, textvariable=value)
valueEntry.place(x=70, y=10)
quitButton = Button(self, text="Quit", command=self.quit)
quitButton.place(x=10, y=50)
okButton = Button(self, text="OK", command=self.quit)
okButton.place(x=120, y=50)
def main():
root = Tk()
root.geometry("220x100+300+300")
app = Example(root)
root.mainloop()
if __name__ == '__main__':
main()
I've read a gazillion of tutorials, but none of them explains this clearly. Theoretically, I should be able to get the selected value with value.get(), but I keep getting error messages no matter where I put it. Also, AFAIK, I should be able to define a default value with value.set(), but this doesn't seem to have an effect, since the text box is empty when I run the program.
What is the easiest way to pass on values to the main python program after root.mainloop() terminates? (The actual dialog box contains several entry boxes for entering string and integer values.)
I.e. I want to be able to use something like:
root = Tk()
root.geometry("220x100+300+300")
app = Example(root)
root.mainloop()
print value
print value2
print value3
How do I define default values for entry boxes?
Change every occurrence of the value variable with self.value. This should fix it and the default value will be displayed.
UPDATE
from Tkinter import Tk, BOTH, StringVar, IntVar
from ttk import Frame, Button, Style, Label, Entry
class Example(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.initUI()
def showMe(self):
print(self.value.get())
def initUI(self):
self.parent.title("Get Value")
self.style = Style()
self.style.theme_use("default")
self.pack(fill=BOTH, expand=1)
valueLabel = Label(self, text="Value: ")
valueLabel.place(x=10, y=10)
self.value=StringVar(None)
self.value.set("this is the default value")
valueEntry=Entry(self, textvariable=self.value)
valueEntry.place(x=70, y=10)
quitButton = Button(self, text="Quit", command=self.quit)
quitButton.place(x=10, y=50)
okButton = Button(self, text="OK", command=self.showMe)
okButton.place(x=120, y=50)
def main():
root = Tk()
root.geometry("220x100+300+300")
app = Example(root)
root.mainloop()
if __name__ == '__main__':
main()
Both your quitButton and okButton call the self.quit functions. So no mater what value you enter when you press the OK button you are calling the quit function which has its own problems as well outside the scope of your question.
Try to define value as self.value and make the okButton call a function that does: print self.value.get().

Categories

Resources