Python 3.x 如何使用Tkinter在不删除任务栏图标的情况下删除标题栏?

Python 3.x 如何使用Tkinter在不删除任务栏图标的情况下删除标题栏?,python-3.x,tkinter,custom-titlebar,Python 3.x,Tkinter,Custom Titlebar,所以,我最近正在查看VsCode,我注意到了一个有趣的特性。虽然有一个任务栏图标,但没有标题栏;相反,VsCode实现了自己的。我看了微软的一些其他程序,它们也做了同样的事情。我认为这是一个非常酷的功能 我用Tkinter*制作了很多提高效率的应用程序,所以我研究了如何在我的应用程序中实现这一点。不幸的是,清除Tkinter中标题栏的标准方法是禁用窗口管理器(使用overridedirect(1))。这也去掉了任务栏图标,我想保留它 换句话说,我想得到的是 但仍保留此: *作为参考,我使用的是P

所以,我最近正在查看VsCode,我注意到了一个有趣的特性。虽然有一个任务栏图标,但没有标题栏;相反,VsCode实现了自己的。我看了微软的一些其他程序,它们也做了同样的事情。我认为这是一个非常酷的功能

我用Tkinter*制作了很多提高效率的应用程序,所以我研究了如何在我的应用程序中实现这一点。不幸的是,清除Tkinter中标题栏的标准方法是禁用窗口管理器(使用
overridedirect(1)
)。这也去掉了任务栏图标,我想保留它

换句话说,我想得到的是 但仍保留此:


*作为参考,我使用的是Python 3.8和TkInter 8.6。

请查看它为我工作的以下代码:-

import tkinter as tk
import tkinter.ttk as ttk
from ctypes import windll

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

def set_appwindow(root):
    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.wm_withdraw()
    root.after(10, lambda: root.wm_deiconify())

def main():
    root = tk.Tk()
    root.wm_title("AppWindow Test")
    button = ttk.Button(root, text='Exit', command=lambda: root.destroy())
    button.place(x=10,y=10)
    root.overrideredirect(True)
    root.after(10, lambda: set_appwindow(root))
    root.mainloop()

if __name__ == '__main__':
    main()

您可以使用边框创建自己的按钮标题栏。来看看这个。我还开发了一个基于tkinter的应用程序,并使用@Hruthik Reddy提供的内容创建了这个应用程序

我用Tkinter*制作了很多提高效率的应用程序,所以我研究了如何在我的应用程序中实现这一点

假设您可能在这些应用程序中的某个时候使用过类,我创建了这个继承的
Tk
子类,并在注释中解释:

import tkinter as tk
import tkinter.ttk as ttk
from ctypes import windll


