Python: why must Tkinter class instantiation use a Frame? - python

If I want to create a Tkinter GUI simply with statements, I can do this:
from Tkinter import *
root = Tk()
root.title("Test Window")
tkFrame = Frame(root)
tkButton = Button(tkFrame)
[...]
The documentation, however, advises that Tkinter be used with a class definition, subclassing a Frame widget:
class App(Frame):
[...]
I would like to understand why that is so. Why can't we subclass the Frame's container, the window? It appears that is what is done with statements in the first example, so why not in a class definition?
EDIT (following Bryan Oakley's answer):
I would like to instantiate at the highest level of Tkinter, which I assume to be Tk() (though I have come across references stating Frame is the top level, but never mind). Indeed, the following will create a window:
from Tkinter import *
class Application(Tk):
pass
app = Application()
app.mainloop()
...but as soon as I try to add widgets I either get errors or two windows, with the widgets in a new window, depending on how I structure the code. Here's a basic example that will produce a second window with the button:
from Tkinter import *
class Application(Tk):
tkBtn = Button()
tkBtn.pack()
app = Application()
app.mainloop()
Anything more, using self, __init__, etc., produces errors. Could someone point me to working code that instantiates Tkinter at the highest level? Just like all the Frame subclasses I'm seeing, but at the highest level?

There is nothing that says a tkinter class must inherit from a frame. You can inherit from any of the tkinter widgets, or any other classs. If you have found documentation that states otherwise, that documentation is wrong. Using Frame is a logical choice since it is designed to be a container of other widgets, but it is not the only choice.
Personally I inherit from a frame because I find it convenient. Some of my GUIs need the ability to open more than one identical window. By having my main code in a Frame I am able to create multiple windows simply by creating multiple instances of the frame, and packing them in Toplevel widgets.
When you inherit from Tk, you can only have a single instance. In the real world that's usually enough, and there's absolutely nothing wrong with doing it that way. Since I personally write a fair number of tkinter programs, having them all start out exactly the same is convenient for me.
Another good choice is a Canvas since you can easily add a background image, which is not something you can do with a Frame.
Bottom line: you are absolutely not required to inherit from Frame. Inherit from whatever you want.
(the following was written in response to an edit of the original question)
In reference to this code:
from Tkinter import *
class Application(Tk):
tkBtn = Button()
tkBtn.pack()
app = Application()
app.mainloop()
The reason you see two windows is that you're not creating the class properly. You need to call the __init__ method of the superclass before creating widgets, because that's what actually creates the root window. Because you don't, you end up with two windows. You get one that is created implicitly when you add a button to a not-yet-constructed root window, and you get another when your subclass finishes initializing.
The solution is to not take shortcuts, and instead initialize the class properly:
from Tkinter import *
class Application(Tk):
def __init__(self):
Tk.__init__(self)
tkBtn = Button()
tkBtn.pack()
app = Application()
app.mainloop()
Note that this isn't a tkinter-specific problem. When subclassing, unless you have explicit reasons to do otherwise, you always should call the __init__ method of the superclass.
You asked for working examples, here are a couple:
https://stackoverflow.com/a/22424245/7432
https://stackoverflow.com/a/11405393/7432
You might also want to read the responses in the question Inheriting from Frame or not in a Tkinter application

Related

PyQt5 Controller cannot access children names

for a python program I created a gui with QtDesigner. Inside my program the gui is initiated and calls the .ui-file to implement content.
The gui class object is than given to a Controller, and here it gets tricky:
Instead of one Controller, there are a main controller and some sub-controller for different parts of the gui. The main Controller does one or two general things and than hands over different parts of the gui to different sub controller.
See the following example for better understanding:
import Controller # import the folder with the controller
def __init__(self, window):
self.window = window # self.window is the handed over gui
self.sub_controls()
def sub_controls(self):
Controller.Sub_Controller_1(window = self.window.part_1)
Controller.Sub_Controller_2(window = self.window.part_2)
Controller.Sub_Controller_3(window = self.window.part_3)
...
The sub-Controller is set up like this:
def __init__(self, window):
self.window = window
... # do stuff:
-----------------------------------------
by trying to call a widget in this Sub-Controller (lets say its a label called timmy), i get an error:
self.window.timmy.setVisible(False)
AttributeError: 'QWidget' object has no attribute 'timmy'
but by using the children()-Method, which returns a list of all children in the gui, I may access this label:
self.window.children()[1].setVisible(False)
This works well and hides timmy.
By trying to do this in the main Controller, it works fine as usual:
self.window.timmy.setVisible(False) # works fine here
I also tried to save the sub-controller object like this:
def sub_controls(self):
self.save_part_1 = Controller.Sub_Controller_1(window = self.window.part_1)
but this doesn't work.
Does any one have a suggestion, how I could solve this Problem?
Sure, I couldt access just all widgets with the children()-method-list, but this is laborious because of the great amount of widgets. Same thing applies to reassigning every child.
PS:
Unfortunately I cannot show you the original Code due to company guidelines.
Ok so I figured out, that the tree Structure of in each other nested widgets has nothing to do with the naming of the widgets. For example:
MainWindow
->centralWidget
->otherWidget
->Button
In this you can address "Button" with self.Button, because (how I believe) the name is saved in MainWindow-Level. If you cut out "otherWidget", the QPushButton in it still exists, but cannot addressed by name.
So it's not possible to just hand over a part of your Gui to a Handler, at least if it's build with QtDesigner.
My Solution for me to the original Problem is to hand over the complete Gui to every sub handler:
def __init__(self, window):
self.window = window # self.window is the handed over gui
self.sub_controls()
def sub_controls(self):
Controller.Sub_Controller_1(window = self.window)
Controller.Sub_Controller_2(window = self.window)
# Controller.Sub_Controller_3(window = self.window.part_3) # before

Why does this simple Tkinter code create two top-level windows?

Consider this very simple code snippet:
import tkinter as tk
class GUI:
def __init__(self):
self.top_level_window = tk.Tk()
GUI()
GUI().top_level_window.mainloop()
It creates two top-level windows on my screen. Why?
I thought the first instance would be immediately garbage collected, so that I would only get one window.
I have also tried slightly modified version, which I was sure for would create two separate objects, and thus only one window:
a=GUI()
b=GUI()
b.top_level_window.mainloop()
but I was wrong. And I can't think of a reason.
Any help?
You are saying
GUI()
GUI().top_level_window.mainloop()
Which is two windows because you called the class twice like Frost Dream said so all you need to do is delete GUI()
import tkinter as tk
class GUI:
def __init__(self):
self.top_level_window = tk.Tk()
GUI().top_level_window.mainloop()
I think that with tkinter, the framework itself keeps hold of instances of GUI objects that you create. This defeats any garbage collection that you might assume is going to happen.
You would need to call .destroy() on any elements you want tkinter to forget.
I thought the first instance would be immediately garbage collected
The python object that is the instance of GUI is garbage-collected. However, tkinter creates objects inside of an embedded tcl interpreter, and the tcl interpreter doesn't know anything about python objects. So, while the object is removed from python, the widgets still exist inside the tcl interpreter.
Put another way, garbage collect of a python object doesn't guarantee that the underlying tcl object is deleted. If you want the first window to be destroyed, you must call destroy() on the instance.
Because you called the class two times.
GUI()
GUI().top_level_window.mainloop()
>>>
GUI().top_level_window.mainloop()

Tk(), Toplevel() and winfo_toplevel(). Difference between them and how and when to use effectively?

I am trying to understand how a widget is being created. And I found above three functions are getting used in creating a widget, yet I couldn't come up with the difference and the advantage of one over the other. Even though, I had taken a look on this answer that still leaves me with confusion (and it hadn't said anything about winfo_toplevel too).
Here is my code.
from tkinter import *
root = Tk()
root.title("Root widget")
root.mainloop()
window = Toplevel()
window.title("Window widget")
window.mainloop()
On running above code "Root" widget is getting created. On closing "Root", two widgets are created with one titled "Window widget" and other being unwanted. On closing unwanted widget, "Window widget" is also getting destroyed.
What is actually happening here and how to overcome?
Another sample:
class ldo(Frame):
def __init__(self, master = None):
Frame.__init__(self,master)
self.grid()
self.appOutline()
def appOutline(self):
top = self.winfo_toplevel()
self.menuBar = Menu(top)
top["menu"] = self.menuBar
self.subMenu1 = Menu(self.menuBar)
self.menuBar.add_cascade(label = "File", menu = self.subMenu1)
app = ldo()
app.master.title("Sample UI")
app.mainloop()
On the other hand, this code is using winfo_toplevel() where the widget looks perfectly fine. Here, my assumption is, Frame plays a role of creating widget and winfo_toplevel() is an enhancing tool to other tkinter items. But would like to know what it does actually.
However, below snippet is not working:
winf = winfo_Toplevel()
winf.title("Winfo Widget")
winf.mainloop()
And returning such error:
winf = winfo_Toplevel()
NameError: name 'winfo_Toplevel' is not defined
What is the exact difference between Tk(), Toplevel() and winfo_Toplevel(). What and when should one be used effectively. Looking for really a better understanding.
On running above code "Root" widget is getting created. On closing "Root", two widgets are created with one titled "Window widget" and
other being unwanted. On closing unwanted widget, "Window widget" is
also getting destroyed. What is actually happening here and how to
overcome?
When you create any widget with the absence of an actual Tk() instance, a Tk() instance automatically gets created, thus resulting in an unwanted Toplevel-like widget when the second part of the first code snippet runs. Additionally, when a widget gets created with the absence of master option, it is assumed that the instance is a child of one of the Tk instances, in above case, there's only one, and that's the one that got automatically created. When a parent gets destroyed all widgets that are under it also gets destroyed, so when you close the unwanted widget that is an instance of Tk, the Toplevel instance also gets destroyed as its parent is destroyed.
On the second part, winfo_toplevel refers to the automatically created Tk instance again and creates other children with that automatically created Tk as the parent, which should be technically fine but would be harder to maintain as a code, than the standard ways of creating the same GUI I'd presume.
winf = winfo_Toplevel()
winf.title("Winfo Widget")
winf.mainloop()
In the code piece above, unless imported or otherwise defined winfo_Toplevel has no meaning, first of all, it's not as same as winfo_toplevel as python is case sensitive. Secondly, even if python wasn't case sensitive, it would still throw an error as it is a method and it lacks the first positional argument, which is the object instance to the class of which the winfo_toplevel method is also defined for.
Essentially, you're trying to use a case-insensitive spelling of a method, as if it is a class name such as Toplevel or Tk, which winfo_toplevel has almost nothing to do with.
Examine the following code:
import tkinter as tk
root = tk.Tk()
root.title("This is the actual Tk instance, root")
toplevel = tk.Toplevel(root)
toplevel.title("This is a Toplevel, whose parent is root"),
r_lbl = tk.Label(text="""This label is a children to the default master,
as it lacks the first positional argument for an explicit parent
assignment.""")
r_lbl2 = tk.Label(r_lbl.winfo_toplevel(), text="""This label checks who the
toplevel parent for r_lbl is, and then selects that parent as a parent
to itself.""")
r_lbl3 = tk.Label(root, text="""This label will appear on root, as it's
explicitly passed as the first positional argument, which is the parent,
as root.""")
t_lbl = tk.Label(toplevel, text="""This label will appear on toplevel, as it's
explicitly passed as the first positional argument, which is the parent,
as toplevel.""")
t_lbl2 = tk.Label(t_lbl.winfo_toplevel(), text="""This label checks who the
toplevel parent for t_lbl is, and then selects that parent as a parent
to itself.""")
r_lbl.pack()
r_lbl2.pack()
r_lbl3.pack()
t_lbl.pack()
t_lbl2.pack()
root.mainloop()
In conclusion, Tk, while being a Toplevel widget, is also the tcl interpreter for the entire GUI that runs in a thread. There can be more than one present, which is discouraged as usually multiple instances of it is unjustified, but there also has to be at least one instance present in order to have a GUI.
Toplevel can be considered to be the only visual part of a Tk instance, and it can be used when there is a need for multiple window-like widgets.
Finally, winfo_toplevel is merely a method returns the reference for the Toplevel-like parent that a widget is in, be the parent an instance of a Toplevel or a Tk.

