how to get a better window postion, if I click on an overrideredirectted window and move it (windows)?

Viewed 139

When I click on the window (in my case, when I click on self.top_frame and self.app_name), to move it, the window postion changes to 0 x and y coordinates. So if I want to move the window, the window moves to the right side. Can I fix it, so that the window doesn't move to the right side (or from my cursor)?

from tkinter import *
from tkinter import ttk
#from controls import call_back
#import random

class MainWindow:
    def __init__(self):
        # lists
        # color list [0] frame main bg [1] navibar [2] border color [3] status bar [4] Description Font
        self.color_lst = ["#363e4a", "#1d2128", "#8b9ebf", "#2b303b", "#45505f"]
        # button list [0] Home [1] Keyboard [2] Music [3] Timer [4] Setting Bar [5] exit [6] minimize
        self.btn_list = ["\u2263","\u2328","\u266B","\u23F1","\u2348","\u26CC","\u268A"]
        # control list [0] [1] [2] [3] [4] move setting bar
        self.controls = ["","","","",self.setting_move]

        # window settings, background, transparent, geometry
        self.root = Tk()
        self.root.geometry("1150x800")
        self.root.minsize(400, 400)
        self.root.overrideredirect(1)          # <------------
        self.root.resizable(True, True)

        # create a background frame (border)
        self.background_frame = Frame(self.root, bg=self.color_lst[2])
        self.background_frame.pack(expand=YES, fill=BOTH)

        # create a background frame
        self.background = Frame(self.background_frame, bg=self.color_lst[0])
        self.background.pack(expand=YES, fill=BOTH, padx=1, pady=1)

        # navigation bar
        self.navi_bar = Frame(self.background, bg=self.color_lst[1])
        self.navi_bar.pack(side="left",  fill=Y, ipadx=40)

        # setting bar
        self.setting_bar = Frame(self.background, bg=self.color_lst[2])
        self.setting_bar.pack(side="left",  fill=Y, ipadx=0)

        # top frame
        self.top_frame = Frame(self.background, bg=self.color_lst[1])
        self.top_frame.pack(fill=X)
        self.top_frame.bind("<B1-Motion>", self.call_back)      #  <--------------
        self.top_frame.bind("<Map>", lambda event: self.show_window(event))

        # create label description in the top frame
        self.top_description = Label(self.top_frame,anchor=W, padx=20, bg=self.color_lst[3], fg=self.color_lst[4], text="Application Description")
        self.top_description.pack(side="bottom", anchor=SW, expand=YES, fill=X)

        # exit buttton
        self.exit_btn = Button(self.top_frame,  bg=self.color_lst[1], fg=self.color_lst[2], text=self.btn_list[5], font=("Bahnschrift", 16), bd=0, command=self.root.destroy)
        self.exit_btn.pack(side="right", anchor=E, ipadx=10)

        # minimize buttton
        self.minimize_btn = Button(self.top_frame,  bg=self.color_lst[1], fg=self.color_lst[2], text=self.btn_list[6], font=("Bahnschrift", 16), bd=0, command=self.minimize_window)
        self.minimize_btn.pack(side="right", anchor=E, ipadx=10)

        # Application name
        self.app_name = Label(self.top_frame,anchor=W, padx=20, bg=self.color_lst[1], fg=self.color_lst[2], font=("Bahnschrift", 14), text="Application Name")
        self.app_name.pack(side="right", anchor=E, expand=YES, fill=X)
        self.app_name.bind("<B1-Motion>", self.call_back)     # <------------

        # background for different frames
        self.frames = Frame(self.background, bg=self.color_lst[0])
        self.frames.pack(expand=YES, fill=BOTH, side="top")

        # bottom status bar
        self.bottom_bar = Label(self.background,anchor=W, padx=20, bg=self.color_lst[3], fg=self.color_lst[4], text="Application Description")
        self.bottom_bar.pack(fill=X, side="bottom", ipady=2)

        # resize grip
        self.resize_grip = ttk.Sizegrip(self.bottom_bar)
        #self.resize_grip.
        self.resize_grip.pack(side="right")
        self.resize_grip.bind("<B1-Motion>", self.grip_resize)

        # button statement to move setting bar
        self.btn_SETTINGS = False
        # buttons in the navigation bar
        y=0
        for index in range(5):
            self.btn = Button(self.navi_bar, bg=self.color_lst[1], fg=self.color_lst[2], text=self.btn_list[index], font=("Bahnschrift", 20), bd=0)
            self.btn.configure(command=self.controls[index])
            self.btn.place(x=0, y=y, relwidth=1, height=64)
            y +=64

    # function to move the setting bar
    def setting_move(self):
        self.x = 0
        if self.btn_SETTINGS == False:
            for x in range(10):
                self.setting_bar.pack_configure(ipadx=self.x)
                self.btn.configure(text="\u2347")
                self.setting_bar.after(20)
                self.setting_bar.update_idletasks()
                self.x += 10
                self.btn_SETTINGS = True
        else:
            self.setting_bar.pack_configure(ipadx=0)
            self.btn.configure(text="\u2348")
            self.btn_SETTINGS = False

    # better graphics
    def grip_resize(self, event):
        self.root.update_idletasks()

    # minimize the window
    def minimize_window(self):
        self.root.overrideredirect(0)
        self.root.iconify()

    # show the window
    def show_window(self, event):
        self.root.overrideredirect(1)
        #self.root.deiconify()

    # function to move the whole window (on top frame clicked)      #  <------------
    def call_back(self, event):
        self.root.geometry("+{0}+{1}".format(event.x_root,event.y_root))# <--------------


