Tkinter OOP - Class Instance Management With Multiple Top Level Windows - python

My initial approach is based on the following example:
Best way to structure a tkinter application?
I am trying to create a more complex application involving multiple sub applications each displayed in a separate top level window. All sub applications need to be able to exchange information among each other. Hence, I intend to use class variables in the main application in order to accomplish this requirement.
At the moment I am stuck at one point:
I do not know how to properly implement the destruction of the sub application class instance once it has been created.
The "app_one" window can be opened exactly one time and then it can be properly closed. Of course, if the window is opened a second time an error occurs since only the "Toplevel" window instance has been destroyed, but not the sub application class instance itself.
How do I properly implement the destruction of the sub application class instance upon closing the "Toplevel" window ?
Besides that, I am also very grateful for any other advise on how to improve the structure of my template below in terms of following "Python" as well as "tkinter" best practice paradigms:
import tkinter
import tkinter.filedialog
class main_app(tkinter.Tk):
root = None
var_one = None
sub_app_one_instance = None
sub_app_two_instance = None
sub_app_thr_instance = None
def __init__(self):
super().__init__()
main_app.root = self # <<< in order to be able to refer to root window in other classes
main_app.var_one = 99 # <<< class variables in order to exchange data between main app and sub apps
main_app.sub_app_one_instance = None
main_app.sub_app_two_instance = None
main_app.sub_app_thr_instance = None
self.create_tkinter_interface_main_app()
def create_tkinter_button(self, button_destination, button_text, button_width):
tkinter_button = tkinter.Button(button_destination, text=button_text, width=button_width)
return tkinter_button
def create_tkinter_label(self, label_destination, label_text):
tkinter_label = tkinter.Label(label_destination, text=label_text)
return tkinter_label
def create_tkinter_interface_main_app(self):
frame_main = tkinter.Frame(self)
frame_sub_one = tkinter.Frame(frame_main, bg="red")
frame_sub_two = tkinter.Frame(frame_main, bg="green")
frame_sub_thr = tkinter.Frame(frame_main, bg="blue")
label_app = self.create_tkinter_label(frame_main, "application")
label_one = self.create_tkinter_label(frame_sub_one, "menu one")
label_two = self.create_tkinter_label(frame_sub_two, "menu two")
label_thr = self.create_tkinter_label(frame_sub_thr, "menu thr")
button_one = self.create_tkinter_button(frame_sub_one, "app_one", 20)
button_one.configure(command=self.sub_app_one_open)
button_two = self.create_tkinter_button(frame_sub_one, "app_two", 20)
label_app.pack(side=tkinter.TOP, fill=tkinter.X, expand=tkinter.NO)
frame_sub_one.pack(side=tkinter.LEFT, fill=tkinter.BOTH, expand=tkinter.YES)
frame_sub_two.pack(side=tkinter.LEFT, fill=tkinter.BOTH, expand=tkinter.YES)
frame_sub_thr.pack(side=tkinter.LEFT, fill=tkinter.BOTH, expand=tkinter.YES)
label_one.pack(side=tkinter.TOP, fill=tkinter.NONE, expand=tkinter.NO, padx=10, pady=10)
button_one.pack(side=tkinter.TOP, fill=tkinter.NONE, expand=tkinter.NO, padx=10, pady=10)
button_two.pack(side=tkinter.TOP, fill=tkinter.NONE, expand=tkinter.NO, padx=10, pady=10)
label_two.pack(side=tkinter.TOP, fill=tkinter.NONE, expand=tkinter.NO, padx=10, pady=10)
label_thr.pack(side=tkinter.TOP, fill=tkinter.NONE, expand=tkinter.NO, padx=10, pady=10)
frame_main.pack(fill=tkinter.BOTH, expand=tkinter.TRUE)
def sub_app_one_open(self):
if not main_app.sub_app_one_instance == None:
main_app.sub_app_one_instance.sub_app_one_move_to_front()
return None
else:
main_app.sub_app_one_instance = sub_app_one()
class sub_app_one(main_app): # <<< inherit from main application in order to be able to access class variables
def __init__(self):
self.sub_app_one_toplevel_instance = tkinter.Toplevel(main_app.root)
self.sub_app_one_toplevel_instance.protocol("WM_DELETE_WINDOW", self.sub_app_one_close_toplevel)
print(main_app.var_one) # <<< access information from main app
def sub_app_one_move_to_front(self):
self.sub_app_one_toplevel_instance.attributes("-topmost", 0x01) # <<< set window state
self.sub_app_one_toplevel_instance.attributes("-topmost", 0x00) # <<< reset window state
def sub_app_one_close_toplevel(self):
self.sub_app_one_toplevel_instance.destroy()
self.sub_app_one_toplevel_instance = None
if __name__ == "__main__":
main_app_instance = main_app()
main_app_instance.mainloop()

