Coder
Coder

Reputation: 92

App's icon is not show in taskbar because of Custom title bar?

I am creating an code editor in which I want to custom title bar to match my app theme and I have created an custom title bar but my app is not showing in taskbar

If any external libraries are for this, Please tell me

What libraries I have to learn to solve my problem please tell me

how to show app icon on taskbar, Actually I have no idea about it if you can solve it Please help me to solve my problem

this is my full code(not full code but short version of real one):-

from tkinter import*
def move(e):
        xwin = root.winfo_x()
        ywin = root.winfo_y()
        startx = e.x_root
        starty = e.y_root
        ywin -= starty
        xwin -= startx
        def move_(e):
            root.geometry(f"+{e.x_root + xwin}+{e.y_root + ywin}")
        startx = e.x_root
        starty = e.y_root
        frame.bind("<B1-Motion>",move_)
def minieme1_(event=None):
        root.update_idletasks()
        root.overrideredirect(False)
        root.state("iconic")
def frame_map(event=None):
        root.update_idletasks()
        root.overrideredirect(True)
        root.state("normal")
        root.call()
def minimefunction(event=None):
        global size
        if size:
            root.geometry(f"{screen_width}x{screen_height-40}+0+0")
            minimsi.config(text=" \u2752 ")
            size = False
        else:
            root.geometry(f"{app_width}x{app_height}+{int(x)}+{int(y)}")
            minimsi.config(text=" \u25a0 ")
            size = True
            
def quitApp():
    root.destroy()
def close_blink(event=None):
    close_button.config(bg="red")
def close_blink1(event=None):
    close_button.config(bg="gray19")
def minimsi_blink(event=None):
    minimsi.config(bg="gray29")
def minimsi_blink1(event=None):
    minimsi.config(bg="gray19")
def minimsi1_blink(event=None):
    minimsi1.config(bg="gray29")
def minimsi1_blink1(event=None):
    minimsi1.config(bg="gray19")
root = Tk()
size = True
app_width = 600
app_height = 500
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
print(screen_width,screen_height)
x = (screen_width/2) - (app_width/2)
y = (screen_height/2) - (app_height/2)
root.geometry(f"{app_width}x{app_height}+{int(x)}+{int(y)}")
root.overrideredirect(True)
frame = Frame(root,bg="gray29")

Label(frame,text="My App",font="Consolas 15",bg="gray29",fg="white").pack(side=LEFT,padx=10)
close_button = Button(frame,text=" X ",font="Consolas 15",bg="gray19",fg="white",relief=GROOVE,borderwidth=0,command=quitApp)
close_button.pack(side=RIGHT)
minimsi = Button(frame,text=" \u25a0 ",font="Consolas 15",bg="gray19",fg="white",relief=GROOVE,borderwidth=0,command=minimefunction)
minimsi.pack(side=RIGHT)
minimsi1 = Button(frame,text=" - ",font="Consolas 15",bg="gray19",fg="white",relief=GROOVE,borderwidth=0,command=minieme1_)
minimsi1.pack(side=RIGHT)
frame.pack(fill=X)

yscroll = Scrollbar(orient=VERTICAL)
yscroll.pack(side=RIGHT,fill=Y)
editor = Text(font="Consolas 15",bg="gray19",fg="white",insertbackground="white",borderwidth=0,yscrollcommand=yscroll.set)
yscroll.config(command=editor.yview)
editor.pack(expand=True,fill=BOTH)
root.config(bg="gray19")

frame.bind("<Button-1>",move)
frame.bind("<B1-Motion>",move)
# minimsi1.bind("<Button-1>",minieme1_)
frame.bind("<Map>",frame_map)
close_button.bind("<Enter>",close_blink)
close_button.bind("<Leave>",close_blink1)
minimsi.bind("<Enter>",minimsi_blink)
minimsi.bind("<Leave>",minimsi_blink1)
minimsi1.bind("<Enter>",minimsi1_blink)
minimsi1.bind("<Leave>",minimsi1_blink1)


root.mainloop()