class TestApp(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)

        # set overrideredirect to True to remove the windows default decorators
        self.overrideredirect(True)

        self.geometry('700x500+10+10')  # you may or may not want to initialize the geometry of the window
        self.minsize(193, 109)

        # (x, y) coordinates from top left corner of the window
        self.x = None
        self.y = None

        # Create a frame that will contain the title label of the window
        self.frame = tk.Frame(self, bg='gray38')
        self.frame.pack(side=tk.TOP, fill=tk.X)

        # Label `name` for the window
        # Since buttons are on the right side and the name of the window is on the left side, the label will be packed towards LEFT side
        self.name = tk.Label(self.frame, text='Simple Text Box', font='Consolas 11',
                             bg=self.frame.cget('background'), fg='white')
        self.name.pack(side=tk.LEFT, fill=tk.X, anchor=tk.CENTER)

        # Pack the close button to the right-most side
        self.close = tk.Button(self.frame, text='✕', bd=0, width=3, font='Consolas 13',
                               command=self.destroy, bg=self.frame.cget('background'))
        self.close.pack(side=tk.RIGHT)

        # Pack the maximize button second from the right
        # The unicode string as the value of the keyword `text` below, is taken from the internet, it contains the maximize icon as unicode character
        self.maximize = tk.Button(self.frame, text=u"\U0001F5D6", bd=0, width=3, font='Consolas',
                                  command=self.maximize_win, bg=self.frame.cget('background'))
        self.maximize.pack(side=tk.RIGHT)

        # Pack the minimize button third from the right
        self.minimize = tk.Button(self.frame, text='—', bd=0, width=3, font='Consolas 13',
                                  command=self.minimize_win, bg=self.frame.cget('background'))
        self.minimize.pack(side=tk.RIGHT)

        # -------------------
        # NOW YOU CAN PUT WHATEVER WIDGETS YOU WANT AFTER THIS BUT FOR THIS EXAMPLE I
        # HAVE TAKEN A TEXTBOX WITH HORIZONTAL AND VERTICAL SCROLLBARS AND A SIZEGRIP
        # -------------------

        # The frame below contains the vertical scrollbar and the sizegrip (sizegrip helps in resizing the window
        self.scroll_frame = tk.Frame(self)
        v_scroll = tk.Scrollbar(self.scroll_frame, orient=tk.VERTICAL)
        h_scroll = tk.Scrollbar(self, orient=tk.HORIZONTAL)
        self.grip = ttk.Sizegrip(self.scroll_frame)

        # I am directly putting the textbox in the window, you may add frames and other stuff
        self.text = tk.Text(self, wrap=tk.NONE, yscrollcommand=v_scroll.set, xscrollcommand=h_scroll.set,
                            font='Consolas 14', width=1, height=1)

        # set the scrollbar for y and x views of the textbox respectively
        v_scroll.config(command=self.text.yview)
        h_scroll.config(command=self.text.xview)

        # Packing scrollbar frame, the scrollbars and the grip according to the arrangement I want
        self.scroll_frame.pack(side=tk.RIGHT, fill=tk.Y)
        v_scroll.pack(side=tk.TOP, fill=tk.Y, expand=tk.Y)
        self.grip.pack(side=tk.BOTTOM)
        self.text.pack(side=tk.TOP, expand=tk.TRUE, fill=tk.BOTH)
        h_scroll.pack(side=tk.BOTTOM, fill=tk.X)
        
        self.grip.bind("<B1-Motion>", self.onmotion)
        # Bind the motion of mouse after mouse click to the onmotion function for window resizing

        self.call('encoding', 'system', 'utf-8')

        # Binding `<Enter>` and `<Leave>` mouse event to their respective functions
        # `<Enter>` event is called when the mouse pointer enters any widget
        # `<Leave>` event is called when the mouse pointer leaves any widget
        # Here when the mouse pointer enters or leaves the buttons their color will change
        self.close.bind('<Enter>', lambda _: self.close.config(bg='red'))
        self.close.bind('<Leave>', lambda _: self.close.config(bg=self.frame.cget('background')))
        self.minimize.bind('<Enter>', lambda _: self.minimize.config(bg='gray58'))
        self.minimize.bind('<Leave>', lambda _: self.minimize.config(bg=self.frame.cget('background')))
        self.maximize.bind('<Enter>', lambda _: self.maximize.config(bg='gray58'))
        self.maximize.bind('<Leave>', lambda _: self.maximize.config(bg=self.frame.cget('background')))

        # Now you may want to move your window (obviously), so the respective events are bound to the functions
        self.frame.bind("<ButtonPress-1>", self.start_move)
        self.frame.bind("<ButtonRelease-1>", self.stop_move)
        self.frame.bind("<B1-Motion>", self.do_move)
        self.frame.bind('<Double-1>', self.maximize_win)
        self.name.bind("<ButtonPress-1>", self.start_move)
        self.name.bind("<ButtonRelease-1>", self.stop_move)
        self.name.bind("<B1-Motion>", self.do_move)
        self.name.bind('<Double-1>', self.maximize_win)

    def start_move(self, event):
        """ change the (x, y) coordinate on mousebutton press and hold motion """
        self.x = event.x
        self.y = event.y

    def stop_move(self, event):
        """ when mouse button is released set the (x, y) coordinates to None """
        self.x = None
        self.y = None

    def do_move(self, event):
        """ function to move the window """
        self.wm_state('normal')  # if window is maximized, set it to normal (or resizable)
        self.maximize.config(text=u"\U0001F5D6")  # set the maximize button text to the square character of maximizing window
        deltax = event.x - self.x
        deltay = event.y - self.y
        x = self.winfo_x() + deltax
        y = self.winfo_y() + deltay
        self.geometry(f"+{x}+{y}")

    def onmotion(self, event):
        """ function to change window size """
        self.wm_state('normal')
        self.maximize.config(text=u"\U0001F5D6")
        x1 = self.winfo_pointerx()
        y1 = self.winfo_pointery()
        x0 = self.winfo_rootx()
        y0 = self.winfo_rooty()
        self.geometry("%sx%s" % ((x1-x0), (y1-y0)))
        return

    def minimize_win(self, event=None):
        """ function to iconify or minimize window as an icon """
        self.overrideredirect(False)
        self.wm_iconify()
        self.bind('<FocusIn>', self.on_deiconify)

    def maximize_win(self, event=None):
        """ function to maximize window or make it normal (exit maximize) """
        if self.maximize.cget('text') == u"\U0001F5D7":
            self.wm_state('normal')
            self.maximize.config(text=u"\U0001F5D6")
            return
        self.wm_state('zoomed')
        self.maximize.config(text=u"\U0001F5D7")

    def on_deiconify(self, event):
        """ function to deiconify or window """
        self.overrideredirect(True)
        set_appwindow(root=self)


