Search code examples
pythonwindowstkintercombobox

Dropdown list of ttk.Combobox remains visible after a new window becomes active


After clicking on the combobox, the dropdown list is shown. Wait for 2 seconds and a browser page becomes active and find that the dropdown list remains visible while the test application itself is hidden.

import tkinter as tk
import webbrowser
from tkinter import ttk

root = tk.Tk()
root.geometry("300x200")

combo = ttk.Combobox(root, state="readonly")
combo['values'] = ["Option 1", "Option 2", "Option 3"]
combo.bind('<Button-1>', lambda event: root.after(2000, lambda: webbrowser.open("https://stackoverflow.com")))
combo.pack()

root.mainloop()

With Python 3.12.4

I expect the dropdown disappears along with the test application itself. Also I'd like to receive <FocusOut> event for root.


Solution

  • The problem was that the dropdown list you see is actually a separate window. if you look at its attributes using the "attributes" command, you will see the following:

    -alpha 1.0 -transparentcolor {} -disabled 0 -fullscreen 0 -toolwindow 0 -topmost 1

    So your window always appears on top of all other windows because topmost is set to 1. The solution is obvious: set "-topmost" to 0 when creating the dropdown list so that it doesn't appear on top of everything. You also need to make sure that if it is unmapped, you close it. So, the code:

    import tkinter as tk
    from tkinter import ttk
    import webbrowser
    
    root = tk.Tk()
    root.geometry("300x200")
    
    combo = ttk.Combobox(root, state="readonly")
    combo['values'] = ["Option 1", "Option 2", "Option 3", "Option 4", "Option 5", "Option 6"]
    combo.bind('<Button-1>', lambda event: root.after(2000, lambda: webbrowser.open("https://stackoverflow.com")))
    combo.pack()
    
    
    def on_widget_map(event: tk.Event):
        root.eval(f"wm attributes {event.widget} -topmost 0")
    
    
    root.bind_class("ComboboxPopdown", '<Map>', on_widget_map, add='+')
    
    root.mainloop()
    

    Now you can see that the above code may still not work completely. I think what can be done is to regularly check if the dropdown is below the root. If so, then close it. Updated code:

    ...
    combo.pack()
    
    
    def close_if_not_visible(dropdown_list):
        if root.eval(f"winfo ismapped {dropdown_list}") == '0':
            return
        if root.eval(f"wm stackorder {dropdown_list} isabove .") == '1':
            root.after(50, lambda: close_if_not_visible(dropdown_list))
        else:
            root.focus_set()
    
    
    def on_widget_map(event: tk.Event):
        root.eval(f"wm attributes {event.widget} -topmost 0")
        root.bind_class(event.widget, '<Unmap>', lambda _e: root.focus_set())
        close_if_not_visible(event.widget)
    
    
    root.bind_class("ComboboxPopdown", '<Map>', on_widget_map, add='+')
    
    root.mainloop()
    

    If you have any questions about the code or something is still not working properly, feel free to ask.