if __name__ == "__main__":
    app = MainWindow()
    mainloop()

I set some arrows to the function and where it's binded, that you can find it better, to see what I mean.

3 Answers

Try this:

import tkinter as tk

class DraggableWindow(tk.Tk):
    def __init__(self):
        self.draggable = True
        super().__init__()
        self._offsetx = 0
        self._offsety = 0
        self.bind("<Button-1>", self.clickwin)
        self.bind("<B1-Motion>", self.dragwin)

    def topmost(self):
        self.attributes("-topmost", True)
        try:
            self.attributes("-type", "splash") # Linux
        except:
            self.overrideredirect(True) # Windows

    def dragwin(self, event):
        if self.draggable:
            x = self.winfo_pointerx() - self._offsetx
            y = self.winfo_pointery() - self._offsety
            self.geometry("+%d+%d" % (x, y))

    def clickwin(self, event):
        if self.draggable:
            self._offsetx = event.widget.winfo_rootx() - self.winfo_rootx() + event.x
            self._offsety = event.widget.winfo_rooty() - self.winfo_rooty() + event.y

root = DraggableWindow()
root.mainloop()

I made this a long time ago - forgot the details on how it works. I will update this with an explanation when I find out how it works again.

To make the window undraggable change <DraggableWindow>.draggable to False. You might also want to look at this. It supports resizing and Alt-Tabing to it.

Ok here the fixed version:

from tkinter import *
from tkinter import ttk
#from controls import Functions
#import random

