Search code examples
pythontkintermenutopmost

Tkinter menu bar goes invisible when checking topmost attribute


I have a larger tkinter app that I wanted to dinamically set the topmost attribute. I am able to achieve what I want, but everytime I check the state of topmost, the selected menu bar on the screen goes invisible.

To reproduce this, consider the MRE below and upon running the code, click the "menu" button on the menu bar, the cascade opens and the exit button shows, watch it vanish after the check_topmost function runs, but the "menu" button is still pressed somehow.

Commenting out either the line that checks the attribute or the line that sets it as True stops the behaviour

import tkinter as tk

def check_topmost():
    print(app.attributes('-topmost')) # comment this
    app.after(1000, check_topmost)

app = tk.Tk()
menu_bar = tk.Menu(app)
sub_menu = tk.Menu(menu_bar, tearoff=0)
sub_menu.add_command(label = 'exit', command = app.destroy)
menu_bar.add_cascade(label = "menu", menu = sub_menu)
app.config(menu = menu_bar)
app.attributes('-topmost', 1) # comment this
app.after(1000, check_topmost)
app.mainloop()

What am I doing wrong?


Solution

  • This is not a fix for your issue, but rather a workaround. It seems like the topmost attribute is really weird... First, it is platform specific. Second, once set, it doesn't just change. If you set "-topmost", 1 for another window both will have the same attribute. From the documentation I found "-topmost gets or sets whether this is a topmost window". I assume, that once you call the attribute, it not only checks the attribute, but reassigns the value, therefore giving you the weird behavior with the drop-down menu. Interestingly, if you get all attributes at the same time by app.attributes(), your drop-down menu is not affected. So you can use this as workaround to check the attribute and only set it if necessary. Most likely, this is not the best solution, but it is the best I could come up with.

    import tkinter as tk
    
    
    def set_topmost():
        app2.attributes('-topmost', 1)
        print('window 1', app.attributes())
        print('window 2', app2.attributes())  # as you can see both windows can have the same topmost attribute
        app.attributes('-topmost', 0)  # try the behavior after commenting this out
    
    
    def check_topmost():
        print(app.attributes())  # this does not affect your window/drop-down menu
        att = str(app.attributes())  # making it a string as workaround
    
        if "'-topmost', 1" in att:
            print('already set as topmost')
        else:
            print('switch topmost')
            app.attributes('-topmost', 1)  # this will still disrupt your drop down menu
    
        app.after(5000, check_topmost)  # i set it to 5 seconds to better see the effects
    
    
    app = tk.Tk()
    menu_bar = tk.Menu(app)
    sub_menu = tk.Menu(menu_bar, tearoff=0)
    sub_menu.add_command(label = 'exit', command = app.destroy)
    menu_bar.add_cascade(label = "menu", menu = sub_menu)
    app.config(menu = menu_bar)
    app.attributes('-topmost', 1) # comment this
    
    app2 = tk.Toplevel()
    app2.geometry('500x500')
    
    btn = tk.Button(app, text='set topmost', command=set_topmost)
    btn.pack()
    
    app.after(1000, check_topmost)
    app.mainloop()