You can see the problem in this image:- enter image description here

Upvotes: 4

Views: 1609

Answers (3)

acw1668
acw1668

Reputation: 47193

You can use a hidden root window to let the system window manager to show an icon in the taskbar, and make the custom window as a transient child window of the hidden root window in order to simulate those iconify and deiconify effect.

You need to bind <Button> event on the custom window so to bring it to the front when it is clicked. Also need to bind <FocusIn> event on the hidden root window to move the focus to the custom window instead.

Below is the required changes:

...
def minieme1_(event=None):
    # iconify hidden root window will iconify the custom window as well
    hidden_root.iconify()
...
def quitApp():
    # destroy the hidden root window will destroy the custom window as well
    hidden_root.destroy()
...

def on_focus(event):
    # bring custom window to front
    root.lift()

# create a hidden root window
hidden_root = Tk()
hidden_root.attributes('-alpha', 0)
hidden_root.title('My App')
# you can use hidden_root.iconbitmap(...) or hidden_root.iconphoto(...) to set the icon image

# use Toplevel instead of Tk for the custom window
root = Toplevel(hidden_root)
# make it a transient child window of hidden root window
root.transient(hidden_root)

root.bind('<Button>', on_focus)
hidden_root.bind('<FocusIn>', on_focus)
...
#frame.bind("<Map>",frame_map) # it is not necessary and frame_map() is removed as well
...

Upvotes: 2

Thingamabobs
Thingamabobs

Reputation: 8072

