简体   繁体   中英

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:

自定义 Tkinter 窗口

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://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowpos
#https://docs.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:

橱窗示范

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM