I've included some basic code below, which generates a frame, and then a toplevel made to destroy itself. A second is created after the first is destroyed.
When this application is run, while the first toplevel is waiting, if the 'X' on the main window is clicked, it kills itself and the toplevel, but then the second toplevel is created along with a generic Tk(). When that is closed I get an error: _tkinter.TclError: can't invoke "wm" command: application has been destroyed
I've tried using root.destroy(), quit() and os._exit(), but none of these completely stops the application. What can be done to completely stop any script from running after the root window is destroyed?
from tkinter import *
class Application(Frame):
def __init__(self,master):
Frame.__init__(self,master)
self.L1 = Label(root,text='Hi!')
self.L1.pack()
def Window1():
Wind1 = Toplevel()
Wind1.geometry('100x100+100+100')
Wind1.B1 = Button(Wind1,text='Close',command=Wind1.destroy)
Wind1.B1.pack()
Wind1.lift(aboveThis=root)
Wind1.wait_window()
def Window2():
Wind2 = Toplevel()
Wind2.geometry('100x100+100+100')
Wind2.B2 = Button(Wind2,text='Close',command=Wind2.destroy)
Wind2.B2.pack()
Wind2.lift(aboveThis=root)
Wind2.wait_window()
def Close_Window():
root.destroy()
root = Tk()
root.geometry('100x100+50+50')
root.protocol('WM_DELETE_WINDOW',Close_Window)
app = Application(root)
Window1()
Window2()
root.mainloop()
The exact reason for your error is caused by 2 problems. One is that both windows are not being created at start up due to the wait_window() method. The other problem is the lack of a parent being defined for your Toplevel() windows.
Take a look at the below modified code. (Note this code needs some work still but is what you need to change to fix the error)
from tkinter import *
class Application(Frame):
def __init__(self,master):
Frame.__init__(self,master)
self.L1 = Label(root, text='Hi!')
self.L1.pack()
def Window1():
Wind1 = Toplevel(root)
Wind1.geometry('100x100+100+100')
Wind1.B1 = Button(Wind1,text='Close',command=Wind1.destroy)
Wind1.B1.pack()
Wind1.lift(aboveThis=root)
#Wind1.wait_window()
def Window2():
Wind2 = Toplevel(root)
Wind2.geometry('100x100+100+100')
Wind2.B2 = Button(Wind2,text='Close',command=Wind2.destroy)
Wind2.B2.pack()
Wind2.lift(aboveThis=root)
#Wind2.wait_window()
def Close_Window():
root.destroy()
root = Tk()
root.geometry('100x100+50+50')
root.protocol('WM_DELETE_WINDOW',Close_Window)
app = Application(root)
Window1()
Window2()
root.mainloop()
I think you would benifit more from moving everything into a class. This way you can use class attributes to manage all data within the application including those you get from Toplevel() widgets.
import tkinter as tk
class Application(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.geometry('100x100+50+50')
self.protocol('WM_DELETE_WINDOW', self.close_window)
self.L1 = tk.Label(self, text='Hi!')
self.L1.pack()
tk.Button(self, text="Window 1", command=self.window1).pack()
tk.Button(self, text="Window 2", command=self.window2).pack()
def window1(self):
wind1 = tk.Toplevel(self)
wind1.geometry('100x100+100+100')
wind1.B1 = tk.Button(wind1, text='Close', command=wind1.destroy).pack()
def window2(self):
wind2 = tk.Toplevel(self)
wind2.geometry('100x100+100+100')
wind2.B2 = tk.Button(wind2, text='Close', command=wind2.destroy).pack()
def close_window(self):
self.destroy()
app = Application()
app.mainloop()
Related
I am working on a game called 'Flag Quiz' using tkinter. I have a script called mainmenu where I can choose between an easy mode and a hard mode. If I click on one of the buttons the recent mainmenu window disappears and a new tkinter window opens.
Here is my mainmenu script:
from tkinter import *
import tkinter as tk
from hardmode import HardApp
from easymode import EasyApp
class TitleScreen(tk.Tk):
def __init__(self):
super().__init__()
self.title('Flag Quiz')
self.geometry('600x600')
self.resizable(0,0)
self.make_widgets()
def make_widgets(self):
self.background = PhotoImage(file = './background.png')
self.label = Label(self, image=self.background)
self.label.place(x=0, y=0, relwidth=1, relheight=1)
self.easy = Button(self, text="Easy Mode", height=2, width=6, font=('default', 20), command=self.play_easy)
self.hard = Button(self, text="Hard Mode", height=2, width=6, font=('default', 20), command=self.play_hard)
self.easy.place(relx=0.5, rely=0.45, anchor=CENTER)
self.hard.place(relx=0.5, rely=0.55, anchor=CENTER)
def play_easy(self):
self.withdraw()
self.app = EasyApp()
#self.app.start()
def play_hard(self):
self.withdraw()
self.app = HardApp()
#self.app.start()
def start(self):
self.mainloop()
TitleScreen().start()
And here is my easy mode script:
import tkinter as tk
from tkinter import *
import random
import os
import json
class EasyApp(tk.Toplevel):
def __init__(self):
super().__init__()
self.title('Flag Quiz')
self.geometry('')
self.resizable(0,0)
self.score = 0
self.create_widgets()
def create_widgets(self):
# variables
self.user_guess = StringVar(self)
self.text = StringVar(self)
self.text.set(" ")
# initial image
self.scoretext = Label(self, text="Score: ").pack(side='top', fill='x')
self.scorevalue = Label(self, text=self.score).pack(side='top', fill='x')
self.file = random.choice(os.listdir('pngs'))
self.randimg = PhotoImage(file='pngs/{}'.format(self.file))
self.randimg = self.randimg.subsample(2, 2)
self.panel = Label(self, image=self.randimg)
self.panel.pack()
self.country, self.ext = self.file.split('.')
self.countries = self.load_lookup()
self.countryname = [country for country in self.countries if country['alpha2'] == self.country]
self.s = []
for i in range(0,3):
country = random.choice(self.countries)
self.s.append(country['de'])
self.s.append(self.countryname[0]['de'])
random.shuffle(self.s)
self.btndict = {}
for i in range(4):
self.btndict[self.s[i]] = Button(self, text=self.s[i], height=2, width=35, font=('default', 20), command=lambda j=self.s[i]: self.check_input(j))
self.btndict[self.s[i]].pack()
def check_input(self, d):
if d != self.countryname[0]['de']:
print("Falsch")
else:
self.score += 5
for widget in self.winfo_children():
widget.destroy()
self.create_widgets()
def load_lookup(self):
with open('lookup.json') as file:
self.obj = file.read()
self.countryobj = json.loads(self.obj)
return self.countryobj
# def start(self):
# self.mainloop()
After clicking the close button (the default button on windows/osx to close a window) the window from my easy mode app disappears but PyCharm says that my program is still running.
I made some investigations and removed the self.withdraw() function in the function play_easy(self) in my mainmenu script. So now the mainmenu is still open after I click on the easy mode button. If I'm closing both windows now, the program fully ends.
Replacing self.withdraw() with self.destroy() is not working. The main menu is closed, but a new empty window opens instead.
Any suggestions on how to handle this problem so that my program fully ends if I click the close button within the easy/hard mode window?
You have two windows - main window created with Tk and subwindow created with Toplevel. When you use close button to close main window then it should also close all subwindows but when you close subwindow then it doesn't close main window (parent window) but only own subwindows - because usually it can be useful to display again main window to select other options and open again subwindow.
One of the methods is to destroy first window and use Tk to create new window.
But in this method you can't use some button in second window to go back to first window - and sometimes it can be problem. Even if you create again first window then it will not remeber previous values (if you have some Entry or other widgets to set values)
# from tkinter import * # PEP8: `import *` is not preferred`
import tkinter as tk
class EasyApp(tk.Tk): # use `Tk` instead of `Toplevel`
def __init__(self):
super().__init__()
self.scoretext = tk.Label(self, text="EasyApp")
self.scoretext.pack()
#def start(self):
# self.mainloop()
class TitleScreen(tk.Tk):
def __init__(self):
super().__init__()
self.button = tk.Button(self, text="Easy Mode", command=self.play_easy)
self.button.pack()
def play_easy(self):
self.destroy() # destroy current window
self.app = EasyApp()
def start(self):
self.mainloop()
TitleScreen().start()
Other method is to use self.wm_protocol("WM_DELETE_WINDOW", self.on_close) to execute function on_close when you use close button and in this function destroy main window (master).
This way you can still use Button to go back to main window which will remember previous content.
# from tkinter import * # PEP8: `import *` is not preferred`
import tkinter as tk
class EasyApp(tk.Toplevel): # still use `Toplevel`
def __init__(self, master): # send main window as master/parent
super().__init__(master) # it will also set `self.master = master`
self.scoretext = tk.Label(self, text="EasyApp")
self.scoretext.pack()
self.button = tk.Button(self, text="Go Back", command=self.go_back)
self.button.pack()
# run `on_close` when used `close button`
#self.protocol("WM_DELETE_WINDOW", self.on_close)
self.wm_protocol("WM_DELETE_WINDOW", self.on_close)
def go_back(self):
self.destroy() # destroy only current window
self.master.deiconify() # show again main window
def on_close(self):
self.destroy() # destroy current window
self.master.destroy() # destroy main window
class TitleScreen(tk.Tk):
def __init__(self):
super().__init__()
self.entry = tk.Entry(self)
self.entry.pack()
self.entry.insert('end', "You can change text")
self.button = tk.Button(self, text="Easy Mode", command=self.play_easy)
self.button.pack()
def play_easy(self):
self.withdraw()
self.app = EasyApp(self) # send main window as argument
def start(self):
self.mainloop()
TitleScreen().start()
Here's a fairly simple architecture for doing what you want. An application class is derived from Tk() which hides the default "root" window it normally displays and all the windows it does display are subclasses of a custom Toplevel subclass I've named BaseWin.
This class is just a Toplevel with its protocol for being delete (closed) set to call an a method named on_close(). This additional method simply destroys the current window before quits the application's mainloop() causing it to terminate.
The first window—an instance of the TitleScreen class—is displayed automatically when an instance of the application class is created. This window has two Buttons one labelled Easy Mode and the other Hard Mode. When one of them is clicked, an instance of the appropriate Toplevel subclass is created after the current window is removed by it call its destroy() method.
mainmenu.py
import tkinter as tk
from tkinter.constants import *
from tkinter import font as tkfont
from basewin import BaseWin
from easymode import EasyApp
from hardmode import HardApp
class SampleApp(tk.Tk):
def __init__(self):
super().__init__()
self.title('Flag Quiz')
self.geometry('600x600')
self.resizable(FALSE, FALSE)
self.title_font = tkfont.Font(family='Helvetica', size=18, weight="bold")
self.withdraw() # Hide default root Tk window.
startpage = TitleScreen(self.master)
self.mainloop()
class TitleScreen(BaseWin):
def __init__(self, master):
super().__init__(master)
self.make_widgets()
def make_widgets(self):
label = tk.Label(self, text="This is the Start Page", font=self.master.title_font)
label.pack(side="top", fill="x", pady=10)
self.easy = tk.Button(self, text="Easy Mode", font=('default', 20),
command=self.play_easy)
self.hard = tk.Button(self, text="Easy Mode", font=('default', 20),
command=self.play_hard)
self.easy.pack()
self.hard.pack()
def play_easy(self):
self.destroy()
self.app = EasyApp(self.master)
def play_hard(self):
self.destroy()
self.app = HardApp(self.master)
if __name__ == '__main__':
SampleApp()
basewin.py
import tkinter as tk
from tkinter.constants import *
class BaseWin(tk.Toplevel):
def __init__(self, master):
super().__init__(master)
self.protocol("WM_DELETE_WINDOW", self.on_close)
def on_close(self):
self.destroy() # Destroy current window
self.master.quit() # Quit app.
easymode.py
import tkinter as tk
from tkinter.constants import *
from basewin import BaseWin
class EasyApp(BaseWin):
def __init__(self, master):
super().__init__(master)
self.title('Flag Quiz')
self.resizable(FALSE, FALSE)
self.make_widgets()
def make_widgets(self):
label = tk.Label(self, text="This is the Easy App", font=self.master.title_font)
label.pack(side="top", fill="x", pady=10)
hardmode.py
import tkinter as tk
from tkinter.constants import *
from basewin import BaseWin
class HardApp(BaseWin):
def __init__(self, master):
super().__init__(master)
self.title('Flag Quiz')
self.resizable(FALSE, FALSE)
self.make_widgets()
def make_widgets(self):
label = tk.Label(self, text="This is the Hard App", font=self.master.title_font)
label.pack(side="top", fill="x", pady=10)
I want to create a screen manager in python. A menu with options for screens to go, and on each screen an option to go back to the menu. If I put the classes in the same file I won't have a problem, but if I try to modularize and distribute the classes in files, I can't import the 'main()' function in FirstScreen, for example. Each screen is in a different class and in a different file, when I try to go back to the menu the circle error will occur. What is the best way to solve it? Here is the code for each file:
Main
from tkinter import *
from primeira import *
class main():
def __init__(self):
self.master = Tk()
self.master.title('main window')
self.master.geometry('480x240')
self.master.configure(borderwidth=4, background='white')
self.button = Button(self.master, text='window one', command= lambda: self.evento())
self.button.pack(side='left', fill='x')
self.master.mainloop()
def evento(self):
self.master.destroy()
FirstWindow()
main()
First window (in another archive)
from tkinter import *
from main import main
class FirstWindow():
def __init__(self, master=None):
master = Tk()
self.master = master
self.master.title('window one')
self.master.configure(background='green')
self.master.geometry('480x240')
self.button = Button(master, text='menu', command= lambda: self.goMain())
self.button.pack(side='left', fill='x', expand=True)
master.mainloop()
def goMain(self):
self.master.destroy()
main()
Hi I am pretty new to tkinter and have being trying to create a button that opens a window then a have a button in the new window the gives a message when pressed. I ran into the problem that the only whay I could get it to recognise the function I wrote was to write it inside the function that opens the second window. I don't know if I have being searching for the wrong things but I can't find how to do this properly. Can someone help me out Here is my code
from tkinter import *
master = Tk()
master.title("frame control")
def win():
window2 = Toplevel()
def open():
stamp = Label(window2, text="Staped").pack()
lab2 = Button(window2,text = "yo ",command = open).pack()
lab1 = Button(master,text = " open a new window" , command = win).pack()
mainloop()
This is your code but with best practises:
import tkinter as tk
def create_stamp():
stamp = tk.Label(window2, text="Stamp")
stamp.pack()
def create_second_win():
global window2
window2 = tk.Toplevel(root)
lab2 = tk.Button(window2, text="Click me", command=create_stamp)
lab2.pack()
root = tk.Tk()
root.title("Frame control")
button = tk.Button(root, text="Open a new window", command=create_second_win)
button.pack()
root.mainloop()
I made window2 a global variable so that I can access it from create_stamp. Generally it is discouraged to use from ... import *. As #Matiiss said, sometimes you can have problems with global variables if you don't keep track of the variable names that you used.
If you want to avoid using global variables and want to use classes, look at this:
import tkinter as tk
class App:
def __init__(self):
self.stamps = []
self.root = tk.Tk()
self.root.title("Frame control")
self.button = tk.Button(self.root, text="Open a new window", command=self.create_second_win)
self.button.pack()
def create_stamp(self):
stamp = tk.Label(self.window2, text="Stamp")
stamp.pack()
self.stamps.append(stamp)
def create_second_win(self):
self.window2 = tk.Toplevel(self.root)
self.lab2 = tk.Button(self.window2, text="Click me", command=self.create_stamp)
self.lab2.pack()
def mainloop(self):
self.root.mainloop()
if __name__ == "__main__":
app = App()
app.mainloop()
As #Matiiss mentioned it would be more organised if you move the second window to its own class. For bigger projects it is a must but in this case you don't have to.
I've written the following code by taking reference from this question
from tkinter import *
def main():
def hide_me(event):
event.widget.pack_forget()
root = Tk()
btn=Button(root, text="Click")
btn.bind('<Button-1>', hide_me)
btn.pack()
btn2=Button(root, text="Click too")
btn2.bind('<Button-1>', hide_me)
btn2.pack()
btn3=Button(root,text="reload",command=main)
btn3.pack()
root.mainloop()
main()
but what I want is when I hit that reload button program will restart from beginning in the same window but it's starting in the new window. And when I've not declared root inside main then it'll restart with a chain of reload buttons.
please help. Thanks In advance.
It would be better to implement this as a class with a top-level Tk() window. This way you can keep one reference to the window throughout its lifecycle. On reload, call pack_forget() on all widgets within the window and then repack by a call to main.
This may help:
from tkinter import *
class UI:
def __init__(self):
self.root = Tk()
def hide_me(self, event):
event.widget.pack_forget()
def main(self):
self.btn=Button(self.root, text="Click")
self.btn.bind('<Button-1>', self.hide_me)
self.btn.pack()
self.btn2=Button(self.root, text="Click too")
self.btn2.bind('<Button-1>', self.hide_me)
self.btn2.pack()
self.btn3=Button(self.root,text="reload",command=self.reload)
self.btn3.pack()
self.root.mainloop()
def reload(self):
self.btn.pack_forget()
self.btn2.pack_forget()
self.btn3.pack_forget()
self.main()
if __name__ == "__main__":
ui = UI()
ui.main()
You can use root.destroy() method destroy the old tkinter root window and reinitialize your App class.
Please refer the following code:
import tkinter as tk
from tkinter import ttk
class App(object):
def __init__(self):
self.root = tk.Tk()
self.setup()
def setup(self):
self.btn = ttk.Button(self.root, text="click")
self.btn.bind('<Button-1>', self.hide_me)
self.btn.pack()
self.btn2 = ttk.Button(self.root, text="Click too")
self.btn2.bind('<Button-1>', self.hide_me)
self.btn2.pack()
self.btn3 = ttk.Button(self.root, text="reload", command=self.restart)
self.btn3.pack()
def hide_me(self, event):
event.widget.pack_forget()
def restart(self):
self.root.destroy()
self.__init__()
def main():
App()
tk.mainloop()
if __name__=="__main__":
main()
When creating a second window using python 3.6 and tkinter, it is not responsible. I`m using os x 10.11.6.
In other systems such as Ubuntu, this code works.
from tkinter import *
class win2:
def __init__(self):
self.root = Tk()
self.root.mainloop()
class win1:
def __init__(self):
self.root = Tk()
self.button = Button(self.root)
self.button.bind('<Button-1>', self.buttonFunc)
self.button.pack()
self.root.mainloop()
def buttonFunc(self, event):
windows2 = win2()
if __name__ == "__main__":
window1 = win1()
It's a very bad idea to use Tk() more than once in your program. Use it to make the root window, and then use Toplevel() to make any additional windows.
def buttonFunc(self, event):
Toplevel(self.root)
That said, it still looks like you are trying to do something the hard way. Can you describe better what your end goal is?
To make a modal window (a popup) use code like this:
try: #python3 imports
import tkinter as tk
except ImportError: #python3 failed, try python2 imports
import Tkinter as tk
class Main(tk.Frame):
def __init__(self, master=None, **kwargs):
tk.Frame.__init__(self, master, **kwargs)
lbl = tk.Label(self, text="this is the main frame")
lbl.pack()
btn = tk.Button(self, text='click me', command=self.open_popup)
btn.pack()
def open_popup(self):
print("runs before the popup")
Popup(self)
print("runs after the popup closes")
class Popup(tk.Toplevel):
"""modal window requires a master"""
def __init__(self, master, **kwargs):
tk.Toplevel.__init__(self, master, **kwargs)
lbl = tk.Label(self, text="this is the popup")
lbl.pack()
btn = tk.Button(self, text="OK", command=self.destroy)
btn.pack()
# The following commands keep the popup on top.
# Remove these if you want a program with 2 responding windows.
# These commands must be at the end of __init__
self.transient(master) # set to be on top of the main window
self.grab_set() # hijack all commands from the master (clicks on the main window are ignored)
master.wait_window(self) # pause anything on the main window until this one closes
def main():
root = tk.Tk()
window = Main(root)
window.pack()
root.mainloop()
if __name__ == '__main__':
main()
This code works for me.
from tkinter import *
class win1:
def __init__(self):
root = Tk()
button = Button(root)
button.bind('<Button-1>', self.buttonFunc)
button.pack()
root.mainloop()
def buttonFunc(self, event):
window2 = win2()
class win2(win1):
def __init__(self):
top = Toplevel()
if __name__ == "__main__":
window1 = win1()