Tkinter: Mouse Drag a Window Without Borders, Eg. Overridedirect(1)

Tkinter: Mouse drag a window without borders, eg. overridedirect(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()

Tkinter python 3 - Moving a borderless window

a complete example that uses a class to wrap all the functionality:

from tkinter import *

class Grip:
''' Makes a window dragable. '''
def __init__ (self, parent, disable=None, releasecmd=None) :
self.parent = parent
self.root = parent.winfo_toplevel()

self.disable = disable
if type(disable) == 'str':
self.disable = disable.lower()

self.releaseCMD = releasecmd

self.parent.bind('<Button-1>', self.relative_position)
self.parent.bind('<ButtonRelease-1>', self.drag_unbind)

def relative_position (self, event) :
cx, cy = self.parent.winfo_pointerxy()
geo = self.root.geometry().split("+")
self.oriX, self.oriY = int(geo[1]), int(geo[2])
self.relX = cx - self.oriX
self.relY = cy - self.oriY

self.parent.bind('<Motion>', self.drag_wid)

def drag_wid (self, event) :
cx, cy = self.parent.winfo_pointerxy()
d = self.disable
x = cx - self.relX
y = cy - self.relY
if d == 'x' :
x = self.oriX
elif d == 'y' :
y = self.oriY
self.root.geometry('+%i+%i' % (x, y))

def drag_unbind (self, event) :
self.parent.unbind('<Motion>')
if self.releaseCMD != None :
self.releaseCMD()

def main():
root = Tk()
root.geometry("200x200")
root.resizable(0, 0)
root.overrideredirect(1)

back = Frame(root, bg="grey")
back.pack_propagate(0)
back.pack(fill=BOTH, expand=1)

top_Frame = Frame(back, bg="#505050")
top_Frame.place(x=0, y=0, anchor="nw", width=200, height=20)
'''Would Be great if it could be specified to only be moved
when dragging with the Frame above.'''
grip = Grip(top_Frame)

Ext_but = Button(top_Frame, text="X", bg="#FF6666", fg="white", command=lambda: exit())
Ext_but.place(x=170, y=0, anchor="nw", width=30, height=20)

root.mainloop()

main()

note there are some extra bits in this class (I copied it from what I'd done before) as it can be used to restrict which direction it can be dragged (the disable flag) and trigger a callback when the drag stops.

Moving an overrideredirected top Level window well allowing for minimization

According to the error message, you have a class name Win which is derived from tkinter.Tk. So all the functions in the posted code are inside that class.

Then the three lines:

top_level.bind("<ButtonPress-1>", top_level.StartMove)
top_level.bind("<ButtonRelease-1>", top_level.StopMove)
top_level.bind("<B1-Motion>", top_level.OnMotion)

should be changed to:

self.top_level.bind("<ButtonPress-1>", self.StartMove)
self.top_level.bind("<ButtonRelease-1>", self.StopMove)
self.top_level.bind("<B1-Motion>", self.OnMotion)

top_level should be better an instance variable of the class so that it can be accessed by other class methods:

self.top_level = tkinter.Toplevel(self)
self.top_level.geometry('600x300')
self.top_level.overrideredirect(1)
self.top_level.config(bg='#1e1e1e')

Also normally the name of the first argument of class methods StartMove(), StopMove() and OnMotion() is self. And those functions should be modified to cater the change of top_level from local variable to instance variable:

def StartMove(self, event):
self.top_level.x = event.x
self.top_level.y = event.y

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

def OnMotion(self, event):
deltax = event.x - self.top_level.x
deltay = event.y - self.top_level.y
x = self.top_level.winfo_x() + deltax
y = self.top_level.winfo_y() + deltay
self.top_level.geometry('+%s+%s' % (x, y))

Below is the definition of the class Win:

class Win(tkinter.Tk):
def __init__(self):
tkinter.Tk.__init__(self)
#Hide Root
self.attributes('-alpha', 0.0)
self.attributes('-topmost', 1)

#Top Level Window
self.top_level = tkinter.Toplevel(self)
self.top_level.geometry('600x300')
self.top_level.overrideredirect(1)
self.top_level.config(bg='#1e1e1e')
self.top_level.bind('<ButtonPress-1>', self.StartMove)
self.top_level.bind('<ButtonRelease-1>', self.StopMove)
self.top_level.bind('<B1-Motion>', self.OnMotion)

def StartMove(self, event):
self.top_level.x = event.x
self.top_level.y = event.y

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

def OnMotion(self, event):
deltax = event.x - self.top_level.x
deltay = event.y - self.top_level.y
x = self.top_level.winfo_x() + deltax
y = self.top_level.winfo_y() + deltay
self.top_level.geometry('+%s+%s' % (x, y))

Tkinter overridedirect (minimizing and windows task bar issues)

Okay guys, I finally figured it out.

# Imports
from tkinter import *
from ctypes import windll

# Some WindowsOS styles, required for task bar integration
GWL_EXSTYLE = -20
WS_EX_APPWINDOW = 0x00040000
WS_EX_TOOLWINDOW = 0x00000080

lastClickX = 0
lastClickY = 0

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

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

def set_appwindow(mainWindow):
# Honestly forgot what most of this stuff does. I think it's so that you can see
# the program in the task bar while using overridedirect. Most of it is taken
# from a post I found on stackoverflow.
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)
# re-assert the new window style
mainWindow.wm_withdraw()
mainWindow.after(10, lambda: mainWindow.wm_deiconify())

def main():
global mainWindow, z
# Default window configuration
mainWindow = Tk()
mainWindow.geometry('800x400')
mainWindow.resizable(width=False, height=False)
mainWindow.overrideredirect(True)
mainWindow.after(10, lambda: set_appwindow(mainWindow))
mainWindow.bind('<Button-1>', SaveLastClickPos)
mainWindow.bind('<B1-Motion>', Dragging)
z = 0

def exitGUI():
mainWindow.destroy()

def minimizeGUI():
global z
mainWindow.state('withdrawn')
mainWindow.overrideredirect(False)
mainWindow.state('iconic')
z = 1

def frameMapped(event=None):
global z
mainWindow.overrideredirect(True)
mainWindow.iconbitmap("ANAL_OG.ico")
if z == 1:
set_appwindow(mainWindow)
z = 0

exitButton = Button(mainWindow, text='', bg='#212121', fg='#35DAFF',
command=exitGUI)
minimizeButton = Button(mainWindow, text='', bg="#212121", fg='#35DAFF',
command=minimizeGUI)
exitButton.place(x=780, y=0)
minimizeButton.place(x=759, y=0)
mainWindow.bind("<Map>", frameMapped) # This brings back the window
mainWindow.mainloop() # Window Loop

if __name__ == '__main__':
main()

Thanks for all your suggestions!

Tkinter: windows without title bar but resizable

The problem is, the window is resizable, but when you turn on overrideredirect you lose any sort of header or edge that you can grab in order to resize the window. The only solution is to implement resizing yourself. You can add your own borders, or add mouse bindings that work when the mouse is near an edge.

This answer shows how to move such a window: Python/Tkinter: Mouse drag a window without borders, eg. overridedirect(1)

Here's a short example that illustrates resizing. It has only barely been tested on OSX but should work on any platform. It uses python2, though it should work with python3 just by changing the import statements.

import Tkinter as tk
import ttk

class Example(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.wm_geometry("400x400")

self.label = tk.Label(self, text="Grab the lower-right corner to resize")
self.label.pack(side="top", fill="both", expand=True)

self.grip = ttk.Sizegrip(self)
self.grip.place(relx=1.0, rely=1.0, anchor="se")
self.grip.lift(self.label)
self.grip.bind("<B1-Motion>", self.OnMotion)

def OnMotion(self, event):
x1 = self.winfo_pointerx()
y1 = self.winfo_pointery()
x0 = self.winfo_rootx()
y0 = self.winfo_rooty()
self.geometry("%sx%s" % ((x1-x0),(y1-y0)))
return

app=Example()
app.mainloop()

Grab Tkinter cursor

<Motion> only binds to the root window. If you move your mouse outside of root window, it no longer generates the x and y position of your mouse cursor.

For windows, what I can think of is to get the current position of your cursor periodically, and limit its position. The below will try to control the cursor x position within the root window.

from tkinter import *
from ctypes import windll, Structure, c_long, byref

root = Tk()
root.geometry("400x200")
user = windll.user32

class Position(Structure):
_fields_ = [("x", c_long), ("y", c_long)]

def ctrl_x_pos():
pt = Position()
windll.user32.GetCursorPos(byref(pt))
if pt.x < root.winfo_x()+5:
x = root.winfo_x()+5
y=pt.y
user.SetCursorPos(x, y)
elif pt.x > root.winfo_x()+405:
x = root.winfo_x()+405
y = pt.y
user.SetCursorPos(x, y)
root.after(5, ctrl_x_pos)

root.after(5,ctrl_x_pos)
root.bind('<Escape>',lambda e: root.destroy())
root.mainloop()


Related Topics



Leave a reply



Submit