由于自定义标题栏,应用程序图标未在任务栏中显示?

4
我正在创建一个代码编辑器,我想自定义标题栏以匹配我的应用程序主题,并且我已经创建了一个自定义标题栏,但是我的应用程序没有显示在任务栏中。
如果有任何外部库可以做到这一点,请告诉我。
请告诉我要学习哪些库来解决我的问题。
如何在任务栏上显示应用程序图标,实际上我对此一无所知。如果您能解决它,请帮助我解决我的问题。
以下是我的完整代码(不是真正的完整代码,而是简短版本):
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()


您可以从这张图片中看到问题:

enter image description here

1
通用方法。一种Windows特定的方法。 - Thingamabobs
3个回答

1
免责声明,这可能不是实现OP目标的最佳方法,但我坚持使用this example,因为:
  • 在StackOverflow上有几个关于此示例的问题
  • 它展示了重要的基础知识,有助于进行更深入的挖掘。

这段代码相对于原始代码的改进在于,当你将其图标化并将窗口再次打开时,样式会再次应用。

这个示例的亮点在于任务栏如何跟踪应用程序。Raymond Chen(Ms-Developer)有一篇好文章引用了这个示例:

“如果您想动态更改窗口的样式以使用不支持可见任务栏按钮的样式,则必须首先隐藏窗口(通过调用ShowWindow与SW_HIDE),然后更改窗口样式,然后显示窗口。”

他还指出了一些程序的弱点,以及它们为什么会丢失或获得空白的任务栏图标:

窗口可以出现在任务栏上。
当窗口变得可见时,任务栏按钮就会被创建。
窗口不再出现在任务栏上。
窗口隐藏后,由于此时窗口不再是任务栏的对象,因此任务栏会忽略它。
无论如何,基本思路如下:
  1. 获取窗口的句柄。(在这种情况下,为 tkinter 相关原因,父窗口的根)
  2. 获取当前样式十六进制代码
  3. 使用位运算来更改十六进制代码以满足需要
  4. 将更改后的样式应用到窗口上
我在您的脚本开头添加了以下代码:
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

你的脚本末尾的代码:
hasstyle = False
root.update_idletasks()
root.withdraw()
set_appwindow()

def frame_map(event=None):中添加了一行set_appwindow()。由于我还需要在def minieme1_(event=None):中实现另外两行代码来更新hasstyle变量,因此整个方法的实现是可能的,以便让您的窗口准备好并为描述的原因而撤回。除此之外,还有一个额外的变量来避免无限循环,当您更改窗口的样式时,它要么是True(显示状态),要么是False(图标化状态),同时使用您的overrideredirect方法。

完整代码

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()

使用 Python 3.10 和 Windows 11 进行测试。

1
您可以使用隐藏的根窗口让系统窗口管理器在任务栏中显示图标,并将自定义窗口作为隐藏根窗口的瞬态子窗口,以模拟缩小和放大的效果。
需要在自定义窗口上绑定<Button>事件,以便在单击时将其置于前台。还需要在隐藏的根窗口上绑定<FocusIn>事件,以将焦点转移到自定义窗口。
以下是所需更改:
...
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
...

0

简短的回答是:你不能仅使用Tkinter来实现这个功能。你可以通过self.iconbitmap(path_to_icon)设置窗口的图标,但是为了拥有一个独特的任务栏图标,你需要将你的应用程序编译成Windows可执行文件。

请参见这里

编辑:虽然与本题无关,但作为一种惯例最好避免星号导入,例如from tkinter import * - 最好使用import tkinter as tk,然后在Tkinter对象前加上tk.以避免命名空间污染!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接