Search code examples
pythontkinter

Tkinter make overrideredirect window appear on top of other windows when clicked from taskbar


How to display a custom Tkinter window (with overrideredirect true) on top of other opened windows when clicking from the taskbar icon? My code below works (maximize not implemented yet) except when this Tkinter window is overlapped by other non-Tkinter windows. For instance, when this Tkinter window is situated below two windows (of other programs) and when called (by clicking the taskbar icon), it will take 2 clicks on that icon before this window will appear on top of those 2 windows. I want to bring my window automatically on top when its taskbar icon clicked.

I based my code from this answer and stitched this workaround for taskbar icon. I tried searching for any Tkinter code that deals on taskbar icon click event but found nothing.

import tkinter as tk
from tkinter import ttk

def get_pos(event):
    global xwin
    global ywin
    xwin = event.x
    ywin = event.y

def move_window(event):
    root.geometry(f'+{event.x_root - xwin}+{event.y_root - ywin}')

def quit():
    root.destroy()

#window contents
root = tk.Tk()
container = tk.Toplevel(root)
root.overrideredirect(True)
#default window dimension
root.geometry('350x150+200+200')
root.minsize(350, 150)
container.attributes("-alpha",0.0)

back_ground = "#2c2c2c"

#minimize btn binding
def onRootIconify(event): root.withdraw()
container.bind("<Unmap>", onRootIconify)
root.bind("<Unmap>", onRootIconify)
def onRootDeiconify(event): root.deiconify()
container.bind("<Map>", onRootDeiconify)
root.bind("<Map>", onRootDeiconify)


#title bar
title_bar = tk.Frame(root, bg=back_ground, bd=1,
                     highlightcolor=back_ground, 
                     highlightthickness=0)
#minimize btn
minimize_btn = tk.Button(title_bar, text='🗕', bg=back_ground, padx=5, pady=2, 
                         bd=0, font="bold", fg='white', width=2,
                         activebackground="red",
                         activeforeground="white", 
                         highlightthickness=0, 
                         command=lambda: container.wm_state('iconic'))

#maximize btn
maximize_btn = tk.Button(title_bar, text='🗖', bg=back_ground, padx=5, pady=2, 
                         bd=0, font="bold", fg='white', width=2,
                         activebackground="red",
                         activeforeground="white", 
                         highlightthickness=0, 
                         command=None)

#close btn
close_button = tk.Button(title_bar, text='🗙', bg=back_ground, padx=5, pady=2, 
                         bd=0, font="bold", fg='white', width=2,
                         activebackground="red",
                         activeforeground="white", 
                         highlightthickness=0, 
                         command= quit)

#window title
title_window = "Untitled window"
title_name = tk.Label(title_bar, text=title_window, font="Arial 12", bg=back_ground, fg="white")

#main area of the window
window = tk.Frame(root, bg="white", highlightthickness=1, highlightbackground=back_ground)

txt = tk.Label(window, bg='white', text="Prototype window").pack(anchor="center")

# pack the widgets
title_bar.pack(fill='x', side=tk.TOP)
title_name.pack(side='left', padx=5)
close_button.pack(side='right')
maximize_btn.pack(side=tk.RIGHT)
minimize_btn.pack(side=tk.RIGHT)

window.pack(fill='both', expand=True, side=tk.TOP)

# bind title bar motion to the move window function
title_bar.bind("<B1-Motion>", move_window)
title_bar.bind("<Button-1>", get_pos)
#workaround to enable window dragging on window title text
title_name.bind("<B1-Motion>", move_window)
title_name.bind("<Button-1>", get_pos)
minimize_btn.bind('<Enter>', lambda x: minimize_btn.configure(bg='#777777'))
minimize_btn.bind('<Leave>', lambda x: minimize_btn.configure(bg=back_ground))
maximize_btn.bind('<Enter>', lambda x: maximize_btn.configure(bg='#777777'))
maximize_btn.bind('<Leave>', lambda x: maximize_btn.configure(bg=back_ground))
close_button.bind('<Enter>', lambda x: close_button.configure(bg='red'))
close_button.bind('<Leave>',lambda x: close_button.configure(bg=back_ground))


root.mainloop()

The custom window:

Custom Tkinter window