# <<< inherit from main application in order to be able to access class variables
This is not how inheritance works. If sub_app_one needs access to variables in the main application, it should get them via the instance of main_app. You should definitely not be inheriting from main_app.
When you use inheritance, you are saying that the subclass is a main class. Meaning, you end up with two main classes. One is the original, and one is exactly like the original, with some modifications. That means you end up with two instances of tk.Tk which is not how tkinter is designed to be used. You can do it, but the behavior is not intuitive, and is rarely the right thing to do.
What you should do instead is pass in the instance of main_app into your sub_app_one when creating the instance. It would look something like this:
class main_app(tkinter.Tk):
...
def sub_app_one_open(self):
...
main_app.sub_app_one_instance = sub_app_one(main_app=self)
...
class sub_app_one():
def __init__(self, main_app):
self.main_app = main_app
...
As for the handling of deleting the window, the simplest solution is to make your child classes subclasses of tk.Toplevel. With that, you can use tkinter's built-in event handling to know when the widget has been destroyed.
Here's a working example:
import tkinter as tk
class Main(tk.Tk):
def __init__(self):
super().__init__()
self.sub_app_one = None
b1 = tk.Button(self, text="Open Sub App One", command=self.sub_app_one_open)
b1.pack(padx=20, pady=20)
def sub_app_one_open(self):
if self.sub_app_one is None:
self.sub_app_one = Sub_app_one(self)
else:
self.sub_app_one.deiconify()
self.sub_app_one.bind("<Destroy>", self._child_destroyed)
def _child_destroyed(self, event):
if event.widget == self.sub_app_one:
print("sub_app_one has been destroyed")
self.sub_app_one = None
class Sub_app_one(tk.Toplevel):
def __init__(self, main_app):
self.main_app = main_app
super().__init__(main_app)
label = tk.Label(self, text="This is sub app one")
label.pack(padx=50, pady=50)
main = Main()
main.mainloop()

