I am working on simple application trying to utilize MVC pattern. The problem I found (which I predict is a really silly one, but I lost my rubber duck) is two tkinter apps are created where I expect just a single one. Moreover, second one is instantiated but is not visible what made me mad initially as I didn't know it exists ;-)
Smells to me like another Tk() instance is being created, but donno when, where and why.
When I replace root = Main() with root = tk.Tk() in AppDelegte everything works like a charm and just one app window is created, as intended.
App is created on following code:
App delegate
from controller.main import Main
root = Main()
root.mainloop()
Main controller
import tkinter as tk
from tkinter import ttk
from view.actual_readings import ActualReadings
class Main(tk.Tk):
actual_readings = ActualReadings(None)
def __init__(self):
super().__init__()
main_frame = ttk.Frame(self, padding="25")
main_frame.grid(column=0, row=0)
self.actual_readings.master = main_frame
self.actual_readings.grid(column=0, row=0)
Readings view
from tkinter import *
from tkinter import ttk
class ActualReadings(ttk.Frame):
def __init__(self, master, **kwargs):
super().__init__(master, **kwargs)
header = ttk.Label(self, text="header text", font="TkFixedFont", padding="75 25 75 10")
current_value_lbl = ttk.Label(self, text="current value",
font="TkFixedFont")
current_value_val = ttk.Label(self, text="here comes reading", font="TkFixedFont")
header.grid(column=0, row=0, columnspan=2)
current_value_lbl.grid(column=0, row=1, sticky=E)
current_value_val.grid(column=1, row=1, sticky=W)
It is because when you create Main (a Tk() instance), its class member actual_readings is created first. Since its parent is None which causes a default instance of Tk() being created. So there will be two Tk() instances.
To fix the issue, you should create actual_readings inside __init__() function:
class Main(tk.Tk):
actual_readings = None
def __init__(self):
super().__init__()
main_frame = ttk.Frame(self, padding='25')
main_frame.grid(row=0, column=0)
if self.actual_readings is None:
self.actual_readings = ActualReadings(main_frame)
self.actual_readings.grid(row=0, column=0)
Related
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()
I am learning Python and tkinter by going through several tutorials (python-course.eu & effbot.org). I have come across a pair of situations and am trying to learn the reasons behind the differences.
NOTE: I found many questions regarding __init__ in class, but not __init__ in frame.
Both call a class from the main, and use a constructor (def __init__) to create an instance of the class. Am I using correct terminology?
They both create a frame; however, one uses Frame.__init__(self,parent): and the other uses Frame(master):
Why does the first include __init__ in the construction of the frame?
Why does the second omit 'self' in the arguments when creating the frame?
Why does the first not use 'pack' to place, but the second does?
What other differences are noteworthy?
The following are MWE to produce the respective outputs.
import tkinter as tk
class Checkbar(tk.Frame):
def __init__(self, parent=None, picks=[], side=tk.LEFT, anchor=tk.W):
tk.Frame.__init__(self, parent)
self.vars = []
for pick in picks:
var = tk.IntVar()
chk = tk.Checkbutton(self, text=pick, variable=var)
chk.pack(side=side, anchor=anchor, expand=tk.YES)
self.vars.append(var)
def state(self):
return map((lambda var: var.get()), self.vars)
if __name__ == '__main__':
root = tk.Tk()
lng = Checkbar(root, ['Python', 'Ruby', 'Perl', 'C++'])
tgl = Checkbar(root, ['English','German'])
lng.pack(side=tk.TOP, fill=tk.X)
tgl.pack(side=tk.LEFT)
lng.config(relief=tk.GROOVE, bd=2)
def allstates():
print(list(lng.state()), list(tgl.state()))
tk.Button(root, text='Quit', command=root.destroy).pack(side=tk.RIGHT)
tk.Button(root, text='Peek', command=allstates).pack(side=tk.RIGHT)
root.mainloop()
print("You've selected ", list(lng.state()))
Language Widget
import tkinter as tk
class App:
def __init__(self, master):
frame = tk.Frame(master)
frame.pack()
self.button = tk.Button(frame, text="Quit", fg="red", command=master.destroy, padx=20)
self.button.pack(side='left', expand=10)
self.hi_there = tk.Button(frame, text="Hello", command=self.say_hi)
self.hi_there.pack(side='left')
def say_hi(self):
print("Hi there, everyone!")
if __name__ == '__main__':
root = tk.Tk()
app = App(root)
root.mainloop()
Hello Window
Thank you, kindly.
I have a small GUI app in Python that uses Tk. It has several filters --- a text entries (they set filter values) with checkboxes (which set filter on/off). I create filters as a class inhetrited from ttk's Labelframe:
from tkinter.ttk import Labelframe
import tkinter as tk
class FilterWidget(Labelframe):
def __init__(self, parent, label):
Labelframe.__init__(self, parent, text=label)
self.grid()
self._entry = tk.Entry(self)
self._entry.grid(row=0, column=0)
self._checkbox = tk.Checkbutton(self, command=lambda: print(self))
self._checkbox.grid(row=0, column=1)
Then I create several instances of this class and place them in GUI:
root = tk.Tk()
source_filter = FilterWidget(root, "Source")
source_filter.grid(row=0, column=0)
level_filter = FilterWidget(root, "Severity")
level_filter.grid(row=0, column=1)
root.mainloop()
The widgets are created and correctly displayed. However, when one of the checkboxes is clicked and changes state, the other changes state as well!
When diffrent checkboxes are clicked, print command outputs .!filterwidget and .!filterwidget2, so those are separate objects. It seems that they are somehow implicitly syncronized, but I have no idea how did this happend.
So, the question is: how to remove this dependancy and make checkboxes independant of each other?
As the docs mention,
To use a Checkbutton, you must create a Tkinter variable. To inspect
the button state, query the variable.
Here's your code updated to use an IntVar to store the Checkbutton state.
from tkinter.ttk import Labelframe
import tkinter as tk
class FilterWidget(Labelframe):
def __init__(self, parent, label):
Labelframe.__init__(self, parent, text=label)
self._entry = tk.Entry(self)
self._entry.grid(row=0, column=0)
self._var = tk.IntVar()
callback = lambda: print(self._entry.get(), self._var.get())
checkbox = tk.Checkbutton(self, variable=self._var, command=callback)
checkbox.grid(row=0, column=1)
root = tk.Tk()
source_filter = FilterWidget(root, "Source")
source_filter.grid(row=0, column=0)
level_filter = FilterWidget(root, "Severity")
level_filter.grid(row=0, column=1)
root.mainloop()
The following script creates a tkinter window with a text label, exit button and change-text button:
from tkinter import *
from tkinter import ttk
class Window(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.master = master
self.init_window()
def init_window(self):
test_label = Label(root, text="none").grid(row=0, column=0, sticky=W)
change_text_btn = Button(root, text="change_text", command=self.set_label_text).grid(row=2, column=0, sticky=W)
exit_btn = Button(root, text="Exit", command=self.client_exit).grid(row=2, column=1, sticky=W)
def set_label_text(self):
test_label.config(text='changed the value')
def client_exit(self):
exit()
if __name__ == '__main__':
root = Tk()
app = Window(root)
root.mainloop()
After click on change_text_btn I get a NameError: name 'test_label' is not defined error. So the problem is that test_label created in init_window() is not avaliable from set_label_text() beacuse of the scope. How do I fix it?
To overcome the issue, you can make test_label an instance variable by prefixing it with self. Besides that, when you chain methods like that, what happens is you assign None to your variable, since grid() returns None - instead, place each method in a separate line (this stands for all of your buttons):
self.test_label = Label(root, text="none")
self.test_label.grid(row=0, column=0, sticky=W)
Of course, you'd need to refer to it with self.test_label later on in your set_label_text function.
Other than that, I suggest you get rid of from tkinter import *, since you don't know what names that imports. It can replace names you imported earlier, and it makes it very difficult to see where names in your program are supposed to come from. Use import tkinter as tk instead.
Widget tk.Entry from example_script.py do not save value 'textvariable' field.
example_script.py:
import Tkinter as tk
class App(tk.Frame):
def __init__(self, master, text):
tk.Frame.__init__(self, master)
textVar = tk.StringVar()
textVar.set(text)
entryVar = tk.Entry(self, textvariable=textVar).pack()
self.pack()
def main():
root = tk.Tk()
text = ['text1', 'text2', 'text3']
for i in text:
App(root, i)
root.mainloop()
main_script.py:
import Tkinter import example_script as ex
if __name__ == '__main__':
root = Tkinter.Tk()
Tkinter.Button(root, text='press', command=lambda: ex.main()).pack()
root.mainloop()
If I change row 'entryVar = tk.Entry(self, textvariable=textVar).pack()' to
entryVar = tk.Entry(self)
entryVar.pack()
entryVar.insert(0, text)
field's value is updated. Why?
How will be correct open new window from imported script? Tkinter.Toplevel() is not suitable. Now I use subprocess.Popen.
When you do entryVar = tk.Entry(self).pack(), entryVar will be set to None because that is what pack() returns. When you call pack on a separate line, entryVar gets set to what you think it does.
You cannot create two instances of the Tk class in one program. Tkinter is not designed to work that way.