Solution

  • After looking for related answers and recommendations of Coder, I finally solved my problem. In my solution, I used the technique from this answer. My finished code does not use any invisible Tkinter window and heavily utilizes ctypes.windll (hence my code is only limited to Windows). The logic for minimize, maximize, and close window are finished as well.

    I managed to solve the following:

    • No random taskbar icon glitches when minimizing/maximizing (results from wrapping invisible window)
    • No issues when bringing up the custom Tkinter window when behind other windows (like that of my problem of double-clicking the taskbar icon)
    #https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowpos
    #https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow
    
    import tkinter as tk
    from tkinter import ttk
    from ctypes import windll
    
    
    def set_appwindow():
        global hasstyle
        GWL_EXSTYLE=-20
        WS_EX_APPWINDOW=0x00040000
        WS_EX_TOOLWINDOW=0x00000080
        if not hasstyle:
            hwnd = windll.user32.GetParent(root.winfo_id())
            style = windll.user32.GetWindowLongW(hwnd, GWL_EXSTYLE)
            style = style & ~WS_EX_TOOLWINDOW
            style = style | WS_EX_APPWINDOW
            res = windll.user32.SetWindowLongW(hwnd, GWL_EXSTYLE, style)
            root.withdraw()
            root.after(10, lambda:root.wm_deiconify())
            hasstyle=True
    
    def get_pos(event):
        global xwin
        global ywin
        xwin = event.x
        ywin = event.y
    
    def move_window(event):
        global previousPosition
        root.geometry(f'+{event.x_root - xwin}+{event.y_root - ywin}')
        previousPosition = [root.winfo_x(), root.winfo_y()]
    
    def move_window_bindings(*args, status=True):
        if status == True:
            title_bar.bind("<B1-Motion>", move_window)
            title_bar.bind("<Button-1>", get_pos)
            title_name.bind("<B1-Motion>", move_window)
            title_name.bind("<Button-1>", get_pos)
        else:
            title_bar.unbind("<B1-Motion>")
            title_bar.unbind("<Button-1>")
            title_name.unbind("<B1-Motion>")
            title_name.unbind("<Button-1>")
    
    def quit():
        root.destroy()
    
    #reference: https://programtalk.com/python-examples/ctypes.windll.user32.ShowWindow/
    def minimize(hide=False):
        hwnd = windll.user32.GetParent(root.winfo_id())
        windll.user32.ShowWindow(hwnd, 0 if hide else 6)
    
    def maximizeToggle():
        global maximized
        global previousPosition
        if maximized == False:
            #maximize current window
            maximize_btn.config(text="❐")
            hwnd = windll.user32.GetParent(root.winfo_id())
            SWP_SHOWWINDOW = 0x40
            windll.user32.SetWindowPos(hwnd, 0, 0, 0, int(root.winfo_screenwidth()), int(root.winfo_screenheight()-48),SWP_SHOWWINDOW)
            maximized = True
            move_window_bindings(status=False)
        else:
            #restore down window
            maximize_btn.config(text="🗖")
            hwnd = windll.user32.GetParent(root.winfo_id())
            SWP_SHOWWINDOW = 0x40
            windll.user32.SetWindowPos(hwnd, 0, previousPosition[0], previousPosition[1], int(root.minsize()[0]), int(root.minsize()[1]),SWP_SHOWWINDOW)
            maximized = False
            move_window_bindings(status=True)
    
    
    #---------------------------------
    root = tk.Tk()
    root.overrideredirect(True)
    
    #window details
    maximized = False
    back_ground = "#2c2c2c"
    dimension = (300, 300)
    #------------------------------
    
    if len(dimension) == 0:
        #default window dimension
        x = (root.winfo_screenwidth()/2)-(350/2)
        y = (root.winfo_screenheight()/2)-(250)
        root.geometry(f'350x150+{int(x)}+{int(y)}')
        root.minsize(350, 150)
        dimension = (350, 150)
        previousPosition = [int(x), int(y)]
    
    else:
        x = (root.winfo_screenwidth()/2)-(dimension[0]/2)
        y = (root.winfo_screenheight()/2)-250
        root.geometry(f'{dimension[0]}x{dimension[1]}+{int(x)}+{int(y)}')
        root.minsize(dimension[0], dimension[1])
        previousPosition = [int(x), int(y)]
    
    
    
    
    #title bar
    title_bar = tk.Frame(root, bg=back_ground, bd=1,
                         highlightcolor=back_ground, 
                         highlightthickness=0)
    
    #window title
    title_window = "Untitled window"
    title_name = tk.Label(title_bar, text=title_window, 
                         font="Arial 12", bg=back_ground, fg="white")
    
    #minimize btn
    minimize_btn = tk.Button(title_bar, text='🗕', bg=back_ground, padx=5, pady=2, 
                             bd=0, font="bold", fg='white', width=2,
                             activebackground="red",
                             activeforeground="white", 
                             highlightthickness=0, 
                             command=minimize)
    
    #maximize btn
    maximize_btn = tk.Button(title_bar, text='🗖', bg=back_ground, padx=5, pady=2, 
                             bd=0, font="bold", fg='white', width=2,
                             activebackground="red",
                             activeforeground="white", 
                             highlightthickness=0, 
                             command=maximizeToggle)
    
    #close btn
    close_button = tk.Button(title_bar, text='🗙', bg=back_ground, padx=5, pady=2, 
                             bd=0, font="bold", fg='white', width=2,
                             activebackground="red",
                             activeforeground="white", 
                             highlightthickness=0, 
                             command= quit)
    
    #hover effect
    minimize_btn.bind('<Enter>', lambda x: minimize_btn.configure(bg='#777777'))
    minimize_btn.bind('<Leave>', lambda x: minimize_btn.configure(bg=back_ground))
    maximize_btn.bind('<Enter>', lambda x: maximize_btn.configure(bg='#777777'))
    maximize_btn.bind('<Leave>', lambda x: maximize_btn.configure(bg=back_ground))
    close_button.bind('<Enter>', lambda x: close_button.configure(bg='red'))
    close_button.bind('<Leave>',lambda x: close_button.configure(bg=back_ground))
    
    
    #main area of the window
    window = tk.Frame(root, bg="white", highlightthickness=1, highlightbackground=back_ground)
    
    txt = tk.Label(window, bg='white', text="Prototype window").pack(anchor="center")
    
    # pack the widgets
    title_bar.pack(fill='x', side=tk.TOP)
    title_name.pack(side='left', padx=5)
    close_button.pack(side='right')
    maximize_btn.pack(side=tk.RIGHT)
    minimize_btn.pack(side=tk.RIGHT)
    window.pack(fill='both', expand=True, side=tk.TOP)
    move_window_bindings(status=True)
    
    
    #ctype
    hasstyle = False
    root.update_idletasks()
    root.withdraw()
    set_appwindow()
    
    root.mainloop()
    

    Demo:

    Window demonstration

    If someone can transform my code into a classful syntax, please comment since I have a hard time in doing so hehe. Code optimizations are very welcomed.