Skitzafreak
Skitzafreak

Reputation: 1917

Tkinter Toplevel Window Not Movable

In my Tkinter app I have a button that opens a Toplevel Window which dispays an event log. There are a few things I need the Toplevel Window to be able to do:

  1. Show previous log entries when opened, as well as update with new ones.
  2. Disable the user's ability to move the window around the while also making the user able to close the window
  3. Have the window always be anchored with it's top right corner being at the root window's top right corner

I have figured out #1. I am able to have the window open and display previous entries as well as update those entries while the window is open. My problem is with #2 and #3.

For #2 I am not sure how to disable the user's ability to move the window. I am assuming this may also disable the user's ability to close the window so I am not sure how to keep that functionality intact. Maybe a button with self.quit() as it's command?

As for #3, I have no idea how to go about doing this. Maybe I suck as Googling but I can't seem to find out how to accomplish this.

This is the code I have at the moment, which is able to properly implement feature #1.

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):
        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()
    root.geometry("800x480")
    myapp = guiapp(root)
    root.mainloop()

Upvotes: 0

Views: 5210

Answers (1)

Mike - SMT
Mike - SMT

Reputation: 15226

We can remove the tool bar from the top of the toplevel window and prevent the user from moving the window with self.textWindow.overrideredirect(True).

Then we can make sure that the toplevel window is positioned in the top right corner by getting the root windows location and then setting the toplevel window to the same location with self.master.winfo_x() and self.master.winfo_y().

Last I would add a button that closes the window because we no longer have the tool bar for the toplevel window.

UPDATE: I have added the ability for the toplevel window to stay on top of the root window and to move around with the root window when root is dragged.

we can use bind() to track when the root window is moved and then have a function that will update the toplevel windows position to match the root windows.

We can also use self.textWindow.attributes("-topmost", True) to tell tkinter to keep out toplevel window on top of all other windows.

Take a look at the modified version of your code below. Let me know what you think or if you have any questions.

import tkinter as tk

class guiapp(tk.Frame):

    def __init__(self, master):
        tk.Frame.__init__(self, master)
        self.master = master
        self.textWindow = None
        self.master.bind("<Configure>", self.move_me)
        self.value = 0.0
        self.list_for_toplevel = []
        btn = tk.Button(self.master, text = "Click", command = self.TextWindow)
        btn.pack()

    def TextWindow(self):
        x = self.master.winfo_x()
        y = self.master.winfo_y()

        self.textWindow = tk.Toplevel(self.master)
        self.textFrame = tk.Frame(self.textWindow)
        self.textWindow.overrideredirect(True)
        self.textFrame.pack()
        self.textWindow.attributes("-topmost", True)

        self.textWindow.geometry('+{}+{}'.format(x+10, y+30))
        self.close_toplevel = tk.Button(self.textWindow, text = "close", command = self.close_textWindow)
        self.close_toplevel.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 close_textWindow(self):
        self.textWindow.destroy()
        self.textWindow = None

    def move_me(self, event):
        if self.textWindow != None:
            x = self.master.winfo_x()
            y = self.master.winfo_y()
            self.textWindow.geometry('+{}+{}'.format(x+10, y+30))

    def timed_loop(self):
        if self.textWindow != None:
            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')


if __name__ == "__main__":

    root = tk.Tk()
    root.geometry("800x480")
    myapp = guiapp(root)
    root.mainloop()

Upvotes: 2

Related Questions