【问题标题】:How to get rid of titlebar without removing icon in taskbar with Tkinter?如何在不使用 Tkinter 删除任务栏中的图标的情况下摆脱标题栏?
【发布时间】:2019-12-10 17:16:44
【问题描述】:

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

我使用 Tkinter* 制作了许多生产力应用程序,因此我研究了如何在我的应用程序中执行此操作。不幸的是,在 Tkinter 中摆脱标题栏的标准方法是禁用窗口管理器(使用 overridedirect(1))。这也摆脱了我想保留的任务栏图标。

换句话说,我想要得到的是 仍然保留这个:。

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

【问题讨论】:

    标签: python-3.x tkinter custom-titlebar


    【解决方案1】:

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

    我使用 Tkinter* 制作了很多生产力应用程序,因此我研究了如何在我的应用程序中执行此操作。

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

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

    当窗口最大化时,您可能看不到任务栏。但是您仍然可以使用 sizegrip 调整它的大小。我还不知道如何使用 overrideredirect 从窗口边框调整窗口大小,但 Sizegrip 工作得很好。

    现在关于set_appwindow 函数,这是 MSDN 所说的:

    Shell 会在应用程序时在任务栏上创建一个按钮 创建一个不属于自己的窗口。确保窗口按钮是 放置在任务栏上,使用 WS_EX_APPWINDOW 扩展样式。防止窗口按钮 被放置在任务栏上,使用 WS_EX_TOOLWINDOW 扩展样式。作为替代方案,您可以创建一个 隐藏窗口并使此隐藏窗口成为您可见的所有者 窗口。

    Complete reference here

    这似乎是一个很长的答案,但我希望它涵盖了您需要的所有内容并为您提供帮助。

    【讨论】:

    • 谢谢!感谢您的帮助。
    【解决方案2】:

    查看以下对我有用的代码:-

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

    【讨论】:

    • 我认为您应该解释一下这段代码是什么,为什么要使用它以及它的作用。
    【解决方案3】:

    function on_deiconify()中我们需要解绑之前的事件&lt;FocusIn&gt;以避免我们这样写的窗口blitting

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-12
      • 2012-02-14
      • 2012-08-04
      • 2021-10-27
      相关资源
      最近更新 更多