Why packing widgets into frames instead of directly packing them into the root window?

I'm learning Python by building an airplane ticket app. I'm currently working on learning TkInter for the GUI, and I want to know why I have to create a container (frame) instead of just putting everything in root = Tk() which seems to work.
Also, isn't ttk part of tkinter, therefore I shouldn't have to have the 2nd line (from tkinter import ttk)?
Here's my code:
from tkinter import *
from tkinter import ttk
root = Tk()
frame = Frame(root)
root.title("AirTix")
flight_title = Label(frame, text = "Flights").grid()
root.mainloop()
Thanks!
I want to know why I have to create a container (frame) instead of just putting everything in root = Tk() which seems to work.
You do not have to create a container. Whatever tutorial or book is telling you that you must is wrong. There's nothing wrong with creating everything in the root window.
That being said, for anything but the most trivial of apps, it helps tremendously to organize your widgets in logical groups, with each logical group being a frame (or, perhaps, a Canvas or PanedWindow). This gives you the flexibility to use the best geometry manager (pack, place, or grid) for each section.
For example, it makes sense to have a toolbar that is a frame with a bunch of buttons packed left-to-right. The same might be true for a bottom status bar. The main body might be a frame with widgets arranged in a grid.
grid can be a bit more complicated to use, and pack excels at placing widgets either in a single horizontal row (toolbar or status bar), or column (toolbar on top, status bar on bottom, main area in the middle.
Also, isn't ttk part of tkinter, therefore I shouldn't have to have the 2nd line (from tkinter import ttk)?
Even though ttk is part of tkinter, it does not get imported when you import everything from tkinter. Many python packages are this way, with sub-modules that must be explicitly imported.
Further, it's bad practice to use a wildcard import. Instead of doing this:
from tkinter import *
root = Tk()
... it's arguably better to do it like this:
import tkinter as tk
root = tk.Tk()
With the former, you end up polluting the global namespace with a bunch of things you may or may not use. With the latter, you import exactly one thing. Plus, it makes your code more self-documenting because it makes it crystal clear when you are expecting to use an object or class from the tk package.

tkinter and GUI programming methods

Hopefully this doesn't fall under "general discussion topic", since I'd like it to be more about resolving these issues in an efficient manner than a giant debate about which general approach to GUI programming is the absolute best.
So I've started some GUI programming with tkinter and long story short my code is getting pretty ugly pretty quickly. I'm trying to create a tile-based map editor for a video game. My main issues seem to be:
the inability of callbacks to return values.
the inability to transfer data between windows easily.
I assume that the reason I see these as issues is because I'm using functions a lot more than I'm using classes. For instance, my "load tileset" window is handled entirely functionally: Clicking the menu option in the main window calls the function that loads the new window. From within that window, I create an open file dialog when looking for the image, and modify the canvas displaying the image when I press the enter key (so that it draws the appropriate grid over the image). function function function.
What looks like really bad practice to me is the inclusion of extra arguments to compensate. For example, when I create a tileset, the instance of the TileSet class created should be sent back to the main window where the appropriate information can be displayed. I have a list of loaded tilesets as a global variable (even more bad practice: Everything dealing with my root window is in the global scope! yay!), and because callback functions don't return values, I pass that list as an argument to my "load tileset window" function, which then passes the argument to the create tileset function (called when you click the appropriate button in the window), where it's actually needed so that I can add my newly created tileset to the list. Passing arguments through a function 'hierarchy' like that seems like a horrible idea. It gets confusing, it's horrible for writing modular code, and just generally seems unnecessary.
My attempt at fixing the problem would be to write a class representing the whole GUI, and custom made window classes (that the GUI class can create and reference) that can actually store relevant data. That should take care of issues with transferring data between windows. Hopefully it would cut down on my gratuitous use of lambda functions in callbacks as well.
But I'm wondering: is this the best way? Or at least close? I'd rather not start rewriting and then end up with another system that's just sloppy and confusing in a different way. I know my methods are bad, but I don't really know what the best approach would be. I'm getting a lot of advice on how to do specific things, but none on how to structure the program as a whole. Any help would be greatly appreciated.
It sounds like you're trying to create a GUI that acts procedurally, which won't work. GUIs aren't procedural, their code doesn't run linearly where functions call callbacks which return values. What you're asking isn't unique to tkinter. This is the nature of event based GUI programming -- callbacks can't return anything because the caller is an event rather than a function.
Roughly speaking, you must use a global object of some sort to store your data. Typically this is called the "Model". It can be a global variable, or it might be a database, or it can be an object of some sort. In any case, it must exist "globally"; that is, it must be accessible to the whole GUI.
Often, this access is provided by a third component called a "Controller". It is the interface between the GUI (the "View") and the data (the "Model"). These three components make up what is called the model-view-controller pattern, or MVC.
The model, view and controller don't have to be three different objects. Often, the GUI and the controller are the same object. For small programs this works quite well -- the GUI components talk directly to your data model.
For example, you could have a class that represents a window which inherits from Tkinter.Toplevel. It can have an attribute that represents the data being edited. When the user selects "New" from a main window, it does something like self.tileset = TileSet(filename). That is, it sets the attribute named tileset of the GUI object named self to be an instance of the TileSet class specific to the given filename. Later functions that manipulate the data use self.tileset to access the object. For functions that live outside the main window object (for example, a "save all" function from the main window) you can either pass this object as an argument, or use the window object as the controller, asking it to do something to its tileset.
Here's a brief example:
import Tkinter as tk
import tkFileDialog
import datetime
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.windows = []
menubar = tk.Menu(self)
self.configure(menu=menubar)
fileMenu = tk.Menu(self)
fileMenu.add_command(label="New...", command=self.new_window)
fileMenu.add_command(label="Save All", command=self.save_all)
menubar.add_cascade(label="Window", menu=fileMenu)
label = tk.Label(self, text="Select 'New' from the window menu")
label.pack(padx=20, pady=40)
def save_all(self):
# ask each window object, which is acting both as
# the view and controller, to save it's data
for window in self.windows:
window.save()
def new_window(self):
filename = tkFileDialog.askopenfilename()
if filename is not None:
self.windows.append(TileWindow(self, filename))
class TileWindow(tk.Toplevel):
def __init__(self, master, filename):
tk.Toplevel.__init__(self, master)
self.title("%s - Tile Editor" % filename)
self.filename = filename
# create an instance of a TileSet; all other
# methods in this class can reference this
# tile set
self.tileset = TileSet(filename)
label = tk.Label(self, text="My filename is %s" % filename)
label.pack(padx=20, pady=40)
self.status = tk.Label(self, text="", anchor="w")
self.status.pack(side="bottom", fill="x")
def save(self):
# this method acts as a controller for the data,
# allowing other objects to request that the
# data be saved
now = datetime.datetime.now()
self.status.configure(text="saved %s" % str(now))
class TileSet(object):
def __init__(self, filename):
self.data = "..."
if __name__ == "__main__":
app = SampleApp()
app.mainloop()

Categories