Inheriting from Frame or not in a Tkinter application - python

I've seen two basic ways of setting up a tkinter program. Is there any reason to prefer one to the other?
from Tkinter import *
class Application():
def __init__(self, root, title):
self.root = root
self.root.title(title)
self.label = Label(self.root, text='Hello')
self.label.grid(row=0, column=0)
root = Tk()
app = Application(root, 'Sample App')
root.mainloop()
and
from Tkinter import *
class Application(Frame):
def __init__(self, title, master=None):
Frame.__init__(self, master)
self.grid()
self.master.title(title)
self.label = Label(self, text='Hello')
self.label.grid(row=0, column=0)
app = Application('Sample App')
app.mainloop()

The option I prefer* is to inherit from the class Tk. I think it is the more reasonable choice since the window is, in effect, your application. Inheriting from Frame doesn't make any more sense to me then inheriting from Button or Canvas or Label. Since you can only have a single root, it makes sense that that is what you inherit from.
I also think it makes the code more readable if you do the import as import Tkinter as tk rather than from Tkinter import *. All of your calls then explicitly mention the tk module. I don't recommend this for all modules, but to me it makes sense with Tkinter.
For example:
import Tkinter as tk
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.label = tk.Label(text="Hello, world")
self.label.pack(padx=10, pady=10)
app = SampleApp()
app.mainloop()
* Note: since originally writing this answer I have changed my position. I now prefer to inherit from Frame rather than Tk. There's no real advantage one way or the other, it's more of a philosophical choice than anything else. Regardless, I believe that whether you inherit from Frame or Tk, I think either choice is better than the first example in the code that inherits from nothing.
The one slight advantage inheriting from Frame has over Tk is in the case where you want your application to support multiple identical windows. In that case, inheriting from Frame lets you create the first window as a child of root, and additional windows as children of instances of Toplevel. However, I've seen very few programs that ever have a need to do this.
For more information about how I think Tkinter programs should be structured, see my answer to the question Python Tkinter program structure.

A Frame is usually used as a geometry master for other widgets.
Since an application usually has numerous widgets, you'll often want to contain them all in a Frame, or at least use the Frame to add some borderwidth, padding, or other nicety.
Many example snippets you might find on the web do not use a Frame because
they just want to demonstrate some feature in the shortest amount of code.
So, use a Frame if you need it, otherwise, do not.
Edit: I think the best way to organize a GUI is given in this Tkinter tutorial:
simpleApp.py:
import Tkinter as tk
class SimpleApp(object):
def __init__(self, master, **kwargs):
title=kwargs.pop('title')
frame=tk.Frame(master, **kwargs)
frame.pack()
self.label = tk.Label(frame, text=title)
self.label.pack(padx=10,pady=10)
if __name__=='__main__':
root = tk.Tk()
app = SimpleApp(root,title='Hello, world')
root.mainloop()
This is mainly like your first example in that SimpleApp inherits from object, not Frame. I think this is better than subclassing Frame since we are not overriding any Frame methods. I prefer to think of SimpleApp as having a Frame rather than being a Frame.
Having SimpleApp subclass object does have a significant advantage over subclassing tk.Tk, however: it makes it easy to embed SimpleApp in a larger app:
import simpleApp
import Tkinter as tk
class BigApp(object):
def __init__(self, master, **kwargs):
title=kwargs.pop('title')
frame=tk.Frame(master, **kwargs)
frame.pack()
self.simple = simpleApp.SimpleApp(frame,title=title)
frame.pack(padx=10, pady=10)
self.simple2 = simpleApp.SimpleApp(frame,title=title)
frame.pack()
if __name__=='__main__':
root = tk.Tk()
app = BigApp(root,title='Hello, world')
root.mainloop()
Thus, simpleApp.py can be a stand-alone script as well as an importable module.
If you try this with SimpleApp inheriting from tk.Tk, you end up with extra undesired windows.