def set_appwindow(root):
    hwnd = windll.user32.GetParent(root.winfo_id())
    style = windll.user32.GetWindowLongPtrW(hwnd, GWL_EXSTYLE)
    style = style & ~WS_EX_TOOLWINDOW
    style = style | WS_EX_APPWINDOW
    res = windll.user32.SetWindowLongPtrW(hwnd, GWL_EXSTYLE, style)
    # re-assert the new window style
    root.wm_withdraw()
    root.after(10, lambda: root.wm_deiconify())


if __name__ == '__main__':
    GWL_EXSTYLE = -20
    WS_EX_APPWINDOW = 0x00040000
    WS_EX_TOOLWINDOW = 0x00000080

    app = TestApp()
    # print(app.tk.call('tk', 'windowingsystem'))
    # # Here root.tk.call('tk', 'windowingsystem') calls tk windowingsystem in Tcl, and that returns 'win32',
    # # 'aqua' or 'x11' as documented in tk
    app.after(10, lambda: set_appwindow(root=app))
    app.text.insert(1.0, 'Drag the window using the title or the empty area to the right of the\ntitle.'
                         ' Try maximizing / minimizing.\n\n-- YOU MAY HAVE A PROBLEM WITH RESIZING --\n'
                         '-- ALSO IF YOU REMOVE `height` AND `width` KEYWORDS FROM THE TEXTBOX DECLARATION'
                         ' AND FONT SIZE IS TOO BIG THE SCROLLBAR MAY DISAPPEAR --\nSO KEEP THOSE KEYWORDS THERE!')
    app.mainloop()

将tkinter作为tk导入
将tkinter.ttk导入为ttk
从ctypes导入Windell
类TestApp(tk.tk):
定义初始化(自):
tk.tk.\uuuuu初始化(self)
#将overrideredirect设置为True以删除windows默认装饰程序
self.overrideredirect(True)
self.geometry('700x500+10+10')#您可能希望也可能不希望初始化窗口的几何图形
self.minsize(193109)
#从窗口左上角开始的(x,y)坐标
self.x=无
self.y=None
#创建包含窗口标题标签的框架
self.frame=tk.frame(self,bg='gray38')
self.frame.pack(side=tk.TOP,fill=tk.X)
#窗口的标签'name'
#由于按钮位于右侧,窗口名称位于左侧,标签将朝左侧包装
self.name=tk.Label(self.frame,text='Simple text Box',font='Consolas 11',
bg=self.frame.cget('background'),fg='white')
self.name.pack(side=tk.LEFT,fill=tk.X,anchor=tk.CENTER)
#将关闭按钮装到最右侧
self.close=tk.Button(self.frame,text=)✕', bd=0,width=3,font='Consolas 13',
command=self.destroy,bg=self.frame.cget('background'))
自关闭组件(侧面=右侧)
#从右数第二秒打包最大化按钮
#unicode字符串作为下面的关键字“text”的值,取自internet,它包含unicode字符形式的最大化图标
self.maximize=tk.Button(self.frame,text=u“\U0001F5D6”,bd=0,width=3,font='Consolas',
command=self.maximize\u win,bg=self.frame.cget('background'))
self.maximize.pack(侧面=右侧)
#将最小化按钮从右侧第三个位置打包
self.minimize=tk.Button(self.frame,text='-',bd=0,width=3,font='Consolas 13',
command=self.minimize\u win,bg=self.frame.cget('background'))
自我最小化包装(侧面=右侧)
# -------------------
#现在你可以把任何你想要的小部件放在后面,但是对于这个例子,我
#使用带有水平和垂直滚动条以及SIZEGRIP的文本框
# -------------------
#下面的框架包含垂直滚动条和sizegrip(sizegrip有助于调整窗口大小)
self.scroll\u frame=tk.frame(self)
v_scroll=tk.Scrollbar(self.scroll_框架,orient=tk.VERTICAL)
h_scroll=tk.滚动条(自,方向=tk.水平)
self.grip=ttk.Sizegrip(self.scroll\u框架)
#我直接将文本框放在窗口中,您可以添加框架和其他内容
self.text=tk.text(self,wrap=tk.NONE,yscrollcommand=v_scroll.set,xscrollcommand=h_scroll.set,
font='Consolas 14',宽度=1,高度=1)
#分别为文本框的y和x视图设置滚动条
v_scroll.config(命令=self.text.yview)
h_scroll.config(命令=self.text.xview)
#包装滚动条框架,滚动条和把手根据我想要的安排
自滚动框架包装(侧边=tk.RIGHT,填充=tk.Y)
v_scroll.pack(side=tk.TOP,fill=tk.Y,expand=tk.Y)
自夹式包装(侧面=底部)
self.text.pack(side=tk.TOP,expand=tk.TRUE,fill=tk.BOTH)
h_滚动包装(侧面=底部,填充=底部)
self.grip.bind(“,self.onmotion)
#将鼠标单击后的移动绑定到onmotion函数以调整窗口大小
self.call('encoding'、'system'、'utf-8')
#将``和``鼠标事件绑定到各自的函数
#当鼠标指针进入任何小部件时,将调用`event
#当鼠标指针离开任何小部件时,将调用`event
#在这里,当鼠标指针进入或离开按钮时,按钮的颜色将发生变化
self.close.bind(“”,lambda_979;:self.close.config(bg='red'))
self.close.bind(“”,lambda quo;:self.close.config(bg=self.frame.cget('background'))
self.minimize.bind(“”,lambda_979;:self.minimize.config(bg='gray58'))
self.minimize.bind(“”,lambda quo;:self.minimize.config(bg=self.frame.cget('background'))
self.maximize.bind(“”,lambda_979;:self.maximize.config(bg='gray58'))
self.maximize.bind(“”,lambda 979;:self.maximize.config(bg=self.frame.cget('background'))
#现在您可能需要移动窗口(显然),以便将相应的事件绑定到函数
self.frame.bind(“,self.start\u move)
self.frame.bind(“,self.stop\u move)
自我框架