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
How to Read a Column of CSV as Dtype List Using Pandas
Check If Value Already Exists Within List of Dictionaries
Using the Multiprocessing Module for Cluster Computing
Loop Over a List Containing Path to Sound Files
List All Base Classes in a Hierarchy of Given Class
Sqlalchemy Unique Across Multiple Columns
How to Display a 3D Plot of a 3D Array Isosurface in Matplotlib Mplot3D or Similar
Matplotlib: Adding an Axes Using the Same Arguments as a Previous Axes
Login to Website Using Urllib2 - Python 2.7
How to Initialize the Base (Super) Class
Pandas: Change Data Type of Series to String
From ... Import or Import ... as for Modules
Python - Read File from and to Specific Lines of Text