简体   繁体   中英

Open Tkniter Toplevel only if it doesn't already exist

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM