Tkinter: Mouse drag a window without borders, eg. overridedirect(1)

19,231

Solution 1

Yes, Tkinter exposes enough functionality to do this, and no, there are no easier/higher-level ways to achive what you want to do. You pretty much have the right idea.

Here's one example, though it's not the only way:

import tkinter as tk

class App(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.floater = FloatingWindow(self)

class FloatingWindow(tk.Toplevel):
    def __init__(self, *args, **kwargs):
        tk.Toplevel.__init__(self, *args, **kwargs)
        self.overrideredirect(True)

        self.label = tk.Label(self, text="Click on the grip to move")
        self.grip = tk.Label(self, bitmap="gray25")
        self.grip.pack(side="left", fill="y")
        self.label.pack(side="right", fill="both", expand=True)

        self.grip.bind("<ButtonPress-1>", self.start_move)
        self.grip.bind("<ButtonRelease-1>", self.stop_move)
        self.grip.bind("<B1-Motion>", self.do_move)

    def start_move(self, event):
        self.x = event.x
        self.y = event.y

    def stop_move(self, event):
        self.x = None
        self.y = None

    def do_move(self, event):
        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}")

app=App()
app.mainloop()

Solution 2

Here is my solution:

from tkinter import *
from webbrowser import *


lastClickX = 0
lastClickY = 0


def SaveLastClickPos(event):
    global lastClickX, lastClickY
    lastClickX = event.x
    lastClickY = event.y


def Dragging(event):
    x, y = event.x - lastClickX + window.winfo_x(), event.y - lastClickY + window.winfo_y()
    window.geometry("+%s+%s" % (x , y))


window = Tk()
window.overrideredirect(True)
window.attributes('-topmost', True)
window.geometry("400x400+500+300")
window.bind('<Button-1>', SaveLastClickPos)
window.bind('<B1-Motion>', Dragging)
window.mainloop()

Solution 3

The idea of Loïc Faure-Lacroix is useful, the following is my own simple code snippets on Python3.7.3, hope it will help:

from tkinter import *


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


root = Tk()
root.bind("<B1-Motion>", move_window)
root.mainloop()

But the position of the mouse is always in the upper left corner of the window. How can I keep it unchanged? Looking forward to a better answer!


Thanks to Bryan Oakley, because at the beginning I couldn't run your code on my computer, I didn't pay attention to it. Just now after the modification, it was very good to run, and the above situation would not happen (the mouse is always in the upper left corner), The updated code recently as follows:

def widget_drag_free_bind(widget):
    """Bind any widget or Tk master object with free drag"""
    if isinstance(widget, Tk):
        master = widget  # root window
    else:
        master = widget.master

    x, y = 0, 0
    def mouse_motion(event):
        global x, y
        # Positive offset represent the mouse is moving to the lower right corner, negative moving to the upper left corner
        offset_x, offset_y = event.x - x, event.y - y  
        new_x = master.winfo_x() + offset_x
        new_y = master.winfo_y() + offset_y
        new_geometry = f"+{new_x}+{new_y}"
        master.geometry(new_geometry)

    def mouse_press(event):
        global x, y
        count = time.time()
        x, y = event.x, event.y

    widget.bind("<B1-Motion>", mouse_motion)  # Hold the left mouse button and drag events
    widget.bind("<Button-1>", mouse_press)  # The left mouse button press event, long calculate by only once

Solution 4

Try this, and it surely works;

  1. Create an event function to move window:

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

  2. Bind window:

    root.bind('', movewindow)

Now you can touch the the window and drag

Share:
19,231
Malcolm
Author by

Malcolm

Updated on June 06, 2022

Comments

  • Malcolm
    Malcolm almost 2 years

    Any suggestions on how one might create event bindings that would allow a user to mouse drag a window without borders, eg. a window created with overridedirect(1)?

    Use case: We would like to create a floating toolbar/palette window (without borders) that our users can drag around on their desktop.

    Here's where I'm at in my thinking (pseudo code):

    1. window.bind( '<Button-1>', onMouseDown ) to capture the initial position of the mouse.

    2. window.bind( '<Motion-1>', onMouseMove ) to track position of mouse once it starts to move.

    3. Calculate how much mouse has moved and calculate newX, newY positions.

    4. Use window.geometry( '+%d+%d' % ( newX, newY ) ) to move window.

    Does Tkinter expose enough functionality to allow me to implement the task at hand? Or are there easier/higher-level ways to achieve what I want to do?

  • Malcolm
    Malcolm over 13 years
    Bryan! Thank you very much for your help. What a cool feature - I'm going to have a lot of fun building tools that take advantage of this capability. I also enjoyed your technique for creating the grip - very clever. For readers following this thread, I added the line "self.attributes( '-topmost', 1 )" after the line "self.overrideredirect(True)" to make Bryan's floating window show on top of all windows. I think this shows off the potential for how one might use Bryan's solution to create a variety of desktop utilities.
  • the_prole
    the_prole over 9 years
    Bryan, you don't mind making an edit illustrating how to reposition the mouse cursor over the specific coordinate the user clicked on? As your code is currently, the mouse cursor re-positions itself on the top left of the window after the user completes dragging the window by the label and releases the left mouse button.
  • Russell Smith
    Russell Smith over 9 years
    @the_prole: My code shouldn't be moving the mouse anywhere. All it does is respond to mouse movement. Are you saying this exact code is moving your mouse for you? On what platform?
  • the_prole
    the_prole over 9 years
    @BryanOakley It's not even the cursor that moves. It's the canvas that moves underneath the cursor. The window looks like this after I begin dragging it. imgur.com/qmOIOqG
  • Russell Smith
    Russell Smith over 9 years
    @the_prole: that looks like your code, not mine. Does the code in my example exhibit this problem?
  • the_prole
    the_prole over 9 years
    @BryanOakley You're right... I got this post stackoverflow.com/questions/7455573/… confused with yours. Which approach do you recommend? I tried to use your approach since it works apparently, but I do not see the label. Also the window frame does not disappear unless I insert self.overrideredirect(True) into def __init__(self):. If it makes any difference, I am using Python 3.4.
  • omgimdrunk
    omgimdrunk over 7 years
    in python 3 i'm getting an error File "C:\python\python3.5\lib\tkinter_init_.py", line 1686, in wm_geometry return self.tk.call('wm', 'geometry', self._w, newGeometry) _tkinter.TclError: bad geometry specifier "+562 + 580"
  • Alex
    Alex over 6 years
    This is the best Tkinter draggable overrideredirect(True)-code I found in 2 days!
  • Mike from PSG
    Mike from PSG over 5 years
    This is awesome code! Credited you Bryan in my project for your code. Working great so far!
  • Herii
    Herii almost 4 years
    I wanted to hide the main window that in my case didn't contain anything. I added the next line: tk.Tk.withdraw(self) after tk.Tk.__init__(self) and the main window disappeared while still displaying the floating window.