Here is some advice that I can give after having worked on a application like this for the past few weeks. I will split my answer into two parts:
Some examples from my own code
Advice that will be helpful if this is a long-term project
Examples from my code
Managing your GUI
I would advise you to have one main class that manages your application. This class puts all the frames/pages of your applicaton on top of each other and raises the one you need to the top. I have a separate file for every frame, since the code for each of them can get very long. This is the 'Main.py' file:
import tkinter as tk
#importing frames that we will create soon!
from Frame1 import Frame1
frome Frame2 import Frame2
frome Frame3 import Frame3
class Main(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
#Sets the screen size of the GUI
self.width= self.winfo_screenwidth()
self.height= self.winfo_screenheight()
self.geometry(str(self.width) + 'x' + str(self.height) + '+0+0')
self.title('project_title')
#The container is a frame that contains the projects's frames
container = tk.Frame(self,
height=self.height,
width=self.width)
#Pack the container to the root
container.pack(side="top", fill="both", expand=True)
#Fixes pack location of the container using grid
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
#Create empty dictionary of frames
self.frames = {}
#Use container as master frame for imported frames
for Frame in (Frame1, Frame2, Frame3):
self.frames[Frame] = Frame(container)
self.frames[Frame].grid(row=0, column=0, sticky="nsew")
#Define buttons to switch GUI pages
self.frame1_btn = tk.Button(self,
text='Frame1',
width=15,
font=('Arial', 10),
command=self.show_frame1)
self.frame1_btn.place(relx=0.75,
rely=0.025,
anchor='center')
self.frame2_btn = tk.Button(self,
text='Frame2',
width=15,
font=('Arial', 10),
command=self.show_frame2)
self.frame2_btn.place(relx=0.85,
rely=0.025,
anchor='center')
self.frame3_btn = tk.Button(self,
text='Frame3',
width=15,
font=('Arial', 10),
command=self.show_frame3)
self.frame3_btn.place(relx=0.95,
rely=0.025,
anchor='center')
#The start page is raised to the top at the beginning.
self.show_frame1()
#Raises the selected frame to the top.
def show_frame1(self):
frame = self.frames[Frame1]
frame.tkraise()
def show_frame2(self):
frame = self.frames[Frame2]
frame.tkraise()
def show_frame3(self):
frame = self.frames[Frame3]
frame.tkraise()
def main():
app = Main()
app.mainloop()
if __name__ == '__main__':
main()
You have a 'main' file that manages the GUI. Now you have to create the frames that will later be the pages of your application. Best practice: keep them each in a separate file and import them in Main.py! This is an example Frame.py file:
import tkinter as tk
class Frame1(tk.Frame):
def __init__(self, ParentFrame):
tk.Frame.__init__(self, ParentFrame)
#The label acts as a title for each main frame.
self.label = tk.Label(self,
text='App_Name: Frame1',
font=('Arial', 20))
self.label.pack(padx=10, pady=10, anchor='w')
Repeat this for as many frames as you need. You should then have the following files in your directory:
Main.py, Frame1.py, Frame2.py, Frame3.py, etc.
Try running the Main.py file! The GUI still looks boring right now, but you will fill each frame with your own code.
Destroying frames
Let's say you have a start page that you only want to use once. It should be destroyed after its use. Let our 'Frame1.py' file be that start page. The code for it should look like this:
import tkinter as tk
class Frame1(tk.Frame):
def __init__(self, ParentFrame):
tk.Frame.__init__(self, ParentFrame)
#The label acts as a title for each main frame.
self.label = tk.Label(self,
text='App_Name: Frame1',
font=('Arial', 20))
self.label.pack(padx=10, pady=10, anchor='w')
self.selfdestruct_btn = tk.Button(self,
text='selfdestruct',
command=self.selfdestruct)
self.selfdestruct_btn.pack(pady=20)
def selfdestruct(self):
self.destroy()
If you click the 'selfdestruct' button on the start page, this frame will be properly destroyed. However, you can still see the 'Frame 1' button that you defined in 'Main.py', even if clicking it produces no result. Go back to the 'Main.py' file and delete the part where you defined this button. You don't need it anyway, since the start page will be raised to the top by the line 'self.show_frame1()' when you start your application.
I hope that helps you with your question. Here are some other tips I would like to give you:
Further advice
File scructure
A good file structure helps you to have an overview of your code and will make fixing errors much easier. You might want to have a directory that is similar to this:
Main.py file
Folder for Frame1
Folder for Frame2
Folder for Frame3
possibly a database
any other major components of your application
Why use folders for each frame of your application? When you write the code for each of these frames, you will probably start filling each frames with subframes. Each subframe has its own function and may need lots of code. I would therefore define a class for every major subframe you use that is described by lots of code. If you have multiple of those subframe classes, you might even want to have a 'subframes' folder within the frame folder. You can also put those files in the frame folder that are used only by this frame. Lets say you have to perform some complex calculation and define a class that does just that. You can keep this class in a separate file in the frame folder.
Fast GUI loadup
Try not making your computer load a bunch of unncessary stuff when it starts your program. The best case scenario would be that it only has to pack all of your pages/frames and their buttons. Don't make it load huge picture files or anything of that sort. Make it do these thing by the click of a button instead.
Pretty won't win any prizes
...at least that's probably not the case here. I wouldn't spend too much time formatting each button perfectly before moving on, since you are likely to move things around all the time anyway. Once you have all elements that you need packed to a frame, you can think about formatting.
That's all I'm going to share for now. Feel free to ask if you have any follow up questions!
Best,
Ansel

Related

In tkinter for Python, can someone identify why <<ListBoxSelect>> is not firing?

I am trying to develop a small database application using tkinter in python. I have a small database which has inventory numbers for various assets. I have successfully constructed the listbox using tkinter and managed to populate it using sqlite3's library.
I've been trying to get the << ListBoxSelect >> event bound to which item is selected in the listbox and to output the asset's information. Several hours later, all I'm trying to do now is get the bind event to fire at all and print out a simple string until I can figure it out.
Before I post my code, I used This code snippet to see if I could get ANY binding event to work. This code snippet does in fact work however trying to integrate the way its written into my own code has not been successful. The event binder simply does not fire in my code at all. Can anyone please identify why this is or what I'm doing incorrectly?
Please see relevant code below (please note the indenting is not correct when trying to paste it into this forum):
class MainWindow(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
# Create Widgets
self.invListbox = tk.Listbox(self, height=20, width=10, selectmode=tk.SINGLE)
self.assetDescLabel1 = tk.Label(self, text="Asset Description")
self.assetDescEntry1 = tk.Entry(self)
self.exitButton = tk.Button(self, text="Exit", command=exit)
# Configure Widgets
# Updates assetDescEntry based on invListbox selection
self.invListbox.bind('<<ListboxSelect>>', self.invOnSelect())
# Place Widgets
self.invListbox.grid(row=1, column=1)
self.assetDescLabel1.grid(row=1, column=2)
self.assetDescEntry1.grid(row=2, column=2)
self.exitButton.grid(row=2, column=1)
# Populate invListbox
self.InvList = main.sqlInventoryNumberList()
for row in self.InvList:
item = row[0]
self.invListbox.insert(1, item)
def invOnSelect(self):
selection = "This is just a regular old string"
print(selection)
I can't get the code above to run as too much is missing. You must bind a function in the bind statement not the result of a function. So .bind( ... , func ) NOT .bind(..., func() ). Your function must take an event object too. Below is a minimal working example.
import tkinter as tk
def invOnSelect(event):
selection = "This is just a regular old string"
print( event, selection )
root = tk.Tk()
invListbox = tk.Listbox( root, height=20, width=10, selectmode=tk.SINGLE )
invListbox.grid()
invListbox.bind( '<<ListboxSelect>>', invOnSelect ) # invOnSelect has no brackets here.
InvList = [ '1', '2', '3' ]
for item in InvList:
invListbox.insert(tk.END, item)
root.mainloop()
The ideas should integrate into your application.

I can't get an image to load on the canvas in Tkinter python

I am having a big issue. The Canvas loads perfectly but the image does not display.
I started Python 1 week ago and I have no clue why does is not working. Can anyone please show me the way to solve the issue of the image not loading on the canvas?
from Tkinter import *
from PIL import ImageTk
from PIL import Image
class Fake_Virus:
def __init__(self, master):
self.master = master
master.title("Totally not a virus!")
b = Button(master, text="Help", command=self.prank)
b.pack(padx=10, pady=10, side=LEFT)
quit = Button(master, text="Close", command=self.close_window)
quit.pack(padx=10, pady=10, side=RIGHT)
photo = PhotoImage("eh.gif")
label = Label(image=photo)
label.image = photo # keep a reference!
label.pack()
f = Frame(master, height=150, width=150)
f.pack_propagate(0) # don't shrink
f.pack()
def prank(self):
print "work"
return
def close_window(self):
root.destroy()
return
root = Tk()
my_gui = Fake_Virus(root)
root.mainloop()
You should use the file option to initialize the photo image object.
This means you need to change photo = PhotoImage("eh.gif") to photo = PhotoImage(file="eh.gif")
Now your code will work. But a working code is not necessarily a good code. There are other issues with your code. Let me go through them quickly:
It is better to code import Tkinter as Tk than from Tkinter import *
Why that hyphen in your class name? Follow PEP8 so that, in the futur, people will find it easy to review and understand your code.
Good that you have written self.master = master (read complete code to know why) but then you have never used it. This means you made a good decision and you render it useless.
You set the title of the window within the initializer. It is better if you do that in a separate function so that whenever you want to add additional settings to your GUI (such as the size, font or whatever) you will only add code to that function instead of vomiting lot of trash inside the initializer which rather needs to be clean.
None of the widgets you created is 'selfed' (you may read Why explicit self has to stay)
It is better you create the widgets in a separate function otherwise your __init__() will be dirty.
Why do you use return in prank() and close_window()? By default, Python functions that do not return something return None anyway so it is useless to code that.
Why did you pack one button to left and the other one to right and then no pack siding for the label? Read about the pack() geometry manager.
Why you did not attach the label to a parent widget as you did for the 2 other buttons? All Tkinter widgets need to be clung into a parent widget. The grand parent of those widgets is an instance of Tkinter.Tk()
Why did you create that frame and then you never used it? You are not doing anything with it, so ..?
Given these remarks, I want to provide you an improved -but not perfect- version of your program. You can then follow this 'philosophy' to add or modifying existing widgets:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import Tkinter as Tk
from PIL import ImageTk
class FakeVirus:
def __init__(self, master):
self.master = master
self.configure_gui()
self.create_widgets()
def configure_gui(self):
self.master.title('Totally not a virus!')
def create_widgets(self):
self.create_buttons()
self.create_label_for_image()
def create_buttons(self):
self.help = Tk.Button(self.master, text='Help', command=self.prank)
self.help.pack(side=Tk.LEFT)
self.quit = Tk.Button(self.master, text='Close', command=self.close_window)
self.quit.pack(side=Tk.LEFT)
def create_label_for_image(self):
self.image_label = Tk.Label(self.master)
self.image_label.pack(side=Tk.LEFT)
self.load_image_to_label()
def load_image_to_label(self):
self.photo = ImageTk.PhotoImage(file='eh.gif')
self.image_label.image = self.photo
self.image_label.config(image=self.photo)
def prank(self):
print "work"
def close_window(self):
root.destroy()
if __name__ == '__main__':
root = Tk.Tk()
my_gui = FakeVirus(root)
root.mainloop()
The output of the above program is:

Tkinter Button unable to find callback command

I'm working on a program that reads and parses a config file for an embedded hardware system I'm working on. I'm trying to use tkinter and python to make a simple GUI for reading and writing this file. I have the file IO and parsing mostly working, but I'm having trouble getting all the parts integrated with the GUI. I want to read the information off of the device and then populate the entry fields with the relevant info. The problem that I'm having is I keep getting the error
"in initUI
opnBut = Button(butFrm, text="Open", command=openCfg)
NameError: name 'openCfg' is not defined"
I can get it to find the function if I put it outside the Application class but then I can't figure out how to reference the fields inside the window I've made to update them.
Any help would be greatly appreciated. My code segment is included below.
class Application(Frame):
def openCfg():
name = getNameFromFile()
nameEntry.insert(0, name)
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.initUI()
def initUI(self):
self.parent.title("Config Reader")
self.pack(fill=BOTH, expand=True)
nameFrm = Frame(self)
nameFrm.pack(fill=X)
nameLbl = Label(nameFrm, text="Device Name",width=20)
nameLbl.pack(side=LEFT,padx=5, pady=5)
nameEntry = Entry(nameFrm)
nameEntry.pack(fill=X, padx=5, pady=5)
butFrm = Frame(self)
butFrm.pack(fill=X)
opnBut = Button(butFrm, text="Open Cfg", command=openCfg)
opnBut.pack(fill=X, padx=5,pady=5)
root = Tk()
root.geometry("600x600")
app = Application(root)
app.mainloop()
items which are defined in the class namespace are bound to the class. In this case, you have a couple options...
One option is that you could make it a staticmethod:
class Application(Frame):
#staticmethod
def openCfg():
name = getNameFromFile()
nameEntry.insert(0, name)
And then bind the button as:
opnBut = Button(butFrm, text="Open Cfg", command=self.openCfg)
However, staticmethods are rarely necessary. In this case, I might just move the definition of openCfg out of the class and keep everything else as it is:
def openCfg():
name = getNameFromFile()
nameEntry.insert(0, name)
class Application(Frame):
...
On a second look, this will probably still bring up a NameError for nameEntry. You'll probably want a first-class method:
class Application(Frame):
def openCfg(self):
name = getNameFromFile()
self.nameEntry.insert(0, name)
And of course, when you create nameEntry, you'll need to make it a member of the Application class.
...
self.nameEntry = Entry(nameFrm)
self.nameEntry.pack(fill=X, padx=5, pady=5)
...

Python: Tkinter: Why is it root.mainloop() and not app.mainloop()

I'm a new member to Stack Overflow.
I found this thread, but was not allowed to comment or ask questions on it, so I thought I'd just reference it here: How can I make a in interactive list in Python's Tkinter complete with buttons that can edit those listings?
from tkinter import *
import os
import easygui as eg
class App:
def __init__(self, master):
frame = Frame(master)
frame.pack()
# character box
Label(frame, text = "Characters Editor").grid(row = 0, column = 0, rowspan = 1, columnspan = 2)
charbox = Listbox(frame)
for chars in []:
charbox.insert(END, chars)
charbox.grid(row = 1, column = 0, rowspan = 5)
charadd = Button(frame, text = " Add ", command = self.addchar).grid(row = 1, column = 1)
charremove = Button(frame, text = "Remove", command = self.removechar).grid(row = 2, column = 1)
charedit = Button(frame, text = " Edit ", command = self.editchar).grid(row = 3, column = 1)
def addchar(self):
print("not implemented yet")
def removechar(self):
print("not implemented yet")
def editchar(self):
print("not implemented yet")
root = Tk()
root.wm_title("IA Development Kit")
app = App(root)
root.mainloop()
Could somebody explain to me why the very last line is root.mainloop()?
Being a novice with Python, and coming from a background that's procedural-oriented with no object-orient experience, I would have thought it would have been app.mainloop().
In fact app = App(root) , app is never used again in the rest of the code! I'm having trouble understanding why root.mainloop() still works.
I'm not sure if you'll find this answer satisfying, but you call root.mainloop() because root is the object that has the mainloop method. In the code you've given, App has no mainloop function.
In simpler terms, this is just how tkinter works -- you always end your script by calling the mainloop method of the root window. When that method returns, your program will exit.
Let's start at the beginning. The simplest, non-OO Tkinter program is going to look like the following example. Note that this is a python 2.x example, and I do not use a global import since I think global imports are bad.
import Tkinter as tk
root = tk.Tk()
<your widgets go here>
root.mainloop()
Many people find that a pure procedural style is not an effective way to write code, so they might choose to write this in an object-oriented style. It's natural to think of "the app" as a singleton object. There are many ways to do this -- the one in your question is, unfortunately, not one of the clearer ways to do it.
A slightly better way, IMO, would be to structure the code like this:
class App(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
<your widgets go here>
app = App()
app.mainloop()
In this case, mainloop is still being called, though now it's a method of App since App inherits from Tk. It is conceptually the same as root.mainloop() since in this case, app is the root window even though it goes by a different name.
So, in both cases, mainloop is a method that belongs to the root window. And in both cases, it must be called for the GUI to function properly.
There is a third variation which is what the code you picked is using. And with this variation, there are several ways to implement it. The variation is your question uses a class to define the GUI, but does not inherit from Tk. This is perfectly fine, but you still must call mainloop at some point. Since you don't create (or inherit) a mainloop function in your class, you must call the one associated with the root window. The variations I speak of are how and where the instance of App is added to the root window, and how mainloop is ultimately called.
Personally I prefer that App inherits from Frame, and that you pack the app outside the definition of the app. The template I use looks like this:
class App(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
<your widgets go here>
if __name__ == "__main__":
root = tk.Tk()
app = App(root)
app.pack(fill="both", expand=True)
root.mainloop()
In this final example, app and root are two completely different objects. app represents a frame that exists inside the root window. Frames are commonly used this way, as a container for groups of other widgets.
So, in all cases, mainloop must be called. where it is called, and how, depends a bit on your coding style. Some people prefer to inherit from the root window, some don't. In either case, you must call the mainloop function of the root window.
I tested both like you see:
One is written with "app." + ".pack()" and one calls "mainframe." + ".grid()"
#-*- coding: utf-8 -*-
#THIS IS THE "MAINFRAME." - PART
from Tkinter import *
import ttk
def show():
p = password.get() #get password from entry
print(p)
root = Tk()
root.title("Ingos first program")
mainframe = ttk.Frame(root, padding="30 30 60 12")
mainframe.grid(column=0, row=0, sticky=(N, W, E, S))
mainframe.columnconfigure(0, weight=1)
mainframe.rowconfigure(0, weight=1)
password = StringVar() #Password variable
passEntry = Entry(mainframe, textvariable=password, show='*').grid(column=3, row=3, sticky=S)
submit = Button(mainframe, text='Show Console',command=show).grid(column=3, row=4, sticky=S)
root.mainloop()
def show():
p = password.get() #get password from entry
print(p)
#THIS IS THE "APP."-PART. BOTH WORKS FINE.
app = Tk()
app.title("Ingos first program")
password = StringVar() #Password variable
passEntry = Entry(app, textvariable=password, show='#').pack()
submit = Button(app, text='Show Console',command=show).pack()
app.mainloop()
This instance works fine with python 2.7. In that test even app. can handle "mainloop()"
That script opens 2 windows, one after another (if you close the first one) and the first programm is formatted, didn't tryed to write the colum=3... stuff in the pack() clamps.
I still started the Tkinter so don't fight me, just trying.. Hope I could help to answer your question.
All the best, Ingo
The App object is just your app code, and the reason you call App(root) is to make an instance with your class, which then has access to your root window.
It receives this reference in the __init__ method:
def __init__(self, master):
# master refers to the root window now
...
You can see the entire definition of the App object (given by the block beginning with class App:), and it doesn't even have a mainloop method, so to start the main Tkinter loop, you have to call it on the root window.
In the example in the Python2 documentation, they do call it as you suspected should be done, but note that their example class subclasses the Tk object Frame. In your example code, App is an old-style class that doesn't inherit anything.

Best way to structure a tkinter application?

The following is the overall structure of my typical python tkinter program.
def funA():
def funA1():
def funA12():
# stuff
def funA2():
# stuff
def funB():
def funB1():
# stuff
def funB2():
# stuff
def funC():
def funC1():
# stuff
def funC2():
# stuff
root = tk.Tk()
button1 = tk.Button(root, command=funA)
button1.pack()
button2 = tk.Button(root, command=funB)
button2.pack()
button3 = tk.Button(root, command=funC)
button3.pack()
funA funB and funC will bring up another Toplevel windows with widgets when user click on button 1, 2, 3.
I am wondering if this is the right way to write a python tkinter program? Sure, it will work even if I write this way, but is it the best way? It sounds stupid but when I see the code other people written, their code is not messed up with bunch of functions and mostly they have classes.
Is there any specific structure that we should follow as good practice? How should I plan before start writing a python program?
I know there is no such thing as best practice in programming and I am not asking for it either. I just want some advice and explanations to keep me on the right direction as I am learning Python by myself.
I advocate an object oriented approach. This is the template that I start out with:
# Use Tkinter for python 2, tkinter for python 3
import tkinter as tk
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
<create the rest of your GUI here>
if __name__ == "__main__":
root = tk.Tk()
MainApplication(root).pack(side="top", fill="both", expand=True)
root.mainloop()
The important things to notice are:
I don't use a wildcard import. I import the package as "tk", which requires that I prefix all commands with tk.. This prevents global namespace pollution, plus it makes the code completely obvious when you are using Tkinter classes, ttk classes, or some of your own.
The main application is a class. This gives you a private namespace for all of your callbacks and private functions, and just generally makes it easier to organize your code. In a procedural style you have to code top-down, defining functions before using them, etc. With this method you don't since you don't actually create the main window until the very last step. I prefer inheriting from tk.Frame just because I typically start by creating a frame, but it is by no means necessary.
If your app has additional toplevel windows, I recommend making each of those a separate class, inheriting from tk.Toplevel. This gives you all of the same advantages mentioned above -- the windows are atomic, they have their own namespace, and the code is well organized. Plus, it makes it easy to put each into its own module once the code starts to get large.
Finally, you might want to consider using classes for every major portion of your interface. For example, if you're creating an app with a toolbar, a navigation pane, a statusbar, and a main area, you could make each one of those classes. This makes your main code quite small and easy to understand:
class Navbar(tk.Frame): ...
class Toolbar(tk.Frame): ...
class Statusbar(tk.Frame): ...
class Main(tk.Frame): ...
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.statusbar = Statusbar(self, ...)
self.toolbar = Toolbar(self, ...)
self.navbar = Navbar(self, ...)
self.main = Main(self, ...)
self.statusbar.pack(side="bottom", fill="x")
self.toolbar.pack(side="top", fill="x")
self.navbar.pack(side="left", fill="y")
self.main.pack(side="right", fill="both", expand=True)
Since all of those instances share a common parent, the parent effectively becomes the "controller" part of a model-view-controller architecture. So, for example, the main window could place something on the statusbar by calling self.parent.statusbar.set("Hello, world"). This allows you to define a simple interface between the components, helping to keep coupling to a minimun.
Putting each of your top-level windows into it's own separate class gives you code re-use and better code organization. Any buttons and relevant methods that are present in the window should be defined inside this class. Here's an example (taken from here):
import tkinter as tk
class Demo1:
def __init__(self, master):
self.master = master
self.frame = tk.Frame(self.master)
self.button1 = tk.Button(self.frame, text = 'New Window', width = 25, command = self.new_window)
self.button1.pack()
self.frame.pack()
def new_window(self):
self.newWindow = tk.Toplevel(self.master)
self.app = Demo2(self.newWindow)
class Demo2:
def __init__(self, master):
self.master = master
self.frame = tk.Frame(self.master)
self.quitButton = tk.Button(self.frame, text = 'Quit', width = 25, command = self.close_windows)
self.quitButton.pack()
self.frame.pack()
def close_windows(self):
self.master.destroy()
def main():
root = tk.Tk()
app = Demo1(root)
root.mainloop()
if __name__ == '__main__':
main()
Also see:
simple hello world from tkinter docs
Tkinter example code for multiple windows, why won't buttons load correctly?
Tkinter: How to Show / Hide a Window
Hope that helps.
This isn't a bad structure; it will work just fine. However, you do have to have functions in a function to do commands when someone clicks on a button or something
So what you could do is write classes for these then have methods in the class that handle commands for the button clicks and such.
Here's an example:
import tkinter as tk
class Window1:
def __init__(self, master):
pass
# Create labels, entries,buttons
def button_click(self):
pass
# If button is clicked, run this method and open window 2
class Window2:
def __init__(self, master):
#create buttons,entries,etc
def button_method(self):
#run this when button click to close window
self.master.destroy()
def main(): #run mianloop
root = tk.Tk()
app = Window1(root)
root.mainloop()
if __name__ == '__main__':
main()
Usually tk programs with multiple windows are multiple big classes and in the __init__ all the entries, labels etc are created and then each method is to handle button click events
There isn't really a right way to do it, whatever works for you and gets the job done as long as its readable and you can easily explain it because if you cant easily explain your program, there probably is a better way to do it.
Take a look at Thinking in Tkinter.
OOP should be the approach and frame should be a class variable instead of instance variable.
from Tkinter import *
class App:
def __init__(self, master):
frame = Frame(master)
frame.pack()
self.button = Button(frame,
text="QUIT", fg="red",
command=frame.quit)
self.button.pack(side=LEFT)
self.slogan = Button(frame,
text="Hello",
command=self.write_slogan)
self.slogan.pack(side=LEFT)
def write_slogan(self):
print "Tkinter is easy to use!"
root = Tk()
app = App(root)
root.mainloop()
Reference: http://www.python-course.eu/tkinter_buttons.php
My preferred way of doing it is like Bryan Oakley's answer.
Here's an example, made by Sentdex on Youtube, go check his "GUIs with Tkinter" playlist.
I think it's really relevant to put it here because it's a great example for the OP, and so it also answers this answer that was upped by 35 people and wasn't answered;
#Bryan Oakley do you know any good sample codes on internet that i can
study their structure? – Chris Aung Jul 5 '13 at 8:35
import tkinter as tk
LARGE_FONT= ("Verdana", 12)
class SeaofBTCapp(tk.Tk):
"""
tkinter example app with OOP
"""
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.pack(side="top", fill="both", expand = True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for frame_class in (StartPage,PageOne, PageTwo):
frame = frame_class(container, self)
self.frames[frame_class] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(StartPage)
def show_frame(self, cont):
"""
Put specific frame on top
"""
frame = self.frames[cont]
frame.tkraise()
class StartPage(tk.Frame):
"""
Starting frame for app
"""
def __init__(self, parent, controller):
tk.Frame.__init__(self,parent,bg='grey')
label = tk.Label(self, text="Start Page", font=LARGE_FONT)
label.pack(pady=10,padx=10)
button_page1 = tk.Button(self, text = 'Visit Page 1', command= lambda: controller.show_frame(PageOne))
button_page1.pack()
button_page2 = tk.Button(self, text = 'Visit Page 2', command= lambda: controller.show_frame(PageTwo))
button_page2.pack()
class PageOne(tk.Frame):
"""
First page of program
"""
def __init__(self,parent,controller):
tk.Frame.__init__(self,parent,bg='light blue')
label = tk.Label(self, text="Page one", font=LARGE_FONT)
label.pack(pady=10,padx=10)
button_home = tk.Button(self, text = 'Back to Home', command= lambda: controller.show_frame(StartPage))
button_home.pack()
button_home = tk.Button(self, text = 'Go to page2', command= lambda: controller.show_frame(PageTwo))
button_home.pack()
class PageTwo(tk.Frame):
"""
First page of program
"""
def __init__(self,parent,controller):
tk.Frame.__init__(self,parent,bg='light green')
label = tk.Label(self, text="Page two", font=LARGE_FONT)
label.pack(pady=10,padx=10)
button_home = tk.Button(self, text = 'Back to Home', command= lambda: controller.show_frame(StartPage))
button_home.pack()
button_home = tk.Button(self, text = 'Go to page1', command= lambda: controller.show_frame(PageOne))
button_home.pack()
app = SeaofBTCapp()
app.mainloop()
Find the code here also : [https://pythonprogramming.net/change-show-new-frame-tkinter/]
Organizing your application using class make it easy to you and others who work with you to debug problems and improve the app easily.
You can easily organize your application like this:
class hello(Tk):
def __init__(self):
super(hello, self).__init__()
self.btn = Button(text = "Click me", command=close)
self.btn.pack()
def close():
self.destroy()
app = hello()
app.mainloop()
Probably the best way to learn how to structure your program is by reading other people's code, especially if it's a large program to which many people have contributed. After looking at the code of many projects, you should get an idea of what the consensus style should be.
Python, as a language, is special in that there are some strong guidelines as to how you should format your code. The first is the so-called "Zen of Python":
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than right now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
On a more practical level, there is PEP8, the style guide for Python.
With those in mind, I would say that your code style doesn't really fit, particularly the nested functions. Find a way to flatten those out, either by using classes or moving them into separate modules. This will make the structure of your program much easier to understand.
I personally do not use the objected oriented approach, mostly because it a) only get in the way; b) you will never reuse that as a module.
but something that is not discussed here, is that you must use threading or multiprocessing. Always. otherwise your application will be awful.
just do a simple test: start a window, and then fetch some URL or anything else. changes are your UI will not be updated while the network request is happening. Meaning, your application window will be broken. depend on the OS you are on, but most times, it will not redraw, anything you drag over the window will be plastered on it, until the process is back to the TK mainloop.

Categories