There can be an advantage to setting your top level object to inherit from Tk instead of Frame. The advantage arises when you have dynamic element to your GUI, e.g. a Label whose contents you want to set with a textvariable=foo instead of text= 'Label text'.
In this case, it is very helpful to use the Tkinter.DoubleVar, Tkinter.IntVar, and Tkinter.StringVar objects to hold the data, since the GUI will automatically update whenever these objects are set. However, to use these objects, you must specify their master as the root Tkinter.Tk() instance running. This is easier if you explicitly make your main object be a subclass of Tkinter.Tk, then have that generate frames and widgets, so you can pass along the Tk instance and set up your variables properly.
Here is a short example program to illustrate the idea.
import Tkinter as tk
class Tkclass(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
app=Application(self)
app.master.title("Animal to Meat")
app.mainloop()
class Application(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.grid(sticky=tk.N+tk.S+tk.E+tk.W)
self.meatvar = tk.StringVar(master=parent)
self.meatvar.set("Meat?")
self.createWidgets()
def createWidgets(self):
top=self.winfo_toplevel()
top.rowconfigure(0, weight=1)
top.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
self.columnconfigure(0, weight=1)
self.columnconfigure(1, weight=1)
self.columnconfigure(2, weight=1)
self.columnconfigure(3, weight=1)
self.cowButton = tk.Button(self, text='Cow', command=self.setBeef)
self.cowButton.grid(row=0,column=0)
self.pigButton = tk.Button(self, text='Pig',command=self.setPork)
self.pigButton.grid(row=0,column=1)
self.meatLabel = tk.Label(self)
self.meatLabel.configure(textvariable=self.meatvar)
self.meatLabel.grid(row=0,column=2)
self.quit = tk.Button(self, text='Quit',command=self.QuitApp)
self.quit.grid(row=0, column=3)
def setBeef(self):
self.meatvar.set("Beef")
def setPork(self):
self.meatvar.set("Pork")
def QuitApp(self):
top=self.winfo_toplevel()
top.quit()
main = Tkclass()

Related

External window is empty with "tk.Toplevel" and "parent"

When I click on a label to open an external window (tertiary.py), the window opens normally but is empty inside. I don't get errors, but I don't see Tkinter widgets and miscellaneous items.
This is the secondary window where there is the label on which I click in order to open an external window. I add, for information purposes only, that this window is called secondary because it is displayed in a frame (with a vertical menu on the left) where the main window is called home = tk.Tk(). The secondary window opens and displays correctly in the frame.
Inside the secondary window there is the label thanks to which I want to open an external window (not in the frame) called tertiary
How can I solve?
secondary.py
import tkinter as tk
from tkinter import *
from tkinter import ttk
import other_options
from other_options import form_other_options
class Page2(tk.Frame):
def __init__(self, master, **kw):
super().__init__(master, **kw)
self.configure(bg='white')
bar1=Frame(self, width=2200, height=35, background="#960000", highlightbackground="#b40909", highlightthickness=0) #grigio chiaro #b40909
bar1.place(x=0, y=0)
other_options = Label(self, text="Other Options", bg="#960000", foreground='white', font='Ubuntu 10')
other_options.place(x=1025, y=7)
other_options.bind("<Button-1>", lambda event: other_options.form_other_options(self))
tertiary.py
from tkinter import *
from tkinter import ttk
import tkinter as tk
def form_other_options(parent):
other_options = tk.Toplevel(parent)
other_options.title("Other options")
other_options.geometry("1000x800")
other_options.config(bg="white")
other_options.state("normal")
other_options.transient(parent)
class checkbox():
def __init__(self, master, **kw):
super().__init__(master, **kw)
labelframe1 = LabelFrame(self, text="checkbox", width=600,height=190, bg="white", foreground='#e10a0a')
labelframe1.place(x=10, y=13)
Checkbutton1 = IntVar()
Button1 = Checkbutton(self, text = "option1",
variable = Checkbutton1,
onvalue = 1,
offvalue = 0,
height = 2,
width = 10)
Button1.pack()
other_options.mainloop()
The first reason why the window is empty is that you never create an instance of the class checkbox, so the code that creates the widgets never runs.
So, the first step is to make sure you create an instance of that class:
other_options = tk.Toplevel(parent)
...
cb = checkbox(other_options)
When you do that, you will discover other bugs in your code. The first positional argument when creating a tkinter widget must be another widget. If you want the widget to be inside a Toplevel, the parent needs to be the toplevel or a descendant. You're using self which isn't a widget at all.
A simple fix is to use master instead of self, since you're passing the toplevel window as the master parameter:
labelframe1 = LabelFrame(master, ...)
Button1 = Checkbutton(master, ...)
You also need to remove the call to other_options.mainloop(). Unless there is a very compelling reason to do otherwise, an entire application should only call mainloop() exactly once.
There are other problems in the code that, strictly speaking, aren't related to the question being asked. For example, you're importing tkinter twice: once as import tkinter as tk and once as from tkinter import *. You only need to import tkinter once, and PEP8 recommends to use the first form:
import tkinter as tk
You'll need to add the prefix tk. to every place where you call a tkinter function. This will help make your code easier to understand and helps prevent pollution of the global namespace.
You also should use PEP8 naming standards. Specifically, class names should begin with an uppercase character. This will also make your code easier to understand.
There's also no reason why your checkbox class should be indented. You should move it out of the function.
Also, instead of checkbox not inheriting from anything, and then creating a LabelFrame, you should just inherit from LabelFrame. That way you can use your class more like a real widget, and would enable you to use self rather than master when creating the other widgets.
class Checkbox(tk.LabelFrame):
def __init__(self, master, **kw):
super().__init__(master, **kw)
...
button1 = tk.Checkbutton(self, ...)
...
There are probably other problems, but those are the most obvious.

Having trouble with Tkinter using OOP

I have created a small application in tkinter before using just top down programming but I am starting another project, this time using OOP and classes. But I'm having a hard time getting started, I just need someone to point me in the right direction. I've already dabbled in OOP with PyGame but I'm having difficulty with tkinter. Heres my code, where i'm just trying to display a button to the screen:
import tkinter as tk
from tkinter import ttk as ttk
import sqlite3
class Button(tk.Frame):
def __init__(self):
tk.Frame.__init__(self)
tk.Button(root, text = "Hello", width = 25)
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.button = Button()
self.button.pack(side="bottom",fill="x")
if __name__ == "__main__":
root = tk.Tk()
MainApplication(root).pack(side="top", fill="both", expand=True)
root.mainloop()
Try this:
import tkinter as tk
from tkinter import ttk # The `as tkk` isn't needed
# Here you might want to consider inheriting from `tk.Button` but it isn't going to change anything
class Button(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
# It's always a good idea to keep a reference to all of your widgets
self.button = tk.Button(self, text="Hello", width=25)
# You should call `.pack`/`.grid`/`.place` here:
# Note it doesn't really matter which one you choose
self.button.pack(fill="both")
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent # Technically this isn't needed because the line above sets `self.master` to parent
self.button = Button(self) # Pass in self into the Button class
self.button.pack(side="bottom", fill="x")
if __name__ == "__main__":
root = tk.Tk()
main_app = MainApplication(root)
main_app.pack(side="top", fill="both", expand=True)
root.mainloop()
I passed in self when creating the Button object in self.button = Button() and I called self.button.pack(...) inside the Button class.
The whole point of OOP programming it to limit global variables and group similar objects in a single class definition. That is why both your Button and MainApplication classes shouldn't rely on root. Apart from that, your code is very nice :D

Can't put tkinter widget inside Toplevel Frame

I am trying to open a toplevel widget from a button press, and generate a list within that frame from an example I found. However, when I try to place the widget within the generated frame, I get the following error:
_tkinter.TclError: can't put .!treeview inside .!errorexample.!toplevel.!mclistdemo.!frame.!frame
I have narrowed down the problem to
self.tree.grid(in_=f, row=0, column=0, sticky=NSEW)
within the _create_treeview method. When the in_ command is removed, the widget is generated correctly in the parent window. I suspect that the problem has something to do with my parent/self naming conventions, but I am still struggling to grasp that subject.
Most of the questions I've run across that are described similarly seem to be a matter of trying to place the widget while generating it, but that doesn't seem to be the case in this code.
from tkinter import *
from tkinter import ttk
from tkinter.font import Font
class ErrorExample(Frame):
def __init__(self, parent):
Frame.__init__(self,parent)
self.grid()
self.parent=parent
self.b4=Button(
self,
text="Load",
command=lambda: self.createWindow())
self.b4.grid()
def createWindow(self):
self.t = Toplevel(self)
MCListDemo(self)
class MCListDemo(ttk.Frame):
def __init__(self, parent, isapp=True):
ttk.Frame.__init__(self, parent.t)
self.grid()
self.isapp = isapp
self._create_widgets()
def _create_widgets(self):
if self.isapp:
self._create_demo_panel()
def _create_demo_panel(self):
demoPanel = Frame(self)
demoPanel.grid()
self._create_treeview(demoPanel)
self._load_data()
def _create_treeview(self, parent):
f = ttk.Frame(parent)
f.grid()
self.dataCols = ('country', 'capital', 'currency')
self.tree = ttk.Treeview(columns=self.dataCols,
show = 'headings')
self.tree.grid(in_=f, row=0, column=0, sticky=NSEW)
start=Tk()
ErrorExample(start)
if __name__=="__main__":
main()
You don't give the treeview a parent, so it has the root window as a parent. Widgets live in a hierarchy, and widget's can't be placed in a different part of the hierarchy.
The official documentation describes it like this:
The master for each slave must either be the slave's parent (the default) or a descendant of the slave's parent. This restriction is necessary to guarantee that the slave can be placed over any part of its master that is visible without danger of the slave being clipped by its parent.
If you want the treeview to be in f, the simplest way is to make f be the parent:
self.tree = ttk.Treeview(f, ...)
self.tree.grid(row=0, column=0, sticky=NSEW)

Inconsistent Tkinter behavior at runtime after code changes

Note: I am not a programmer by trade or education, so bear with me. Take the following simple application:
import tkinter as tk
root = tk.Tk()
class app(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.pack()
self.create_widgets()
def create_widgets(self):
self.button1= tk.Button(self, text='1')
self.button1.pack(side='top')
self.quit_button= tk.Button(self, text='quit', command=self.quit_button.destroy())
self.quit_button.pack(side='bottom')
application = app(root)
application.mainloop()
When I run this code, I am told destroy() isn't an method of quit_button. If I change it to:
self.quit_button= tk.Button(self, text='quit', command=self.dest)
and I add the method:
def dest(self):
self.quit_button.destroy()
then it works - I assume this has something to do with the button being unable to reference itself while it's being created (please correct/enlighten me on this if you can since I don't fully understand this behavior).
However, what I'm really asking about is that the first time the program is run after I get this error, I get one window with both buttons, and X extra windows with only button1 (packed appropriately) where X is the number of times I incurred the error. Obviously I can just "not do that" but it would be extremely educative for me to understand how tkinter could be behaving this way. Is anyone here familiar enough with OOP to help? I create the root window before anything else, so there should be only one in any case.
You've got a couple of problems in your code. All in the line:
self.quit_button= tk.Button(self, text='quit', command=self.quit_button.destroy())
The first is you're trying to reference self.quit_button at the same time you're creating it. The second, somewhat related issue, is the command=self.quit_button.destroy() part actually tries to call one of this non-existent Button's methods rather than just supplying a reference to it (because of the ()s following its name).
Here's a version with those problems fixed:
import tkinter as tk
root = tk.Tk()
class app(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.pack()
self.create_widgets()
def create_widgets(self):
self.button1= tk.Button(self, text='1')
self.button1.pack(side='top')
self.quit_button = tk.Button(self, text='quit')
self.quit_button.config(command=self.quit_button.destroy) # set function to execute
self.quit_button.pack(side='bottom')
application = app(root)
application.mainloop()

Python Tkinter class structure practice

#game class
import Tkinter as tk
class Game(tk.Canvas):
def __init__(self, master):
canvas = tk.Canvas(master)
canvas.pack()
button = tk.Button(canvas, text='Quit', command=self.quit_game)
button.pack()
def quit_game(self):
root.destroy()#Should i put something else here?
root = tk.Tk()
game = Game(root)
root.mainloop()
Is it good practice, or, in other words, is there a problem with inheriting from canvas directly instead of frame, if for example I am not going to add any widgets except the canvas?
Another question I have is regarding the root.destroy(). I don't understand why I can't say master.destroy() or something to that effect.
There is nothing wrong with inheriting from Canvas or any other Tkinter widget.
re master.destroy() vs root.destroy(): you can call it however you want. You simply need a reference to the root window. If you call it root, to destroy it you would call root.destroy().
In general you should avoid the use of global variables. Given that you're passing in the root widget to your class, you can save a reference and use that instead:
class Game(tk.Canvas):
def __init__(self, master):
self.master = master
...
def quit_game(self):
self.master.destroy()

Categories

Resources