pythontkintermovetitlebarmouse-position

Move a custom Title bar window with Tkinter without doing so from the Top-left corner


guys, Happy New Year,

So I'm pretty new to Python and even newer to Tkinter. I'm trying to create a Custom Title bar for my UI and so far I'm doing ok using some articles and videos. The problem is that when creating a Custom Title bar and trying to move the window, it moves from the Top-Left corner thus 'teleporting' the window.

Looking at this: Thread there's an answer to my question but I couldn't get it to work.

So this is the code:

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

xwin = root.winfo_x()
ywin = root.winfo_y()

def get_pos(event):

    global xwin
    global ywin

    xwin = xwin - event.x_root
    ywin = ywin - event.y_root

def move_window(event):
    root.geometry(f'+{event.x_root - xwin}+{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)
title_bar.bind("<Button-1>", get_pos)
close_button.bind('<Enter>', change_on_hovering)
close_button.bind('<Leave>', return_to_normal_state)
root.mainloop()

In this version, it's a bit different from what's shown in the thread above because I kept receiving errors the other way. The problem is that now the window teleports to a new location and I can't manage to get it to work properly.

Thank you in advance!


Solution

  • Code works for me when I use

    def get_pos(event):
    
        global xwin
        global ywin
    
        xwin = event.x
        ywin = event.y
    

    And you forgot ywin in

    def move_window(event):
        root.geometry(f'+{event.x_root - xwin}+{event.y_root - ywin}')
    

    EDIT:

    Full working code with reoganized code.

    Tested on Python 3 so I had to use tkinter instead of Tkinter

    PEP 8 -- Style Guide for Python Code

    #from tkinter import *  # PEP8: `import *` is not preferred
    import tkinter as tk
    
    # --- classes ---
    
    # empty
    
    # --- functions ---
    
    def get_pos(event):
        global xwin
        global ywin
    
        xwin = event.x
        ywin = event.y
    
    def move_window(event):
        root.geometry(f'+{event.x_root - xwin}+{event.y_root - ywin}')
    
    def change_on_hovering(event):
        close_button['bg'] = 'red'
    
    def return_to_normal_state(event):
        close_button['bg'] = back_ground
    
    # --- main ---
    
    # set background color of title bar
    back_ground = "#2c2c2c"
    # set background of window
    content_color = "#ffffff"
    
    # ---
    
    root = tk.Tk()
    # turns off title bar, geometry
    root.overrideredirect(True)
    
    # set new geometry
    root.geometry('400x100+200+200')
    
    # make a frame for the title bar
    title_bar = tk.Frame(root, bg=back_ground, relief='raised', bd=1, 
                         highlightcolor=back_ground, 
                         highlightthickness=0)
    
    # put a close button on the title bar
    close_button = tk.Button(title_bar, text='x', bg=back_ground, padx=5, pady=2, 
                             bd=0, font="bold", fg='white',
                             activebackground="red",
                             activeforeground="white", 
                             highlightthickness=0, 
                             command=root.destroy)
                             
    # window title
    title_window = "Title Name"
    title_name = tk.Label(title_bar, text=title_window, bg=back_ground, fg="white")
    
    # a canvas for the main area of the window
    window = tk.Canvas(root, bg="white", highlightthickness=0)
    
    # pack the widgets
    title_bar.pack(expand=True, fill='x')
    title_name.pack(side='left')
    close_button.pack(side='right')
    window.pack(expand=True, fill='both')
    
    # bind title bar motion to the move window function
    title_bar.bind("<B1-Motion>", move_window)
    title_bar.bind("<Button-1>", get_pos)
    close_button.bind('<Enter>', change_on_hovering)
    close_button.bind('<Leave>', return_to_normal_state)
    
    root.mainloop()
    

    EDIT:

    Meanwhile I created version which use classes - so I could easily add more buttons.
    It can also change titlebar color and text when it is moving.

    enter image description here

    import tkinter as tk
    
    # --- constants --- (UPPER_CASE_NAMES)
    
    # title bar colors
    TITLE_FOREGROUND = "white"
    TITLE_BACKGROUND = "#2c2c2c"
    TITLE_BACKGROUND_HOVER = "green"
    
    BUTTON_FOREGROUND = "white"
    BUTTON_BACKGROUND = TITLE_BACKGROUND
    BUTTON_FOREGROUND_HOVER = BUTTON_FOREGROUND
    BUTTON_BACKGROUND_HOVER = 'red'
    
    # window colors
    WINDOW_BACKGROUND = "white"
    WINDOW_FOREGROUND = "black"
    
    # --- classes --- (CamelCaseNames)
    
    class MyButton(tk.Button):
    
        def __init__(self, master, text='x', command=None, **kwargs):
            super().__init__(master, bd=0, font="bold", padx=5, pady=2, 
                             fg=BUTTON_FOREGROUND, 
                             bg=BUTTON_BACKGROUND,
                             activebackground=BUTTON_BACKGROUND_HOVER,
                             activeforeground=BUTTON_FOREGROUND_HOVER, 
                             highlightthickness=0, 
                             text=text,
                             command=command)
    
            self.bind('<Enter>', self.on_enter)
            self.bind('<Leave>', self.on_leave)
    
        def on_enter(self, event):
            self['bg'] = BUTTON_BACKGROUND_HOVER
    
        def on_leave(self, event):
            self['bg'] = BUTTON_BACKGROUND
    
    class MyTitleBar(tk.Frame):
    
        def __init__(self, master, *args, **kwargs):
            super().__init__(master, relief='raised', bd=1, 
                             bg=TITLE_BACKGROUND,
                             highlightcolor=TITLE_BACKGROUND, 
                             highlightthickness=0)
    
            self.title_label = tk.Label(self, 
                                        bg=TITLE_BACKGROUND, 
                                        fg=TITLE_FOREGROUND)
                                        
            self.set_title("Title Name")
    
            self.close_button = MyButton(self, text='x', command=master.destroy)
            self.minimize_button = MyButton(self, text='-', command=self.on_minimize)
            self.other_button = MyButton(self, text='#', command=self.on_other)
                             
            self.pack(expand=True, fill='x')
            self.title_label.pack(side='left')
            self.close_button.pack(side='right')
            self.minimize_button.pack(side='right')
            self.other_button.pack(side='right')
    
            self.bind("<ButtonPress-1>", self.on_press)
            self.bind("<ButtonRelease-1>", self.on_release)
            self.bind("<B1-Motion>", self.on_move)
            
        def set_title(self, title):
            self.title = title
            self.title_label['text'] = title
            
        def on_press(self, event):
            self.xwin = event.x
            self.ywin = event.y
            self.set_title("Title Name - ... I'm moving! ...")
            self['bg'] = 'green'
            self.title_label['bg'] = TITLE_BACKGROUND_HOVER
    
        def on_release(self, event):
            self.set_title("Title Name")
            self['bg'] = TITLE_BACKGROUND
            self.title_label['bg'] = TITLE_BACKGROUND
            
        def on_move(self, event):
            x = event.x_root - self.xwin
            y = event.y_root - self.ywin
            self.master.geometry(f'+{x}+{y}')
            
        def on_minimize(self):
            print('TODO: minimize')
                    
        def on_other(self):
            print('TODO: other')
    
    # --- functions ---
    
    # empty
    
    # --- main ---
    
    root = tk.Tk()
    # turns off title bar, geometry
    root.overrideredirect(True)
    
    # set new geometry
    root.geometry('400x100+200+200')
    
    title_bar = MyTitleBar(root) 
    #title_bar.pack()  # it is inside `TitleBar.__init__()`
    
    # a canvas for the main area of the window
    window = tk.Canvas(root, bg=WINDOW_BACKGROUND, highlightthickness=0)
    
    # pack the widgets
    window.pack(expand=True, fill='both')
    
    root.mainloop()