3

I know how to remove a border from a Tkinter window using overrideredirect, but whenever I do that the window becomes unresponsive. I can't move it using alt and dragging, or any other method.
I want to make an application that looks like one of those "riced" applications that are just a bare window, and obviously I can't get very far if it just sits unresponsive in the upper-left corner. So, how do I do this?

fhdrsdg
  • 10,297
  • 2
  • 41
  • 62
yyyyQqxKNqHyy
  • 343
  • 3
  • 10

4 Answers4

15

To make the window draggable, put bindings for <Button-1> (mouse clicks) and <B1-Motion> (mouse movements) on the window.

All you need to do is store the x and y values of a mouse down event and then during mouse motion events, you position the window based on the current pointer x and y, delta the original event x and y.

The handler for the mouse click binding stores the original event x and y.

The handler for the mouse movement binding calls the TopLevel method geometry() to reposition the window, based on current mouse position and the offset you have stored from the most recent mouse click. You supply a geometry string to the geometry method.

Here is a very minimal example which does not take into account the edges of 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.bind('<Button-1>',self.clickwin)
        self.bind('<B1-Motion>',self.dragwin)

    def dragwin(self,event):
        x = self.winfo_pointerx() - self._offsetx
        y = self.winfo_pointery() - self._offsety
        self.geometry('+{x}+{y}'.format(x=x,y=y))

    def clickwin(self,event):
        self._offsetx = event.x
        self._offsety = event.y


win = Win()
win.mainloop()

EDIT by TheLizzard:

The code above works but doesn't behave correctly when there is more than one widget so this is the fixed code:

import tkinter as tk


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

    def dragwin(self,event):
        x = super().winfo_pointerx() - self._offsetx
        y = super().winfo_pointery() - self._offsety
        super().geometry(f"+{x}+{y}")

    def clickwin(self,event):
        self._offsetx = super().winfo_pointerx() - super().winfo_rootx()
        self._offsety = super().winfo_pointery() - super().winfo_rooty()


root = Win()

label_1 = tk.Label(root, text="Label 1")
label_1.pack(side="left")

label_2 = tk.Label(root, text="Label 2")
label_2.pack(side="left")

root.mainloop()
TheLizzard
  • 7,248
  • 2
  • 11
  • 31
dusty
  • 865
  • 9
  • 15
  • 1
    I had just fixed the issue with the window jumping in your code, use ButtonPress-1 instead of Button-1 – omgimdrunk Jan 23 '17 at 11:08
  • 2
    Unfortunately, my window is still jumping - only if I click the far bottom-right edge it is not. – Alex Dec 14 '17 at 12:15
0

Thanks to @dusty's answer, it had a jumping problem, and I solved it by saving the window location.

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.

Ali Ent
  • 1,420
  • 6
  • 17
-1

This solution is works for me:

from tkinter import *
import mouse
global x, y

def standard_bind():
   root.bind('<B1-Motion>', lambda e: event(e, Mode=True))

def event(widget, Mode=False):
    global x, y
    if Mode:
        x = widget.x
        y = widget.y
    root.bind('<B1-Motion>', lambda e: event(e))
    root.geometry('+%d+%d' % (mouse.get_position()[0]-x, mouse.get_position()[1]-y))

root = Tk()
root.overrideredirect(True)
root.bind('<B1-Motion>', lambda e: event(e, Mode=True))
root.bind('<ButtonRelease-1>', lambda e: standard_bind())
root.geometry('%dx%d+%d+%d' % (600, 60, 50, 50))
mainloop()
  • You should describe what the code does and why, instead of pasting just plain code. – Aleksander Ikleiw Sep 08 '20 at 18:32
  • There is no point to using the `mouse` module and this code doesn't follow PEP 8. Also the first parameter of your `event` function is not a widget it is a `tkinter.Event` object. – TheLizzard Aug 07 '21 at 19:14
-1

Here a bit more sophisticated method which assumes that you don't want to just click any where on the tkinter app to move it, but rather clicking on the title bar to move the app around while retaining the familiar "X" to close the app.

Works for python3.0 and later


Since tkinter does not (by default) allow you to directly achieve this, we must:
  1. Remove the tkinter frame's title bar
  2. Create our own title bar and recreate the "x" for closing the app
  3. bind the event for clicking, such that the app moves when dragged
from tkinter import *

root = Tk()
root.title('The Name of Your Application')

root.geometry("500x300")

# remove title bar
root.overrideredirect(True)

def move_app(e):
  root.geometry(f'+{e.x_root}+{e.y_root}')

def quitter(e):
  root.quit()
  #root.destroy()

# Create Fake Title Bar
title_bar = Frame(root, bg="darkgreen", relief="raised", bd=0)
title_bar.pack(expand=1, fill=X)
# Bind the titlebar
title_bar.bind("<B1-Motion>", move_app)


# Create title text
title_label = Label(title_bar, text="  My Awesome App!!", bg="darkgreen", fg="white")
title_label.pack(side=LEFT, pady=4)

# Create close button on titlebar
close_label = Label(title_bar, text="  X  ", bg="darkgreen", fg="white", relief="sunken", bd=0)
close_label.pack(side=RIGHT, pady=4)
close_label.bind("<Button-1>", quitter)

my_button = Button(root, text="CLOSE!", font=("Helvetica, 32"), command=root.quit)
my_button.pack(pady=100)



root.mainloop()
Mac
  • 134
  • 1
  • 7