class MainWindow:
    def __init__(self):

        # window settings, background, transparent, geometry
        self.root = Tk()
        self.root.geometry("1150x800")
        self.root.minsize(400, 400)
        self.root.overrideredirect(1)   # <-------------------------- 
        self.root.resizable(True, True)
        self.root.attributes("-topmost", True)

        # lists
        # color list [0] frame main bg [1] navibar [2] border color [3] status bar [4] Description Font
        self.color_lst = ["#363e4a", "#1d2128", "#8b9ebf", "#2b303b", "#45505f"]
        # button list [0] Home [1] Keyboard [2] Music [3] Timer [4]                             [5] exit [6] minimize
        self.btn_list = ["\u2263","\u2328","\u266B","\u23F1","\u2348","\u26CC","\u268A"]
        # control list [0] [1] [2] [3] open timer frame [4] move setting bar
        self.controls = ["","","",self.open_Timer ,self.setting_move]

        # statements
        self.draggable = True  # <-------------------------- 
        self._offsetx = 0   # <-------------------------- 
        self._offsety = 0  # <-------------------------- 
        # button statement to move setting bar
        self.btn_SETTINGS = False

        # create a background frame
        self.background_frame = Frame(self.root, bg=self.color_lst[2])
        self.background_frame.pack(expand=YES, fill=BOTH)

        # create a background frame
        self.background = Frame(self.background_frame, bg=self.color_lst[0])
        self.background.pack(expand=YES, fill=BOTH, padx=1, pady=1)

        # navigation bar
        self.navi_bar = Frame(self.background, bg=self.color_lst[1])
        self.navi_bar.pack(side="left",  fill=Y, ipadx=40)

        # setting bar
        self.setting_bar = Frame(self.background, bg=self.color_lst[2])
        self.setting_bar.pack(side="left",  fill=Y, ipadx=0)

        # top frame
        self.top_frame = Frame(self.background, bg=self.color_lst[1])
        self.top_frame.pack(fill=X)
        self.top_frame.bind("<B1-Motion>", self.call_back)
        self.top_frame.bind("<Map>", lambda event: self.show_window(event))

        # create label description in the top frame
        self.top_description = Label(self.top_frame,anchor=W, padx=20, bg=self.color_lst[3], fg=self.color_lst[4], text="Application Description")
        self.top_description.pack(side="bottom", anchor=SW, expand=YES, fill=X)

        # exit buttton
        self.exit_btn = Button(self.top_frame,  bg=self.color_lst[1], fg=self.color_lst[2], text=self.btn_list[5],
                               font=("Bahnschrift", 16), bd=0, command=self.root.destroy, activebackground="red")
        self.exit_btn.pack(side="right", anchor=E, ipadx=10)
        self.exit_btn.bind("<Enter>", lambda event: self.on_Enter(event))
        self.exit_btn.bind("<Leave>", lambda event: self.on_Leave(event))

        # minimize buttton
        self.minimize_btn = Button(self.top_frame,  bg=self.color_lst[1], fg=self.color_lst[2], text=self.btn_list[6],
                                   font=("Bahnschrift", 16), bd=0, command=self.minimize_window, activebackground=self.color_lst[3])
        self.minimize_btn.pack(side="right", anchor=E, ipadx=10)
        self.minimize_btn.bind("<Enter>", lambda event: self.on_Enter(event))
        self.minimize_btn.bind("<Leave>", lambda event: self.on_Leave(event))

        # Application name
        self.app_name = Label(self.top_frame,anchor=W, padx=20, bg=self.color_lst[1], fg=self.color_lst[2], font=("Bahnschrift", 14), text="Application Name")
        self.app_name.pack(side="right", anchor=E, expand=YES, fill=X)
        self.app_name.bind("<B1-Motion>", self.call_back)   # <-------------------------- 
        self.root.bind("<Button-1>", self.clickwin)  # <-------------------------- 

        # background for different frames
        self.frames = Frame(self.background, bg=self.color_lst[0])
        self.frames.pack(expand=YES, fill=BOTH, side="top")

        # bottom status bar
        self.bottom_bar = Label(self.background,anchor=W, padx=20, bg=self.color_lst[3], fg=self.color_lst[4], text="Application Description")
        self.bottom_bar.pack(fill=X, side="bottom", ipady=2)

        # resize grip
        self.resize_grip = ttk.Sizegrip(self.bottom_bar)
        #self.resize_grip.
        self.resize_grip.pack(side="right")
        self.resize_grip.bind("<B1-Motion>", self.grip_resize)

        # buttons in the navigation bar
        y=0
        for index in range(5):
            self.btn = Button(self.navi_bar, bg=self.color_lst[1], fg=self.color_lst[2], text=self.btn_list[index], font=("Bahnschrift", 20), bd=0)
            self.btn.configure(command=self.controls[index], activebackground=self.color_lst[3])
            self.btn.place(x=0, y=y, relwidth=1, height=64)
            self.btn.bind("<Enter>", lambda event: self.on_Enter(event))
            self.btn.bind("<Leave>", lambda event: self.on_Leave(event))
            y +=64

    def on_Enter(self, e):
        e.widget.configure(bg=self.color_lst[0], fg=self.color_lst[2])

    def on_Leave(self, e):
        e.widget.configure(bg=self.color_lst[1], fg=self.color_lst[2])
    # function to move the setting bar
    def setting_move(self):
        self.x = 0
        if self.btn_SETTINGS == False:
            for x in range(10):
                self.setting_bar.pack_configure(ipadx=self.x)
                self.btn.configure(text="\u2347")
                self.setting_bar.after(20)
                self.setting_bar.update_idletasks()
                self.x += 10
                self.btn_SETTINGS = True
        else:
            self.setting_bar.pack_configure(ipadx=0)
            self.btn.configure(text="\u2348")
            self.btn_SETTINGS = False

    # better graphics
    def grip_resize(self, event):
        self.root.update_idletasks()

    # minimize the window
    def minimize_window(self):
        self.root.overrideredirect(0)
        self.root.iconify()

    # show the window
    def show_window(self, event):
        self.root.overrideredirect(1)
        #self.root.deiconify()

    # function to move the whole window (on top frame clicked)
    def call_back(self, event):     # <-------------------------- 
        if self.draggable:
            x = self.root.winfo_pointerx() - self._offsetx
            y = self.root.winfo_pointery() - self._offsety
            self.root.geometry("+%d+%d" % (x, y))

    def clickwin(self, event):   # <-------------------------- 
        if self.draggable:
            self._offsetx = event.widget.winfo_rootx() - self.root.winfo_rootx() + event.x
            self._offsety = event.widget.winfo_rooty() - self.root.winfo_rooty() + event.y


    def open_Timer(self):
        self.timer_Frame = Canvas(self.background, bg=self.color_lst[0])
        self.timer_Frame.pack(expand=YES, fill=BOTH, ipady=1000)

        self.button = Button(self.timer_Frame, text="Test")
        self.button.pack()


