nigel239
nigel239

Reputation: 1765

tkinter Toplevel window won't open from system tray menu

I'm trying to open a Toplevel window with tkinter, from a system tray menu.

from cmath import phase
from tkinter import *
from tkinter import messagebox, messagebox
from tracemalloc import start
from pystray import MenuItem as item
import pystray
from PIL import ImageTk,Image
import pickle

def quit_window(icon, item):
    icon.stop()
    root.destroy()
    exit()

def hidden():
    global my_img1
    top=Toplevel()
    top.title("Secret menu, shhh :^)")
    top.overrideredirect(True)
    top.attributes('-alpha', 0.9)
    w = 1100
    h = 450
    ws = top.winfo_screenwidth()
    hs = top.winfo_screenheight()
    x = (ws/2) - (w/2)
    y = (hs/3) - (h/2)
    top.geometry('%dx%d+%d+%d' % (w, h, x, y))
    top.iconbitmap('screen.ico')
    my_img1 = ImageTk.PhotoImage(Image.open("ITEXTRA.png"))
    label1=Label(top,image=my_img1).place(relx=0.01,rely=0.01)
    button2=Button(top,text="Close window",bg='#ff4a65',command=top.destroy, relief=GROOVE).place(relx=0.9,rely=0.9)
    # Marks window as used
    hiddenwindow=1
    pickle.dump(hiddenwindow, open("window.dat", "wb"))
    Button(root, text="Developer Options", padx=57, bg="#86b3b3",fg="black", command = hidden).grid(row=3,column=0)

def hide_window():
    root.withdraw()
    image=Image.open("screen.ico")
    menu=(item('Dev window', hidden),item('show window', show_window),item('Exit app', quit_window))
    icon=pystray.Icon("ITExtra", image, "Program", menu)
    icon.run()

def show_window(icon, item):
    icon.stop()
    root.after(0,root.deiconify())
    root.after(0,root.focus_force)


root = Tk()
root.title("ITextra")
root.geometry("400x400")
root.protocol('WM_DELETE_WINDOW', hide_window)
hidden()

root.mainloop()

But this unfortunately will not work, it won't pull up the toplevel window, nor the main one. If I then open the root window myself, the toplevel window will open, but be unresponsive.

EDIT Alright, so I tried adding the topwindow as class, but I keep getting error 'Top' object has no attribute 'tk'. I pasted the updated code below. Any help is always greatly appreciated!

from cmath import phase
from tkinter import *
from tkinter import messagebox, messagebox
from tracemalloc import start
from pystray import MenuItem as item
import pystray
from PIL import ImageTk,Image
import pickle


class Top():
    def __init__(self,master=None):
        self.hide = True

    def hidden(self):
      if self.hide:
        global my_img1
        self.top=Toplevel(root)
        self.top.title("Secret menu, shhh :^)")
        self.top.attributes('-alpha', 0.9)
            
        w = 1100
        h = 450
        ws = self.top.winfo_screenwidth()
        hs = self.top.winfo_screenheight()
        x = (ws/2) - (w/2)
        y = (hs/3) - (h/2)
        self.top.geometry('%dx%d+%d+%d' % (w, h, x, y))
        self.top.iconbitmap('screen.ico')
        my_img1 = ImageTk.PhotoImage(Image.open("ITEXTRA.png"))
        label1=Label(self.top,image=my_img1).place(relx=0.01,rely=0.01)
        button2=Button(self.top,text="Close window",bg='#ff4a65',command=self.top.destroy, relief=GROOVE).place(relx=0.9,rely=0.9)
        # Marks window as used
        hiddenwindow=1
        pickle.dump(hiddenwindow, open("window.dat", "wb"))
        self.top.mainloop()


def somewhereelse():
    top.hide = True
    top.hidden()

def quit_window(icon, item):
    icon.stop()
    root.destroy()
    exit()

def show_window(icon, item):
    icon.stop()
    root.after(0,root.deiconify())
    root.after(0,root.focus_force)

