Can I change the title bar in Tkinter?
Asked Answered
R

11

20

I'm using Tkinter as GUI for my program, but as I see, many programs don't have standard look as Tkinter does. By standard look I mean standard title bar, borders, etc.

For example, Tkinter's title bar:

https://static.mcmap.net/file/mcmap/ZG-Ab5ovK1BvZRlAKmfwXe/get/img/1a343ad92cd8c8f19ce3ca9c27afecba.jpg

vs GitHub's title bar:

https://static.mcmap.net/file/mcmap/ZG-Ab5ovK1BvZRlAKmfwXe/get/img/cf5cef0eeae5dcdc02f450733fd87508.jpg

See how they have their own custom exit, resize and minimize buttons? Is it possible to achieve that look using Tkinter?

Thanks in advance! :)

Ropedancer answered 23/5, 2014 at 18:19 Comment(0)
H
29

Yes it's possible. You can use the overrideredirect() method on the root window to kill the title bar and the default geometry settings. After that, you need to rebuild all those methods from scratch to set it back up like you want. Here's a small working example with minimal functionality:

root = Tk()

def move_window(event):
    root.geometry('+{0}+{1}'.format(event.x_root, event.y_root))

root.overrideredirect(True) # turns off title bar, geometry
root.geometry('400x100+200+200') # set new geometry

# make a frame for the title bar
title_bar = Frame(root, bg='white', relief='raised', bd=2)

# put a close button on the title bar
close_button = Button(title_bar, text='X', command=root.destroy)

# a canvas for the main area of the window
window = Canvas(root, bg='black')

# pack the widgets
title_bar.pack(expand=1, fill=X)
close_button.pack(side=RIGHT)
window.pack(expand=1, fill=BOTH)

# bind title bar motion to the move window function
title_bar.bind('<B1-Motion>', move_window)

