I am currently creating a little program which inserts Tkinter Entry boxes into Google Calendar (after all different kind of checks ofcourse). That part is not the problem.
Since I am running a terminal at the same time that I don't want to 'hold' during the Tkinter window is open.
When I close the window using
def quit(self):
self.thread.stop()
self.destroy()
All the parts of the window disappear, but the window stays on screen.
class window(Frame):
def __init__(self, parent, thread):
Frame.__init__(self, parent)
self.parent = parent
self.w = 600
self.h = 400
self.initUI()
self.center()
self.thread = thread
I use this funciton to create the class:
def main(thread):
root = Tk()
root.resizable(width=False, height=False)
app = window(root, thread)
root.mainloop()
The myThread class.
class myThread(threading.Thread):
def __init__(self,threadType):
threading.Thread.__init__(self)
self.threadType = threadType
self.event = threading.Event()
def stop(self):
self.event.set()
def run(self):
if self.threadType == "new_app":
newappoint.main(self)
elif self.threadType == "main":
main()
Can anybody tell me if I missed something that would make the window close properly.
The thread is closed after calling the self.quit()
instead of self.destroy() do this self.parent.destroy().
As far as my understanding we are passing tk object to the class window and it used as parent in the class.So instead of using root to create widgets we are using parent.
Related
I have a project where a passive GUI runs in its own thread and is manipulated by the main thread. Especially, the window is closed by the main thread using event_generate:
from tkinter import Tk
import threading
import time
import queue
q = queue.Queue()
class Window:
def __init__(self):
self.root = Tk()
self.root.title("test")
self.root.bind("<<custom_close_event>>", self.close)
def close(self, event):
print("quit")
self.root.destroy()
def create_window():
window = Window()
q.put(window)
window.root.mainloop()
print("###########")
# Window creation executed in different thread
t1 = threading.Thread(target=create_window)
t1.start()
window = q.get()
time.sleep(2)
window.root.event_generate("<<custom_close_event>>")
print("end")
The program crashes with the following output:
quit
###########
Tcl_AsyncDelete: async handler deleted by the wrong thread
[1] 21572 IOT instruction (core dumped) python window_test.py
According to this discussion, it seems that the order of objects cleanup in multithreaded environment is in fault. The advice to nullify objects (in my case window) and to call gc.collect did not solve the problem.
How should I do?
Instead of using a separate thread to create a second reference to Tk(),
Just inherit tk.Toplevel when you create the "Window" class.
This will allow you to have really as many windows as you want.
You can use tk.after in order to monitor processes and do pseudo-multithreading things. Here's an example of how to do that
class Window(Toplevel):
def __init__(self, parent, *args, **kwargs):
super().__init__(*args, **kwargs)
self.parent = parent
...
self.parent.after(1000, self.do_something)
def do_something(self):
...
<code>
...
self.parent.after(1000, self.do_something)
root = Tk()
Window(root)
root.mainloop()
Using #AndrewPye's answer but inheriting from Tk instead of Toplevel:
from tkinter import *
class Window(Tk):
def __init__(self, **kwargs):
super().__init__(**kwargs)
super().after(1000, self.do_something)
def do_something(self):
print("I am in a loop that runs every 1000ms = 1s")
super().after(1000, self.do_something)
root = Window()
root.mainloop()
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!
I'm trying to write a program that I can hide and show via hotkeys. I managed to get the application to show and hide using the library "keyboard", however due to the "wait" function of the library, it prevents the Text box from functioning correctly. I have tried using the key bindings within Tkinter, however I had a different problem, whereby once the program was hidden or another application was selected, I couldn't return the focus to the hidden window via the hotkey.
import Tkinter as Tk
import keyboard
class MyApp(object):
def __init__(self, parent):
self.root = parent
self.root.title("Main frame")
self.frame = Tk.Frame(parent)
self.frame.pack()
self.editor = Tk.Text(self.frame)
self.editor.pack()
self.editor.config(font="Courier 12")
self.editor.focus_set()
keyboard.add_hotkey('ctrl+alt+s', self.show)
keyboard.add_hotkey('ctrl+alt+h', self.hide)
keyboard.wait()
self.root.withdraw()
def show(self):
self.root.update()
self.root.deiconify()
def hide(self):
self.root.withdraw()
if __name__ == "__main__":
root = Tk.Tk()
root.geometry("800x600")
app = MyApp(root)
root.mainloop()
Any assistance would be great :)
Just drop this wait command, its an additional mainloop, which is not needed as Tkinter does its job. I tried to fix your problem with threading, but as I wanted to check exactly what is NOT working, I accidentially made what I suppose you wanted to. So the Code is:
import tkinter as tk
import keyboard
class App(tk.Tk):
def __init__(self):
super().__init__()
self.geometry("800x600")
self.title("Main frame")
self.editor = Tk.Text(self)
self.editor.pack()
self.editor.config(font="Courier 12")
self.editor.focus_set()
keyboard.add_hotkey('ctrl+alt+s', self.show)
keyboard.add_hotkey('ctrl+alt+h', self.hide)
def show(self):
self.update()
self.deiconify()
def hide(self):
self.update()
self.withdraw()
if __name__ == "__main__":
App().mainloop()
I hope this works for you. I'd also recommend changing this key settings. Testing with those in PyZo is IMPOSSIBLE! It always tries to "save as...", which I don't want to...
I'm trying to build an app that incorporates wxwidgets (just for the tray icon) and Tkinter (for the rest of the GUI).
import wx
import Tkinter
TRAY_TOOLTIP = 'System Tray Icon'
TRAY_ICON = 'icon.png'
frm = False
class TaskBarIcon(wx.TaskBarIcon):
def __init__(self):
super(TaskBarIcon, self).__init__()
self.set_icon(TRAY_ICON)
self.Bind(wx.EVT_TASKBAR_LEFT_DOWN, self.on_left_down)
def set_icon(self, path):
icon = wx.IconFromBitmap(wx.Bitmap(path))
self.SetIcon(icon, TRAY_TOOLTIP)
def on_left_down(self, event):
createframe()
class Frame(Tkinter.Tk):
def __init__(self, parent):
Tkinter.Tk.__init__(self, parent)
self.parent = parent
self.protocol('WM_DELETE_WINDOW', self.closewindow)
self.grid()
def maximize(self):
# supposed to try to hide and bring a window back up
# full code removes the icon from the task bar, so I needed another way to make the window visible again
self.withdraw()
self.deiconify()
def closewindow(self):
self.destroy()
global frm
frm = False
def createframe():
global frm
if isinstance(frm, Tkinter.Tk): # if a window is open, it goes through this if statement
frm.maximize() # and crashes here.
else:
frm = Frame(None)
frm.title('Frame')
frm.mainloop()
def main():
app = wx.App()
TaskBarIcon()
app.MainLoop()
if __name__ == '__main__':
main()
You can run this code and hopefully see the problem. When you left-click the tray icon, a window pops up, you can close it and reopen it, however if you minimize the window (or just click the tray icon while the window is open), the app crashes. I suppose frm.maximize() is the problem, since I can call self.maximize() from within the class without trouble, but I was not able to find a solution.
I had the same problem when I was trying to do frm.destroy() from the TaskBarIcon class (while frm.quit() worked just fine), so maybe that's a hint?
You can't combine wxpython and tkinter in the same program.
I am writing a multi-window GUI program in tkinter. The code shown below is the layout of the main part of my code. The first window works fine but when I get to the second window by calling self.next_win, some things start to go sightly funny.
The main issue is when I go to destroy the GUI: I want there to be a quit button in each window which, when pushed, closes the entire GUI (root.destroy). From the first window this works fine, I call the self.quit method; but when I get to the second window it doesn't work. I know this is because master in the second window is a Toplevel widget but I'm not sure how to get around this. I don't see how I can call root.destroy from the second window.
How can I fix this? A line in the SecondWin.quit method perhaps, or a better code structure which makes the solution trivial (bear in mind I am a beginner so trivialities will have to be explained)? Thanks.
class FirstWin:
def __init__(self, master):
self.master = master
...
...
def next_win(self):
self.master.withdraw()
root2 = Toplevel()
SecondWin(root2)
def quit(self):
self.master.destroy()
class SecondWin:
def __init__(self, master):
self.master = master
...
...
def quit(self):
self.master.destroy() # What goes in here?
...
def main():
root = Tk()
GUI = FirstWin(root)
root.mainloop()
if __name__ == '__main__':
main()
All you need to do is tell SecondWin what the root window is, and it can destroy it.
...
root2 = Toplevel()
# tell the second window what the master is,
# and also tell it to be a child of FirstWin.
SecondWin(self.master, root2)
...
class SecondWin():
def __init__(self, root, master):
# root is the root window, master is the parent of this window
self.root = root
self.master = master
<other initialization code here>
def quit(self):
self.root.destroy()