Python Tkinter - closing a child window with an exit button - python

I have a Raspberry Pi with the Piface adaptor board. I have made a GUI which controls the LED's on the Piface board.
One button on the GUI opens a new window which, on the press of a button, starts and stops running a small piece of code to make the LED's run up and down continuously, like Knight Riders car, using a While loop in a thread.
In this new window I have added a EXIT button. I want to add a piece of code that will close the new window when I click the EXIT button, and then return to the main window.
I have looked up many examples but just can't quite see what I should put or where. I have tried the 'quit' but it closed the whole program.
Having looked at many examples I maybe creating my new window in not quite the right way so feel free to tell me if there are better ways.
So is there a better way of doing it? Any pointers would be appreciated.
Thanks in advance.
Heres a piece of the code....
def new_window(self):
print('New Window')
self.newWindow = tk.Toplevel(self.master)
self.app = App2(self.newWindow)
self.newWindow.grab_set() # I added this line to stop opening multiple new windows
class App2:
def __init__(self, master):
frame = Frame(master)
frame.pack()
Label(frame, text='Turn LED ON').grid(row=0, column=0)
Label(frame, text='Turn LED OFF').grid(row=0, column=1)
self.button0 = Button(frame, text='Knight Rider OFF', command=self.convert0)
self.button0.grid(row=2, column=0)
self.LED0 = Label(frame, image=logo2)
self.LED0.grid(row=2, column=1)
self.button9 = Button(frame, text='Exit', command=self.close_window)
self.button9.grid(row=3, column=0)
def convert0(self, tog=[0]):
tog[0] = not tog[0]
if tog[0]:
print('Knight Rider ON')
self.button0.config(text='Knight Rider ON')
t=threading.Thread(target=self.LED)
t.start()
self.signal = True #added to stop thread
self.LED0.config(image = logo)
else:
print('Knight Rider OFF')
self.button0.config(text='Knight Rider OFF')
self.signal = False #added to stop thread
self.LED0.config(image = logo2)
def LED(self):
while self.signal: #added to stop thread
a=0
while self.signal: #added to stop thread
pfio.digital_write(a,1) #turn on
sleep(0.05)
pfio.digital_write(a,0) #turn off
sleep(0.05)
a=a+1
if a==7:
break
while self.signal: #added to stop thread
pfio.digital_write(a,1) #turn on
sleep(0.05)
pfio.digital_write(a,0) #turn off
sleep(0.05)
a=a-1
if a==0:
break
def close_window(self):
print('Close Child window')
#self.newWindow.destroy() Not sure what to put here?

If you put the new_window into your App2 then you should be fine.
self.newWindow.destroy()
Destroys the window. This is the right call. The window is closed then and all widgets in the window also get destroyed.
quit() will stop the mainloop() In this case the the program ends at the last line and also destroys everything.
You definitely want to use destroy.
class App2:
newWindow = None
def close_window(self):
print('Close Child window')
if self.newWindow:
try: self.newWindow.destroy()
except (): pass # fill in the error here
self.newWindow = None
def new_window(self):
print('New Window')
self.close_window()
self.newWindow = tk.Toplevel(self.master)
self.app = App2(self.newWindow)
self.newWindow.grab_set()
#classmethod
def start_app(cls):
window = tk.Tk(self.master)
app = App2(window)
return app
You should not acces Tkinter from threads. Have a look at alternatives
I was confused by quit and destroy, too, when I was starting with Tkinter.

In Tk windows are destroyed using the destroy method. So if you have a dialog toplevel and you want to get rid of it you call its destroy() method. Or you can withdraw it in which case the object continues to exist but is no longer on-screen and to re-show it you deiconify() the toplevel frame. It's more common to destroy them though. Here is a simple example creating and destroying a child dialog:
import sys
from Tkinter import *
class App(Frame):
def __init__(self, parent = None):
Frame.__init__(self, parent)
self.grid()
self.button = Button(self, text = "Create Dialog", command=self.CreateDialog)
self.button.grid()
def CreateDialog(self):
dialog = Toplevel(self)
dialog.wm_title("Dialog window")
dialog.wm_transient(self)
dialog.wm_protocol("WM_DELETE_WINDOW", lambda: self.onDeleteChild(dialog))
button = Button(dialog, text="Close", command=lambda: self.onDeleteChild(dialog))
button.grid()
def onDeleteChild(self, w):
w.destroy()
def main():
app = App()
app.mainloop()
if __name__ == "__main__":
sys.exit(main())
You should also consider looking at using a timer in the code to drive the LED loop rather than a while loop. Take a look at this answer using Tk's after function to run code after an interval. If you re-schedule another after call in the handler, then you can arrange a function to be run at regular intervals and avoid blocking the event handling without requiring additional threads.

Related

python 3.8 tkinter calling new window from method creates a blank window

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()

Thread won't stop until window is destroyed, window won't destroy unless thread is stopped

So I have a window which is controlled by a thread that runs in the background and changes the GUI when necessary, at some point this thread will be instructed to change window (involving destroying the window it is in and starting up another window), but this never happens because the thread won't stop executing until the window is changed.
Below is a simplified example:
class Window1:
def __init__(...):
self.Master = tk.Tk()
# some code
self.BackgroundUpdates = threading.Thread(target=self.ActiveWindow)
self.BackgroundUpdates.start()
def ActiveWindow(self):
# gets some instruction
if instruction == 'next window':
nextWindow(self)
def StartWindow(self):
self.Master.mainloop()
def KillWindow(self):
self.Master.destroy()
class Window2:
def __init__(...):
self.Master = tk.Tk()
# some code...
def StartWindow(self):
self.Master.mainloop()
def nextWindow(objectWindow):
objectWindow.KillWindow()
# when this function is called it never gets past the line above
nextWindow = Window2()
nextWindow.StartWindow()
application = Window1()
application.StartWindow()
Is there a way that I could rearrange the way I handle the thread so that I don't run into this problem?
a runnable example:
import tkinter as tk
import threading
class MainWindow:
def __init__(self):
self.Master = tk.Tk()
self.Frame = tk.Frame(self.Master, width=100, height=100)
self.Frame.pack()
self.Updates = threading.Thread(target=self.BackgroundUpdates)
self.Updates.start()
def BackgroundUpdates(self):
# imagine instructions to be a really long list with each element being a
# different instruction
instructions = ['instruction1', 'instruction2', 'next window']
while True:
if instructions[0] == 'next window':
ChangeWindow(self)
else:
instructions.remove(instructions[0])
def StartWindow(self):
self.Master.mainloop()
def KillWindow(self):
self.Master.destroy()
class SecondaryWindow:
def __init__(self):
self.Master = tk.Tk()
self.Frame = tk.Frame(self.Master, width=100, height=100)
self.Frame.pack()
def StartWindow(self):
self.Master.mainloop()
def KillWindow(self):
self.Master.destroy()
def ChangeWindow(oldObject):
oldObject.KillWindow()
# the line above will halt the program, since it has to wait on the thread to
# finish before the window can be destroyed, but this function is being called
# from within the thread and so the thread will never stop executing
del oldObject
newObject = SecondaryWindow()
newObject.StartWindow()
window = MainWindow()
window.StartWindow()
I realised that tkinter is singularly threaded, it can be explained more here:
https://stackoverflow.com/a/45803955/11702354
The problem was that I was trying to destroy my window from a different thread to the one it was created in. To solve this problem I had to use the 'after' method from the Tkinter module as well as using a event, this meant that I could control the background stuff (i.e. wait on a specific command from my connected server) and when I needed to change the window I would set the event.
Part of my adapted code can be seen below:
def CheckEvent(self):
if LOBBY_EVENT.is_set():
ChangeWindow(self, 'game')
self.Master.after(5000, self.CheckEvent)
def StartWindow(self):
self.Master.after(5000, self.CheckEvent)
self.Master.after(2000, self.HandleInstruction)
self.Master.mainloop()
So whenever I was calling the StartWindow method for my window, it would check whether the event has been set every 5 seconds, and then every 2 seconds it would go to a separate function 'HandleInstruction' which allowed me to create a response in my GUI (I also used queues to pass information to this function)
I hope this clears up confusion if anyone is to stumble across it!

How do you silence exptions in python, but still perform the action?

For some reason, closing a tkinter popup window with a button requires you to update the window to close it (otherwise it will just sit there), but calling update to close the window raises an exception.
I've tried the following, which works but raises an exception, which I cant have in the final product:
Button1 = ttk.Button(popupWindow, text="Close", command=popupWindow.destroy)
Button1.pack(expand = Y)
while popupWindow:
time.sleep(0.1)
popupWindow.update()
I've also tried the obvious try/except method:
Button1 = ttk.Button(popupWindow, text="Close", command=popupWindow.destroy)
Button1.pack(expand = Y)
while popupWindow:
time.sleep(0.1)
try:
popupWindow.update()
except:
pass
This just causes the program to hang and become unresponsive, as if it's waiting for popupWindow.update() to be called. Is there a way to just silence the exception that's raised?
You should not use a while loop, sleep, and update to wait for a button click or to wait for a window to go away. Those are the root cause of the exception you're seeing.
Tkinter has functions specifically for that purpose. By using one of these features you don't have to try to catch an exception since no exception will be thrown.
In your case, it appears you're wanting to wait for a widget to be destroyed. You can use tkinter's 'wait_window` function which does exactly that -- it waits for a window to be destroyed.
Here's a contrived example of a popup window. When you create it, it centers a new window on its parent, then waits until the window is destroyed. This example uses a Frame as the base, but you could also use Toplevel or Canvas or any other widget.
import tkinter as tk
class PopupWindow(tk.Frame):
def __init__(self, parent, message):
tk.Frame.__init__(
self, parent,
borderwidth=2, relief="raised",
background="bisque",
)
label = tk.Label(self, text=message, bg=self.cget("background"))
ok_button = tk.Button(self, text="Ok", command=self.destroy)
ok_button.pack(side="bottom", pady=10)
label.pack(side="top", padx=20, pady=20)
# center this widget on parent window
self.place(relx=.5, rely=.5, anchor="center")
# wait until the window has been destroyed
self.wait_window(self)
Here's an example of using this widget:
import tkinter as tk
class Example():
def __init__(self):
self.root = tk.Tk()
self.root.geometry("400x400")
button = tk.Button(self.root, text="Do Something", command=self.do_something)
button.pack(side="top")
self.root.mainloop()
def do_something(self):
print("doing something...")
print("waiting...")
popupWindow = PopupWindow(self.root, "Click button to continue")
print("done waiting...")
ex = Example()

Passing variables into a tkinter class window

I want to create a GUI in tkinter that not only executes commands when a button is pressed, but responds to the state of a larger script running in a separate thread.
I have really dug around and tried to find some information on message passing, and I have found some great info on the pickle module, using multiprocessing and its built in tools and also threading, and queuing. I have even dug into David Beazley's lesson on concurrency located here. I just can't get the syntax right on any of those methods.
I have broken down my code into a small functional unit that should launch a little tkinter window like this:
tkinter window
The code below has a "launchGUI" function that launches my tkinter GUI, a "myLoop" function that starts the threads and will also loop to drive my larger program later, right now it just rotates the blink variable. I also have a blinkCheck method in my class that checks the status of the blink variable in the class.
I don't know if I am even putting my message receiver in the right place. In the following example code I am just trying to pass a global variable into the class. I know it is getting into the class, because the blinkCheck() method works even though uncommenting that method crashes the window. However, with the method turned off the label in the GUI never changes. I think the window crashing is the least of my worries, it must be because i have another while loop running.
What is the correct way to get that number in Label to change?
Here is my example code:
import tkinter as tk
from tkinter import Frame, Label
import time
import threading
blink = 0
class MyClass(tk.Frame):
def __init__(self, master):
self.master = master
super().__init__(self.master)
global blink
self.label = Label(master, text=blink)
self.label.pack()
#self.blinkCheck()
def blinkCheck(self):
global blink
while True:
print("blink in blinkCheck method is = {}".format(blink))
time.sleep(2.5)
def launchGUI():
root = tk.Tk()
root.title("My Blinker")
app1 = MyClass(root)
app1.mainloop()
def myLoop():
global blink
t1=threading.Thread(target=launchGUI)
t1.daemon = True
t1.start()
print("blink in blinker function is {}".format(blink))
while True:
if blink == 0:
blink = 1
else:
if blink == 1:
blink = 0
time.sleep(2.5)
if __name__=="__main__":
myLoop()
In your description you have mentioned something about involving buttons. I do not see that in your provided snippet. But with buttons it is possible to configure the label, i.e:
from tkinter import Label, Button
blink = 0
class MyClass(tk.Frame):
def __init__(self, master):
self.master = master
super().__init__(self.master)
global blink
self.label = Label(master, text=blink)
self.button = Button(master, text="Button", command=lambda: foo(self.label))
self.label.pack()
self.button.pack()
#self.blinkCheck()
def blinkCheck(self):
global blink
while True:
print("blink in blinkCheck method is = {}".format(blink))
time.sleep(2.5)
def foo(self, label):
label.config(text=blink)
Conventionally, this would be the most simple way to configure a label within an active thread.
If anyone feels like this answer may not be fully correct, please do edit it because I am new to Stack Overflow!
First, the GUI must run in main thread, and must not blocked by a infinite loop. Use after instead. To communicate, use some appropriate object from threading, e.g. Event:
import tkinter as tk
import time
import threading
class MyClass(tk.Frame):
def __init__(self, master, event):
super().__init__(master)
self.master = master
self.event = event
self.label = tk.Label(master, text='')
self.label.pack()
self.after(100, self.blink_check)
def blink_check(self):
self.label['text'] = self.event.is_set()
self.after(100, self.blink_check)
def blink(event):
while True:
event.set()
time.sleep(2.5)
event.clear()
time.sleep(2.5)
def main():
root = tk.Tk()
root.title("My Blinker")
event = threading.Event()
t = threading.Thread(target=blink, args=(event,))
t.daemon = True
t.start()
frame = MyClass(root, event)
root.mainloop()
if __name__=="__main__":
main()

Keep tkinter Progressbar running until a file is created

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.

Categories