root.mainloop()
Hardboiled answered 23/5, 2014 at 18:46 Comment(5)
It works, but my program isn't showing in Windows's start bar nor in processes? When I minimize it, it actually completely disappears (I can't atl+tab it)...Photogenic
Check this out: #4066527Hardboiled
Hmm, looks like it works, but is there any way to change "right click to exit both apps" (self.bind('<ButtonRelease-3>', self.on_close)) to whenever main is exited other one exits too?Photogenic
Yeah. Google protocol wm_delete_window to find some info, examples, etcHardboiled
Nvm I've done it using root.destroy() and app.destroy()! Thank you very much!Photogenic
K
10

Most will know there is an error when using the 'move_window' method used above; I found a fix that gets the exact position of the mouse and moves with that rather than from the corner:

    def get_pos(event):
        xwin = app.winfo_x()
        ywin = app.winfo_y()
        startx = event.x_root
        starty = event.y_root

        ywin = ywin - starty
        xwin = xwin - startx


        def move_window(event):
            app.geometry("400x400" + '+{0}+{1}'.format(event.x_root + xwin, event.y_root + ywin))
        startx = event.x_root
        starty = event.y_root


        app.TopFrame.bind('<B1-Motion>', move_window)
    app.TopFrame.bind('<Button-1>', get_pos)
Kingfish answered 12/2, 2018 at 1:2 Comment(1)
Works perfectly for me, replace the hard coded screen dimensions by root.winfo_width() and root.winfo_height() to fit every script.Naucratis
S
10

I found a way of making the title bar black using ctypes: (win11 only)

Tkinter dark title bar example:

import ctypes as ct


def dark_title_bar(window):
    """
    MORE INFO:
    https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
    """
    window.update()
    DWMWA_USE_IMMERSIVE_DARK_MODE = 20
    set_window_attribute = ct.windll.dwmapi.DwmSetWindowAttribute
    get_parent = ct.windll.user32.GetParent
    hwnd = get_parent(window.winfo_id())
    rendering_policy = DWMWA_USE_IMMERSIVE_DARK_MODE
    value = 2
    value = ct.c_int(value)
    set_window_attribute(hwnd, rendering_policy, ct.byref(value),
                         ct.sizeof(value))

I searched almost a year for a solution!

Sidesaddle answered 15/1, 2022 at 19:21 Comment(7)
Generally, it is considered unwise to 'force' a style to windows. If the user wants to change 'themes', this window will not following the theme change. Window decorations are generally the responsibility of the window manager.Harridan
And how to switch back to light theme?Stumpy
Try out different numbers for DWMWA_USE_IMMERSIVE_DARK_MODE. I think 1 makes it light again. I think changing the Tk.resizable() attribute also reverts itSidesaddle
This is Windows 11 only, you must mention thatWyler
I agree with @Harridan it should be left to the window manager to apply dark mode or not. Rather Get the attribute and configure your Application accordingly. In addition your question doesn't address the question cause it asks for custom buttons.Insincerity
@DelriusEuphoria USE_IMMERSIVE_DARK_MODE expecting a bool value. So 0 or 1 is fine.Insincerity
This worked fine for me on Win10, I just called the dark_title_bar(root) line right after root = Tk() and then resized the window after that using geometry.Rattan
P
3

These are the modifications that I have made using python 3.7.2

from tkinter import *
root = Tk()
root.overrideredirect(True) # turns off title bar, geometry
root.geometry('400x100+200+200') # set new geometry

# make a frame for the title bar
title_bar = Frame(root, bg='#2e2e2e', relief='raised', bd=2,highlightthickness=0)

# put a close button on the title bar
close_button = Button(title_bar, text='X', command= root.destroy,bg = "#2e2e2e",padx = 2,pady = 2,activebackground='red',bd = 0,font="bold",fg='white',highlightthickness=0)

# a canvas for the main area of the window
window = Canvas(root, bg='#2e2e2e',highlightthickness=0)

# pack the widgets
title_bar.pack(expand=1, fill=X)
close_button.pack(side=RIGHT)
window.pack(expand=1, fill=BOTH)
xwin=None
ywin=None
# bind title bar motion to the move window function

def move_window(event):
    root.geometry('+{0}+{1}'.format(event.x_root, event.y_root))
def change_on_hovering(event):
    global close_button
    close_button['bg']='red'
def return_to_normalstate(event):
    global close_button
    close_button['bg']='#2e2e2e'
    
    
title_bar.bind('<B1-Motion>', move_window)
close_button.bind('<Enter>',change_on_hovering)
close_button.bind('<Leave>',return_to_normalstate)
root.mainloop()

Explanation:
We use bd(border thickness)=0 to remove the borders from the the button
Then we bind the <Enter> event to a function which changes the foreground color.
And to return to its original state we bind the <Leave> event to another function

Initial State
Initial State Change in state after hovering mouse cursor over it
Change in state after hovering mouse cursor over it

Note : The cursor is not visible because my screen capture software somehow removed it

Pinpoint answered 12/2, 2020 at 11:36 Comment(0)
P
1

In python3.5.2 I had to make some modifications to get this to work:

#custom title bar for tkinter

from tkinter import Tk, Frame, Button, Canvas

root = Tk()

def move_window(event):
    root.geometry('+{0}+{1}'.format(event.x_root, event.y_root))

root.overrideredirect(True) # turns off title bar, geometry
root.geometry('400x100+200+200') # set new geometry

# make a frame for the title bar
title_bar = Frame(root, bg='white', relief='raised', bd=2)

# put a close button on the title bar
close_button = Button(title_bar, text='Close this Window', command=root.destroy)

# a canvas for the main area of the window
window = Canvas(root, bg='black')

# pack the widgets
title_bar.pack(expand=1, fill="x")
close_button.pack(side="right")
window.pack(expand=1, fill="both")

# bind title bar motion to the move window function
title_bar.bind('<B1-Motion>', move_window)

root.mainloop()
Pozzy answered 4/10, 2019 at 13:58 Comment(0)
B
1

screenshot

here you go updated to python 3.8 and new variable for title bar background and for main content background and title name add and new background + clear some indention errors

from tkinter import *

root = Tk()
# turns off title bar, geometry
root.overrideredirect(True)
# set new geometry
root.geometry('400x100+200+200')
# set background color of title bar
back_ground = "#2c2c2c"

# set background of window
content_color = "#ffffff"
# make a frame for the title bar
title_bar = Frame(root, bg=back_ground, relief='raised', bd=1, highlightcolor=back_ground,highlightthickness=0)

# put a close button on the title bar
close_button = Button(title_bar, text='x',  command=root.destroy,bg=back_ground, padx=5, pady=2, activebackground="red", bd=0,    font="bold", fg='white',        activeforeground="white", highlightthickness=0)
 # window title
title_window = "Title Name"
title_name = Label(title_bar, text=title_window, bg=back_ground, fg="white")
# a canvas for the main area of the window
window = Canvas(root, bg="white", highlightthickness=0)

# pack the widgets
title_bar.pack(expand=1, fill=X)
title_name.pack(side=LEFT)
close_button.pack(side=RIGHT)
window.pack(expand=1, fill=BOTH)
x_axis = None
y_axis = None
# bind title bar motion to the move window function


def move_window(event):
    root.geometry('+{0}+{1}'.format(event.x_root, event.y_root))


def change_on_hovering(event):
    global close_button
    close_button['bg'] = 'red'


def return_to_normal_state(event):
   global close_button
   close_button['bg'] = back_ground


title_bar.bind('<B1-Motion>', move_window)
close_button.bind('<Enter>', change_on_hovering)
close_button.bind('<Leave>', return_to_normal_state)
root.mainloop()
Benefice answered 5/5, 2020 at 18:36 Comment(0)
M
1

This is how it looks

I think this is the easy one. I used it on one of my project , due to some reasons from client side, client was not able to make any payment for that project and a lot of time got wasted.

from cProfile import label
from tkinter import *
import ttkbootstrap as ttk
from ttkbootstrap import Style
from tkinter import messagebox as m_box



win = Tk()
win.overrideredirect(True)
win.attributes("-topmost", True)
win.geometry("600x300+300+300")
win.resizable(1, 1)
style = Style("cyborg")

# ============================================================= Title bar Header


def ext():
    exit()

def minim():
    win.overrideredirect(0)
    win.wm_state("iconic")
    win.overrideredirect(1)

def about():
    pass
    m_box.showinfo("About", "Developer: Vivek phogat\nemail: [email protected]")


#------------------------------- Header section
Header_frame = ttk.Label(win)

info_label = ttk.Label(Header_frame,text="Customized title bar enjoy :-)",bootstyle="WARNING",font=("Comic Sans MS", 15))
info_label.pack(padx=5, pady=5,side = LEFT)

win_close_btn1 = ttk.Button(Header_frame, text=" X ", command=(win.destroy), bootstyle="danger")
win_close_btn1.pack( side = RIGHT,anchor= NE)
Min_but = ttk.Button(Header_frame, text=" _ ",  command=minim, bootstyle="light")
Min_but.pack( side = RIGHT,anchor= NE)
about_btn1 = ttk.Button(Header_frame, text=" a ", command=about, bootstyle="secondary")
about_btn1.pack(side = RIGHT,anchor= NE)


Header_frame.pack(fill=X)
#-------------------------------


#title bar get position.
def get_pos(event):
    global xwin
    global ywin
    xwin = event.x
    ywin = event.y

#title bar drag functon.
def drag(event):
    win.geometry(f"+{event.x_root - xwin}+{event.y_root - ywin}")


Header_frame.bind("<B1-Motion>", drag)
Header_frame.bind("<Button-1>", get_pos)
info_label.bind("<B1-Motion>", drag)
info_label.bind("<Button-1>", get_pos)


Footer_frame = ttk.Frame(win)

label = ttk.Label(Footer_frame, text= " Hope you all are doing good enjoy the code.",bootstyle="WARNING",font=("Comic Sans MS", 20)).pack()
Footer_frame.pack(expand= TRUE )
win.mainloop()
Mechanism answered 25/1, 2022 at 17:49 Comment(0)
B
0

un hovered image of window hovered image of window

ok i left the old answer if some need it. it is a recreated from scratch custom taskbar that assemble windows 10 bar

    from tkinter import *
    window = Tk()
    bg = "#f5f6f7"
    title_window = "test app"


    class app:
        def __init__(self, main):
            self.main = main
            self.main.configure(bg=bg)
            self.main.overrideredirect(True)
            self.main.geometry('230x130')
            self.main.resizable(width=False, height=False)
            self.top_bar = Frame(main,bg=bg, cursor="sizing")
            self.top_bar.pack(fill=X)
            self.title_txt = Label(self.top_bar, text=title_window ,bg=bg)
            self.title_txt.pack(side="left", padx=3)
            close_btn = Button(self.top_bar,text="x", cursor="arrow", bg=bg,    fg="black", highlightthickness=0,activebackground="red", activeforeground="white",bd=0, command=self.main.quit)
            close_btn.pack(side="right")
            bottom_bar = Frame(main, bg=bg)
            bottom_bar.pack()
            label_scr = Label(bottom_bar, text="label 1", padx=100, pady=5, bg=bg)
            label_scr.grid(row=0, column=0, columnspan=3)
            button_scr = Button(bottom_bar, text="Button1", bg=bg, bd=0)
            button_scr.grid(row=2, column=0, columnspan=3, pady=3)
            button2_scr = Button(bottom_bar, text="Button2", bg=bg,bd=0)
            button2_scr.grid(row=3, column=0, columnspan=3, pady=3)



    def move_window(event):
        window.geometry(f"+{event.x_root}+{event.y_root}")


    execution = app(window)
    execution.top_bar.bind('<B1-Motion>', move_window)
    execution.title_txt.bind('<B1-Motion>', move_window)
    window.mainloop()
Benefice answered 6/6, 2020 at 7:16 Comment(0)
P
0

here is my one

  from tkinter import *
  import windll

  class CustomTitle():
        """
        Ex:
            root = Tk()
            titleBar = CustomTitle(root,title_text = 'Hello,World!' , bg = "#000000" , fg = '#ffffff')
            titleBar.resizeable = True
            titleBar.packBar()
            root.mainloop()
    
            Note:
                    Try to Give Color value in Hex and the 3rd car should be number
                        #7a4e7a
                           ↑ (this one)                  
        """
        resizeable = True
        font_style = ('Candara',13)
        
    
        def __init__(self,win,title_text='Custom Title Bar',bg='#ffffff',fg="#000000"):  
            # deactivating main title bar
            self._win = win
            win.title(title_text)
    
            # props
            self.bg = bg
            self._maximized = False
            self._win_width = win.winfo_width()
            self._win_height = win.winfo_height()
            self._scr_width = win.winfo_screenwidth()
            self._scr_height = win.winfo_screenheight()
            self._addWidget(title_text,bg,fg)
            
        def packBar(self):
            self._title_bar.pack(fill=X)
            self._checkAbility()
            self._win.overrideredirect(1)
            self._finilize()
    
        def _checkAbility(self):
            if not self.resizeable:
                self._maximize_btn.config(state=DISABLED)
            else:
                self._resizey_widget.pack(side=BOTTOM,ipadx=.1,fill=X)
                self._resizex_widget.pack(side=RIGHT,ipadx=.1,fill=Y)
    
        def _maximize_win(self):
            if not self._maximized:
                self._past_size = root.geometry()
                self._win.geometry(f"{self._scr_width}x{self._scr_height}+{0}+{0}")
                self._maximize_btn.config(text = '🗗')
            else:
                self._win.geometry(self._past_size)
                self._maximize_btn.config(text = '🗖')
            self._maximized = not self._maximized
    
    
        def _minimize(self):
            Minimize = win32gui.GetForegroundWindow()
            win32gui.ShowWindow(Minimize, win32con.SW_MINIMIZE)
    
        
        def _setIconToTaskBar(self,mainWindow):
            GWL_EXSTYLE = -20
            WS_EX_APPWINDOW = 0x00040000
            WS_EX_TOOLWINDOW = 0x00000080
            # Magic
            hwnd = windll.user32.GetParent(mainWindow.winfo_id())
            stylew = windll.user32.GetWindowLongW(hwnd, GWL_EXSTYLE)
            stylew = stylew & ~WS_EX_TOOLWINDOW
            stylew = stylew | WS_EX_APPWINDOW
            res = windll.user32.SetWindowLongW(hwnd, GWL_EXSTYLE, stylew)
        
            mainWindow.wm_withdraw()
            mainWindow.after(10, mainWindow.wm_deiconify)
    
        def _addWidget(self,title_text,bg,fg):
            self._title_bar = Frame(self._win,bd=1,bg=bg)
    
            self._title_text = Label(self._title_bar,text=title_text,bg=bg,fg=fg,font=self.font_style)
            self._title_text.pack(side=LEFT,padx=4,pady=3)
            self._title_text.bind("<B1-Motion>",self._drag)
    
            self._close_btn = Button(self._title_bar,text = '×',bd=0,bg=bg,fg=fg,width=3,font=self.font_style,command=self._win.destroy)
            self._close_btn.pack(side=RIGHT,fill=Y)
            self._maximize_btn = Button(self._title_bar,text="🗖",bd=0,bg=bg,fg=fg,width=3,font=self.font_style,command=self._maximize_win)
            self._maximize_btn.pack(side=RIGHT,fill=Y)
            self._minimize_btn = Button(self._title_bar,text="_",bd=0,bg=bg,fg=fg,width=3,font=self.font_style,command=self._minimize)
            self._minimize_btn.pack(side=RIGHT,fill=Y)
            self._title_bar.bind('<Button-1>', self._drag)
    
            self._resizex_widget = Frame(self._win,cursor='sb_h_double_arrow')
            self._resizex_widget.bind("<B1-Motion>",self._resizex)
    
            self._resizey_widget = Frame(self._win,cursor='sb_v_double_arrow')
            self._resizey_widget.bind("<B1-Motion>",self._resizey)
    
            self._hover_effect()
    
        def _hover_effect(self):
            try:
                num = int(self.bg[3]) - 1
                newbg = self.bg.replace(self.bg[3],str(num))
            except:
                newbg = "#c7ebe8"
    
            def change_bg(which_one,bg = newbg):
                which_one.config(bg=bg)
            def restore_bg(which_one):
                which_one.config(bg=self.bg)
            self._maximize_btn.bind('<Enter>',lambda event: change_bg(self._maximize_btn))
            self._maximize_btn.bind('<Leave>',lambda event: restore_bg(self._maximize_btn))
            self._minimize_btn.bind('<Enter>',lambda event: change_bg(self._minimize_btn))
            self._minimize_btn.bind('<Leave>',lambda event: restore_bg(self._minimize_btn))
            self._close_btn.bind('<Enter>',lambda event: change_bg(self._close_btn,bg='#db2730'))
            self._close_btn.bind('<Leave>',lambda event: restore_bg(self._close_btn))
    
    
        def _finilize(self):
            self._win.after(10, lambda: self._setIconToTaskBar(self._win))
    
        def _drag(self,event):
            xwin = root.winfo_x()
            ywin = root.winfo_y()
            startx = event.x_root
            starty = event.y_root
    
            ywin = ywin - starty
            xwin = xwin - startx
    
            def _move_window(event): # runs when window is dragged
    
                root.geometry(f'+{event.x_root + xwin}+{event.y_root + ywin}')
    
    
            def _release_window(event): # runs when window is released
                root.config(cursor="arrow")
                
            self._title_bar.bind('<B1-Motion>', _move_window)
            self._title_bar.bind('<ButtonRelease-1>', _release_window)
            self._title_text.bind('<B1-Motion>', _move_window)
            self._title_text.bind('<ButtonRelease-1>', _release_window)
    
    
        def _resizex(self,event):
    
            xwin = root.winfo_x()
    
            difference = (event.x_root - xwin) - root.winfo_width()
    
            if root.winfo_width() > 150 : # 150 is the minimum width for the window
                try:
                    root.geometry(f"{ root.winfo_width() + difference }x{ root.winfo_height() }")
                except:
                    pass
            else:
                if difference > 0: # so the window can't be too small (150x150)
                    try:
                        root.geometry(f"{ root.winfo_width() + difference }x{ root.winfo_height() }")
                    except:
                        pass
    
    
        def _resizey(self,event):
    
            ywin = root.winfo_y()
    
            difference = (event.y_root - ywin) - root.winfo_height()
    
            if root.winfo_height() > 150: # 150 is the minimum height for the window
                try:
                    root.geometry(f"{ root.winfo_width()  }x{ root.winfo_height() + difference}")
                except:
                    pass
            else:
                if difference > 0: # so the window can't be too small (150x150)
                    try:
                        root.geometry(f"{ root.winfo_width()  }x{ root.winfo_height() + difference}")
                    except:
                        pass
Puck answered 28/9, 2021 at 18:23 Comment(1)
Welcome to Stack Overflow! Your answer could be improved by briefly explaining your approach outside of your code. Thanks for answering!Rostellum
M
0

Expanding on the second answer in this thread noting the error in the movement of the window when dragging the title bar, this answer provides better movement but it is still snappy on the y-axis. We can fix this in a simpler way using global variables. Here is my implementation:

def build():
  window.overrideredirect(True) #Turns off the title bar of the window
  window.geometry("800x600+100+100")

  # Create a new title bar
  title_bar = Frame(window, bg="dark slate blue", relief="raised", bd=2)
  close_button = Button(title_bar, text="X", command=window.destroy)
  title_bar.pack(expand=1, fill=X)
  close_button.pack(side=RIGHT)
  title_bar.bind('<Button-1>', get_title_click)
  title_bar.bind('<B1-Motion>', move_window)

def get_title_click(event):
  global title_click_x, title_click_y
  title_click_x = event.x_root
  title_click_y = event.y_root

def move_window(event):
  global title_click_x, title_click_y

  new_pos_x = window.winfo_x() + (event.x_root - title_click_x)
  new_pos_y = window.winfo_y() + (event.y_root - title_click_y)
  window.geometry(f'+{new_pos_x}+{new_pos_y}')
  title_click_x = event.x_root
  title_click_y = event.y_root

def main():
  global window

  window = Tk()
  build()
  mainloop()

main()
Markland answered 1/8, 2023 at 21:51 Comment(0)
O
-1

In Python 3.7 i can change the tittle with de function root.title(). Source: Source: https://www.geeksforgeeks.org/turtle-title-function-in-python/

import tkinter as tk
from turtle import title, width

root= tk.Tk()

root.title('Civ IV select songs')
canvas1 = tk.Canvas(root, width = 300, height = 600)
canvas1.pack()

The name of title bar was changed

Onaonager answered 3/10, 2022 at 23:31 Comment(2)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Storey
The tittle change with the function .tittle('tittle_text') (Python 3.7) (tkinter)Onaonager

© 2022 - 2024 — McMap. All rights reserved.