def hide_window():
    root.withdraw()
    image=Image.open("screen.ico")
    try:
        if pickle.load(open("window.dat","rb")) ==1:
            menu=(item('Dev window', top.hidden),
                    item('show window', show_window),
                    item('Exit app', quit_window))
        else:
            menu=(item('Exit app', quit_window))
    except:
        menu=(item('Exit app', quit_window))
    icon=pystray.Icon("ITextra", image, "Program", menu)
    icon.run()
    
    
root = Tk()
root.title("ITextra")
root.geometry("400x400")
top = Top(root) #in main part
root.protocol('WM_DELETE_WINDOW', hide_window)

Button(root, text="Developer Options", padx=57, bg="#86b3b3",fg="black", command =top.hidden).grid(row=3,column=0)



root.mainloop()


Top window still unresponsive It's not when root is open, but when top is open by itself, again it remains unresponsive. It responds however when I click a button, and drag my mouse. I tried adding a mainloop in top, but neither a self.top.mainloop nor a root.mainloop will work. I tried using binds, but they also showed the same behaviour.

Am I creating something that won't work?

The app I'm creating is multithreaded, and my question is; would this complicate things with other classes? I am very new to coding, and quite frankly don't know. I have the whole project in a pastebin here, for anyone who's interested. I think it's quite a mess, but I'm still pretty proud of it for a beginner.

Upvotes: 2

Views: 628

Answers (1)

InhirCode
InhirCode

Reputation: 348

The Toplevel() remains unresponsive because it has no event loop attached (mainloop()) because in this code the Toplevel acts as a standalone main window.

Need to attach this Toplevel to the root - top = Toplevel(root) where root is passed as argument to hidden(root). This way the root event loop works for all widget children such as a Toplevel. This would help towards your main part of the question.

(#added...) so there is no need for top.mainloop() because now that the root is the master/parent top is inside root.mainloop().

The event loop is for checking in to any events that happen on your widget which you would normally program with bind(). eg top.bind('<Button>',dosomething) where dosomething is a defined function. (...#added)

If you want a title for top then you need to create your own title baror label if you are using overrideredirect(True) because this removes the platform window manager.

(#added...) The platform window manager is not so much removed as it is not being used when using overrideredirect(True). This is probably another reason why your window seems unresponsive with this stage of code. Need to code for events attached to the widget yourself - as you have done with the Button widget to close. (...#added)

For main part of question: there is nothing that refers to top widget in show_window in this code.

(#added...) could look at making top a class and instantiate that in the root. The default status of hidden for top could be an attribute of this class. Then you can change the class attribute to hide or show functionally inside the body of the code somewhereelse.

eg skeleton sketch:

class Top():
    def __init__(self,master=None):
        ... 
        self.hide = True
        ...

    def hidden(self):
      if self.hide:
          ...

def somewhereelse():
    top.hide = true
    top.hidden()

top = Top(root) #in main part

!!! obviously very brief general idea that needs work here of a way to maintain your design which seems quite good to me. There are several ways to incorporate the Toplevel widget into the class but that digresses a bit from the original question. (...#added)

added 28Jan...

I recommend to study class more thoroughly rather than only putting in my example. But here is a bit more

class Top():
    def __init__(self,master=None):
        super().__init__()
        self.master = master 
        self.hide = True

    def hidden(self):
        ...
        self.top = Toplevel(self.master)
        ...

In my words, but please check Python docs, super().__init__() will call the initialising function of the inherited object which in this case goes back to self.master which is root and then back through to tk.__init__ which is called in Tk().

I recommend looking at the code __init__.py file in the Lib\tkinter\ folder in the Python download to get a good understanding of how tkinter works.

I think this is definitely achievable but might need a different GUI - agree it is an excellent start for a beginner and thus not really a mess!!

Using class is not essential to achieving what you want to do but classes are very useful for encapsulating an object so that any extra attributes and methods relevant to that object can be customised for your project. This makes further or future development easier.

...added 28Jan

Upvotes: 2

Related Questions