if __name__ == "__main__":
    mw = MainWindow()

    mainloop()

The @TheLizzard answer works but has a small problem. It jumps when you click the window. I solved this issue by saving the window position on the screen.

import tkinter

class Win(tkinter.Tk):

    def __init__(self,master=None):
        tkinter.Tk.__init__(self,master)
        self.overrideredirect(True)
        self._offsetx = 0
        self._offsety = 0
        self._window_x = 500
        self._window_y = 100
        self._window_w = 500
        self._window_h = 500
        self.geometry('{w}x{h}+{x}+{y}'.format(w=self._window_w,h=self._window_h,x=self._window_x,y=self._window_y))
        self.bind('<Button-1>',self.clickwin)
        self.bind('<B1-Motion>',self.dragwin)

    def dragwin(self,event):
        delta_x = self.winfo_pointerx() - self._offsetx
        delta_y = self.winfo_pointery() - self._offsety
        x = self._window_x + delta_x
        y = self._window_y + delta_y
        self.geometry("+{x}+{y}".format(x=x, y=y))
        self._offsetx = self.winfo_pointerx()
        self._offsety = self.winfo_pointery()
        self._window_x = x
        self._window_y = y

    def clickwin(self,event):
        self._offsetx = self.winfo_pointerx()
        self._offsety = self.winfo_pointery()


win = Win()
win.mainloop()

self._window_x and self._window_y are the primary position of the window.

self._window_h and self._window_w are the height and width of the window.

Related