Disclaimer, this may not be the best approach to achieve OP`s goal, but I'm sticking to this example because:

  • There are several question with this example on StackOverflow
  • It shows a basic knowledge that is important for deeper digging.

The improvement of this code to the original is that the style applies again after you iconify it and bring that windows back up.

See the twitch of this example is how the taskbar keeps track of the application. There is a good article of Raymond Chen (Ms-Developer) where he quotes this:

“If you want to dynamically change a window’s style to one that doesn’t support visible taskbar buttons, you must hide the window first (by calling ShowWindow with SW_HIDE), change the window style, and then show the window.”

And also points out a weak point of some programs and why they lose or get a blank taskbar icon:

  • Window is taskbar-eligible.
  • Window becomes visible ? taskbar button created.
  • Window goes taskbar-ineligible.
  • Window becomes hidden ? since the window is not taskbar-eligible at this point, the taskbar ignores it.

Anyway, the basic idea is the following:

  1. Get the handle of the window. (Parent window of the root in this case for tkinter related reasons)
  2. get the current style in a hexadecimal code
  3. alter the hexadecimal code to your need with a bitwise operation
  4. apply the altered style to the window

I added the following code at the beginning of your script:

from ctypes import windll

GWL_EXSTYLE=-20
WS_EX_APPWINDOW=0x00040000
WS_EX_TOOLWINDOW=0x00000080

def set_appwindow():
    global hasstyle
    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(100, lambda:root.wm_deiconify())
        hasstyle=True

This code at the end of your script:

hasstyle = False
root.update_idletasks()
root.withdraw()
set_appwindow()

and added the line set_appwindow() in def frame_map(event=None):. Als I had to implement another additional two lines in def minieme1_(event=None): to update the hasstylevariable.

So the overall implementation of this approach was possible to have your window ready and withdrawn for described reason. An additional variable to avoid a infinite loop while you alter the style of your window that is either True while it is shown or False while it is iconyfied, alongside with your overrideredirect method.

Full Code

from tkinter import*
from ctypes import windll

GWL_EXSTYLE=-20
WS_EX_APPWINDOW=0x00040000
WS_EX_TOOLWINDOW=0x00000080

def set_appwindow():
    global hasstyle
    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(100, lambda:root.wm_deiconify())
        hasstyle=True
    
def move(e):
    xwin = root.winfo_x()
    ywin = root.winfo_y()
    startx = e.x_root
    starty = e.y_root
    ywin -= starty
    xwin -= startx
    def move_(e):
        root.geometry(f"+{e.x_root + xwin}+{e.y_root + ywin}")
    startx = e.x_root
    starty = e.y_root
    frame.bind("<B1-Motion>",move_)
def minieme1_(event=None):
    global hasstyle
    root.update_idletasks()
    root.overrideredirect(False)
    root.state("iconic")
    hasstyle = False
def frame_map(event=None):
    root.overrideredirect(True)
    root.update_idletasks()
    set_appwindow()
    root.state("normal")
def minimefunction(event=None):
    global size
    if size:
        root.geometry(f"{screen_width}x{screen_height-40}+0+0")
        minimsi.config(text=" \u2752 ")
        size = False
    else:
        root.geometry(f"{app_width}x{app_height}+{int(x)}+{int(y)}")
        minimsi.config(text=" \u25a0 ")
        size = True
            
def quitApp():
    root.destroy()
def close_blink(event=None):
    close_button.config(bg="red")
def close_blink1(event=None):
    close_button.config(bg="gray19")
def minimsi_blink(event=None):
    minimsi.config(bg="gray29")
def minimsi_blink1(event=None):
    minimsi.config(bg="gray19")
def minimsi1_blink(event=None):
    minimsi1.config(bg="gray29")
def minimsi1_blink1(event=None):
    minimsi1.config(bg="gray19")
root = Tk()
size = True
app_width = 600
app_height = 500
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
print(screen_width,screen_height)
x = (screen_width/2) - (app_width/2)
y = (screen_height/2) - (app_height/2)
root.geometry(f"{app_width}x{app_height}+{int(x)}+{int(y)}")
root.overrideredirect(True)
frame = Frame(root,bg="gray29")

Label(frame,text="My App",font="Consolas 15",bg="gray29",fg="white").pack(side=LEFT,padx=10)
close_button = Button(frame,text=" X ",font="Consolas 15",bg="gray19",fg="white",relief=GROOVE,borderwidth=0,command=quitApp)
close_button.pack(side=RIGHT)
minimsi = Button(frame,text=" \u25a0 ",font="Consolas 15",bg="gray19",fg="white",relief=GROOVE,borderwidth=0,command=minimefunction)
minimsi.pack(side=RIGHT)
minimsi1 = Button(frame,text=" - ",font="Consolas 15",bg="gray19",fg="white",relief=GROOVE,borderwidth=0,command=minieme1_)
minimsi1.pack(side=RIGHT)
frame.pack(fill=X)

yscroll = Scrollbar(orient=VERTICAL)
yscroll.pack(side=RIGHT,fill=Y)
editor = Text(font="Consolas 15",bg="gray19",fg="white",insertbackground="white",borderwidth=0,yscrollcommand=yscroll.set)
yscroll.config(command=editor.yview)
editor.pack(expand=True,fill=BOTH)
root.config(bg="gray19")

frame.bind("<Button-1>",move)
frame.bind("<B1-Motion>",move)
# minimsi1.bind("<Button-1>",minieme1_)
frame.bind("<Map>",frame_map)
close_button.bind("<Enter>",close_blink)
close_button.bind("<Leave>",close_blink1)
minimsi.bind("<Enter>",minimsi_blink)
minimsi.bind("<Leave>",minimsi_blink1)
minimsi1.bind("<Enter>",minimsi1_blink)
minimsi1.bind("<Leave>",minimsi1_blink1)

hasstyle = False
root.update_idletasks()
root.withdraw()
set_appwindow()


root.mainloop()

Tested with python 3.10 and windows 11

Upvotes: 1

JRiggles
JRiggles

Reputation: 6820

The short answer is: you can't do this purely with Tkinter. You can set the window's icon via self.iconbitmap(path_to_icon), but in order to have a unique taskbar icon, you'll need to compile your app into a Windows executable.

See here

Edit: Unrelated, but as a matter of practice it's best to avoid star imports, e.g. from tkinter import * - it's much better to use something like import tkinter as tk and then prefix your Tkinter objects with tk. to avoid namespace pollution!

Upvotes: 0

Related Questions