I am trying to create a python app with a Tkinter UI and am currently having the following issue. I am trying to set up the UI such that a log is being kept in the background, and when the user presses a button a Toplevel
window appears. The window displays the log, and appends updates to it in real time. So far all of that works properly.
However I want to make it so that if the Toplevel
window is open, then it can't be opened again.
Additionally, the main program will be fullscreen when it is being run. This means that if the Log Window is open and the user interacts with the main program again, the Log Window is no longer visible. Is there a way to keep the Toplevel
window on top of the root window, even while the user is interacting with the root window?
Here is the code I have been fiddling with:
import tkinter as tk
class guiapp(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.master = master
self.value = 0.0
self.alive = True
self.list_for_toplevel = []
btn = tk.Button(self.master, text = "Click", command = self.TextWindow)
btn.pack()
def TextWindow(self):
#if not tk.Toplevel.winfo_exists(self.textWindow):
self.textWindow = tk.Toplevel(self.master)
self.textFrame = tk.Frame(self.textWindow)
self.textFrame.pack()
self.textArea = tk.Text(self.textWindow, height = 10, width = 30)
self.textArea.pack(side = "left", fill = "y")
bar = tk.Scrollbar(self.textWindow)
bar.pack(side = "right", fill = "y")
bar.config(command = self.textArea.yview)
self.alive = True
self.timed_loop()
def timed_loop(self):
if self.alive == True and tk.Toplevel.winfo_exists(self.textWindow):
self.master.after(1000, self.timed_loop)
self.value += 1
self.list_for_toplevel.append(self.value)
self.textArea.delete(1.0, "end-1c")
for item in self.list_for_toplevel:
self.textArea.insert('end', "{}\n".format(item))
self.textArea.see('end')
else:
self.alive = False
if __name__ == "__main__":
root = tk.Tk()
myapp = guiapp(root)
root.mainloop()
The line I have commented out in the TextWindow
method ( if not tk.Toplevel.winfo_exists(self.textWindow)
) is what I was attempted to use as a "If this exists, don't make the window" kind of deal. However running it I get the error:
'guiapp' has no attribute ''textWindow'
I mean I understand that the program doesn't have the attribute textWindow
before it exists. That's the whole reason I was attempting to use winfo_exists()
in the first place.
I'm wondering if I should create a isOpen
boolean, but the problem is that I don't know how to detect when a window closes.
Any help is aprpeciated.
As a simple fix, I would just create self.topLevel in the constructor. Then, since tk.Toplevel.winfo_exists() apparently can't take None as an argument, you could make your conditional:
if self.textWindow == None or not tk.Toplevel.winfo_exists(self.textWindow):
Then no matter what you are always passing a valid TopLevel into winfo_exists(), and a TopLevel is still created the first time the button is clicked.
The end result would look like this:
import tkinter as tk
class guiapp(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.master = master
self.value = 0.0
self.alive = True
self.textWindow = None
self.list_for_toplevel = []
btn = tk.Button(self.master, text = "Click", command = self.TextWindow)
btn.pack()
def TextWindow(self):
if self.textWindow == None or not tk.Toplevel.winfo_exists(self.textWindow):
self.textWindow = tk.Toplevel(self.master)
self.textFrame = tk.Frame(self.textWindow)
self.textFrame.pack()
self.textArea = tk.Text(self.textWindow, height = 10, width = 30)
self.textArea.pack(side = "left", fill = "y")
bar = tk.Scrollbar(self.textWindow)
bar.pack(side = "right", fill = "y")
bar.config(command = self.textArea.yview)
self.alive = True
self.timed_loop()
def timed_loop(self):
if self.alive == True and tk.Toplevel.winfo_exists(self.textWindow):
self.master.after(1000, self.timed_loop)
self.value += 1
self.list_for_toplevel.append(self.value)
self.textArea.delete(1.0, "end-1c")
for item in self.list_for_toplevel:
self.textArea.insert('end', "{}\n".format(item))
self.textArea.see('end')
else:
self.alive = False
if __name__ == "__main__":
root = tk.Tk()
myapp = guiapp(root)
root.mainloop()
You just need to initialize self.textWindow
in addition to checking whether it exists:
class guiapp(tk.Frame):
...
self.textWindow = None
...
def TextWindow(self):
if self.textWindow is None or not self.textWindow.winfo_exists():
self.textWindow = tk.Toplevel(self.master)
...
My first thought would be to make a version of Toplevel
that can react to being destroyed:
class Skitzafreak(tk.Toplevel):
def destroy(self):
print('destroyed toplevel') # plus any other code you want to add
tk.Toplevel.destroy(self)
I would disable the button on click and use the other code above to reenable it.
import tkinter as tk
from tkinter.scrolledtext import ScrolledText
class Skitzafreak(tk.Toplevel):
def destroy(self):
self.master.alive = False
self.master.btn.config(state=tk.NORMAL)
tk.Toplevel.destroy(self)
class guiapp(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
# self.master = master # this line is baked into tkinter widgets; you don't have to repeat it.
self.value = 0.0
self.btn = tk.Button(self, text = "Click", command = self.open_text_window)
self.btn.pack()
self.alive = False
def open_text_window(self):
textWindow = Skitzafreak(self)
self.textArea = ScrolledText(textWindow, height = 10, width = 30)
self.textArea.pack(side = "left", fill = "y")
self.btn.config(state=tk.DISABLED)
self.alive = True
self.timed_loop()
def timed_loop(self):
if self.alive:
self.after(1000, self.timed_loop)
self.value += 1
self.textArea.insert('end', "{}\n".format(self.value))
self.textArea.see('end')
if __name__ == "__main__":
root = tk.Tk()
myapp = guiapp(root)
myapp.pack()
root.mainloop()
Also, remember to use 'self' as the master for your widgets, not 'self.master'.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.