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
I am trying to call a new window by pushing the button of an existing window. The original window should close when the new window is being created. When I push the button the new window will show up as expected but additionally a blank window will appear. Using tk.Tk() or tk.Toplevel() will lead to the same result.
Later in the program I want to destroy the created window again. When using tk.Tk() closing the blank window by mouse will create an error "application has been destroyed" when the destroy() method gets applicated to the new window.
import tkinter as tk
from tkinter import messagebox
def main():
root = tk.Tk()
root.title("Hauptmenü")
Menue = MainMenue(root)
Menue.pack()
root.mainloop()
class MainMenue(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
self.button_rennen = tk.Button(self, text="New Window", width=20, command=self.call_bet)
self.button_rennen.pack()
def call_bet(self):
self.destroy()
root2 = tk.Tk()
Bet = BetFrame(root2)
Bet.pack()
class BetFrame(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
self.button = tk.Button(text="Wette platzieren",
command=self.question)
self.button.pack()
def question(self):
dialog = tk.messagebox.askokcancel(message="Destroy Window?")
if dialog is True:
self.destroy()
main()
I am creating a new class for every new window since the original program should return some variables.
I know that there are already many questions to this topic but for me none of these seemed quite to fit and helped to find the solution for my problem. I am grateful for any help!
Look at this:
import tkinter as tk
from tkinter import messagebox
class MainMenue(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
self.button_rennen = tk.Button(self, text="New Window", width=20,
command=self.call_bet)
self.button_rennen.pack()
def call_bet(self):
# `self.master` is the window
Bet = BetFrame(self.master)
Bet.pack()
self.destroy()
class BetFrame(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
# You need to tell the button that its master should be this BetFrame
# If you don't it will assume you mean the window so
# `self.destroy()` isn't going to destroy the button.
self.button = tk.Button(self, text="Wette platzieren", command=self.question)
self.button.pack()
def question(self):
dialog = tk.messagebox.askokcancel(message="Destroy Window?")
if dialog is True:
self.destroy()
def main():
root = tk.Tk()
Menue = MainMenue(root)
Menue.pack()
root.mainloop()
main()
Your code is great but it was 2 mistakes:
Instead of creating a new window is it better to reuse the old one.
When you create your button in your BetFrame class, you don't pass anything for the master argument (the first argument). That is why the button isn't destroyed when you destroy the BetFrame object using self.destroy()
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:
I'm trying to attach link to a button that will open when clicked.
from Tkinter import *
import webbrowser
class Window(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.master = master
self.init_window()
def init_window(self):
self.master.title("Anonymous Button Box")
self.pack(fill=BOTH, expand=1)
_Button = Button(self, text="Don't Click", command=webbrowser.open("http://example.com", new=2, autoraise="True"))
_Click = Button(self, text="Click")
_Click.place(x=80, y=0)
_Button.place(x=0, y=0)
root = Tk()
root.geometry("300x300")
app = Window(root)
root.mainloop()
When I run this it automatically opens the link before the box itself pops up. How would I write it so the link only runs when the button is pressed?
would I create another function to open the link then command it to use the function when button is pressed?
As a rule of thumb, a button command should call a function that accepts no parameters. By doing this, it becomes easier to write the command, to create the button, and to debug the program.
So, in this case I would create a function named something like open_browser, oron_dont_click_button, or something like that:
def open_browser(self):
webbrowser.open("http://example.com", new=2, autoraise="True")
Then, the button becomes much easier to implement:
_Button = Button(..., command=open_browser, ...)
I wrapped the button command in a lambda function. So my button looks like this:
`_Button = Button(self, text="Don't Click", command=lambda: webbrowser.open("http://example.com", new=2, autoraise="True"))`
I'm trying to put a info popup window to the user to advise him that a file is being created and that he must wait until it's created. I've a master frame that creates a popup window that shows the Progressbar with a message. This popupwindow must be destroyed as soon as the file has been created on the system.
This is my try:
import os
from Tkinter import *
import ttk
class UI(Frame):
def __init__(self,master):
Frame.__init__(self, master)
self.master = master
self.initUI()
def initUI(self):
popup = Toplevel(self)
txt = Label(popup, text="Please wait until the file is created").grid(row=0, column=0)
progressbar = ttk.Progressbar(popup, orient=HORIZONTAL, length=200, mode='indeterminate')
progressbar.grid(row=1, column=0)
progressbar.start()
self.checkfile()
progressbar.stop()
popup.destroy()
def checkfile(self):
while os.path.exists("myfile.txt") == False:
print "not created yet"
if __name__ == "__main__":
root = Tk()
aplicacion = UI(root)
root.mainloop()
The problem is that the UI get's freezed and I can't see any window. I think I must use Threads to solve this problem right? Do I've to make two threads, one for the UI and the other one for the checkfile function, or with one is enough?
It would be highly appreciated if someone could add the Threads to my code to make it work as I've never use them and I'm totally lost.
Thanks in advance.
while loop cause the UI unreponsive.
Use Widget.after instead to periodically checkfile method.
def initUI(self):
self.popup = popup = Toplevel(self)
Label(popup, text="Please wait until the file is created").grid(
row=0, column=0)
self.progressbar = progressbar = ttk.Progressbar(popup,
orient=HORIZONTAL, length=200, mode='indeterminate')
progressbar.grid(row=1, column=0)
progressbar.start()
self.checkfile()
def checkfile(self):
if os.path.exists("myfile.txt"):
print 'found it'
self.progressbar.stop()
self.popup.destroy()
else:
print 'not created yet'
self.after(100, self.checkfile) # Call this method after 100 ms.
What modified:
Used after instead of while loop.
Made progressbar, popup accessible in checkfile method by making them instance attribute.
Moved progressbar.stop, popup.destroy to checkfile method.