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.
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