Tkinter: Draw rectangle using a mouse - python

Please, help me to resolve this problem.
I want to allow the user to draw a random rectangle around a specific region of interest in a picture using the mouse ( by clicking the right or left button of the mouse until he releases it).
I deal with large images (images larger than the resolution of my screen, such as this one), so the user needs to scroll the window in order to be able to see the picture fully.
Here is the code I tried just to display a large picture, but I have no idea on how to allow the user to draw using his mouse a rectangle over an object (say a person in a picture):
from Tkinter import *
import Image,ImageTk
root=Tk()
canv=Canvas(root,relief=SUNKEN)
sbarv=Scrollbar(root,orient=VERTICAL)
sbarh=Scrollbar(root,orien=HORIZONTAL)
sbarv.config(command=canv.yview)
sbarh.config(command=canv.xview)
canv.config(yscrollcommand=sbarv.set)
canv.config(xscrollcommand=sbarh.set)
canv.grid(row=0,column=0,sticky=N+S+E+W)
sbarv.grid(row=0,column=1,sticky=N+S)
sbarh.grid(row=1,column=0,sticky=E+W)
im=Image.open("image.jpg")
width,height=im.size
canv.config(scrollregion=(0,0,width,height))
im2=ImageTk.PhotoImage(im)
imgtag=canv.create_image(0,0,anchor="nw",image=im2)
root.mainloop()
EDIT 1:
The rectangle must not be filled. I mean I want to draw only its 4 lines (segments) but it must be empty inside, I want to draw only its contours in one pixel width.
I also want to draw as the cursor is moving (dragging) not after button release.
Also, note that the rectangle to draw may be a long one, I mean the vertical scroll-bar will need to move down to be able to delimitate the whole object of interest (let's say it is a person)
Any help will be highly appreciated.
Thank you very much in advance
EDIT 2:
Following the link given to me above, I coded this. My problem is that the scroll-bars do not appear. May be someone could tell me why ?
Note that in this code, I resolved the first and second problems highlighted in EDIT 1:
import PIL.Image
import Image
import ImageTk
from Tkinter import *
class ExampleApp(Frame):
def __init__(self,master):
Frame.__init__(self,master=None)
self.x = self.y = 0
self.canvas = Canvas(master, cursor="cross")
self.sbarv=Scrollbar(self,orient=VERTICAL)
self.sbarh=Scrollbar(self,orient=HORIZONTAL)
self.sbarv.config(command=self.canvas.yview)
self.sbarh.config(command=self.canvas.xview)
self.canvas.config(yscrollcommand=self.sbarv.set)
self.canvas.config(xscrollcommand=self.sbarh.set)
self.canvas.grid(row=0,column=0,sticky=N+S+E+W)
self.sbarv.grid(row=0,column=1,stick=N+S)
self.sbarh.grid(row=1,column=0,sticky=E+W)
self.canvas.bind("<ButtonPress-1>", self.on_button_press)
self.canvas.bind("<B1-Motion>", self.on_move_press)
self.canvas.bind("<ButtonRelease-1>", self.on_button_release)
self.rect = None
self.start_x = None
self.start_y = None
self.im = PIL.Image.open("logo.png")
self.wazil,self.lard=self.im.size
self.canvas.config(scrollregion=(0,0,self.wazil,self.lard))
self.tk_im = ImageTk.PhotoImage(self.im)
self.canvas.create_image(0,0,anchor="nw",image=self.tk_im)
def on_button_press(self, event):
# save mouse drag start position
self.start_x = event.x
self.start_y = event.y
# create rectangle if not yet exist
#if not self.rect:
self.rect = self.canvas.create_rectangle(self.x, self.y, 1, 1, fill="")
def on_move_press(self, event):
curX, curY = (event.x, event.y)
# expand rectangle as you drag the mouse
self.canvas.coords(self.rect, self.start_x, self.start_y, curX, curY)
def on_button_release(self, event):
pass
if __name__ == "__main__":
root=Tk()
app = ExampleApp(root)
root.mainloop()

The Scrollbars do not show because you grid them into a Frame (self.sbarv=Scrollbar(self, ...)) which you do not place into the parent window. You directly grid the Canvas into the parent window though (self.canvas = Canvas(master, ...)).
What you should do is also put the Canvas in self and then pack the Frame into the master window using
app = ExampleApp(root)
app.pack()
However, when scrolling, the event.x and event.y do not represent the correct position on tha canvas anymore, so you should use
self.start_x = self.canvas.canvasx(event.x)
self.start_y = self.canvas.canvasy(event.y)
and
curX = self.canvas.canvasx(event.x)
curY = self.canvas.canvasy(event.y)
Then, I understand you want to automatically scroll the canvas when the mouse is dragging to one of the borders of the canvas? To do that, you need to check if the mouse is at one of the edges of the canvas and scroll in that direction if it is. You can use something like:
w, h = self.canvas.winfo_width(), self.canvas.winfo_height()
if event.x > 0.9*w:
self.canvas.xview_scroll(1, 'units')
elif event.x < 0.1*w:
self.canvas.xview_scroll(-1, 'units')
if event.y > 0.9*h:
self.canvas.yview_scroll(1, 'units')
elif event.y < 0.1*h:
self.canvas.yview_scroll(-1, 'units')
So, all that implemented in your code becomes:
import PIL.Image
import Image
import ImageTk
from Tkinter import *
class ExampleApp(Frame):
def __init__(self,master):
Frame.__init__(self,master=None)
self.x = self.y = 0
self.canvas = Canvas(self, cursor="cross")
self.sbarv=Scrollbar(self,orient=VERTICAL)
self.sbarh=Scrollbar(self,orient=HORIZONTAL)
self.sbarv.config(command=self.canvas.yview)
self.sbarh.config(command=self.canvas.xview)
self.canvas.config(yscrollcommand=self.sbarv.set)
self.canvas.config(xscrollcommand=self.sbarh.set)
self.canvas.grid(row=0,column=0,sticky=N+S+E+W)
self.sbarv.grid(row=0,column=1,stick=N+S)
self.sbarh.grid(row=1,column=0,sticky=E+W)
self.canvas.bind("<ButtonPress-1>", self.on_button_press)
self.canvas.bind("<B1-Motion>", self.on_move_press)
self.canvas.bind("<ButtonRelease-1>", self.on_button_release)
self.rect = None
self.start_x = None
self.start_y = None
self.im = PIL.Image.open("logo.png")
self.wazil,self.lard=self.im.size
self.canvas.config(scrollregion=(0,0,self.wazil,self.lard))
self.tk_im = ImageTk.PhotoImage(self.im)
self.canvas.create_image(0,0,anchor="nw",image=self.tk_im)
def on_button_press(self, event):
# save mouse drag start position
self.start_x = self.canvas.canvasx(event.x)
self.start_y = self.canvas.canvasy(event.y)
# create rectangle if not yet exist
if not self.rect:
self.rect = self.canvas.create_rectangle(self.x, self.y, 1, 1, outline='red')
def on_move_press(self, event):
curX = self.canvas.canvasx(event.x)
curY = self.canvas.canvasy(event.y)
w, h = self.canvas.winfo_width(), self.canvas.winfo_height()
if event.x > 0.9*w:
self.canvas.xview_scroll(1, 'units')
elif event.x < 0.1*w:
self.canvas.xview_scroll(-1, 'units')
if event.y > 0.9*h:
self.canvas.yview_scroll(1, 'units')
elif event.y < 0.1*h:
self.canvas.yview_scroll(-1, 'units')
# expand rectangle as you drag the mouse
self.canvas.coords(self.rect, self.start_x, self.start_y, curX, curY)
def on_button_release(self, event):
pass
if __name__ == "__main__":
root=Tk()
app = ExampleApp(root)
app.pack()
root.mainloop()

Related

Tkinter: Draw rectangle using a mouse, and crop a photo in the shape of that rectangle

I found this code online, and I changed it for my benefit, so it creates a rectangle and then it crops the photo in the shape of this rectangle. But, I've been facing a problem for a very long time: when the mouse is released, I can't use the image that I created and return it and use it in a variable. I think it's the section of on_button_release.
Basically, I would like to take a photo, then allow the user to draw a rectangle on the screen, crop this certain image in the rectangle shape and use it after wise in a variable as a PIL image.
Here's some code I found online after I changed it for my benefit:
import tkinter as tk
from tkinter import filedialog
import numpy
import PIL
from PIL import ImageTk, Image
from tkinter import *
class ExampleApp(Frame):
def __init__(self,master, file_path):
Frame.__init__(self,master=None)
self.file_path = file_path
self.x = self.y = 0
self.canvas = Canvas(self, cursor="cross")
self.sbarv=Scrollbar(self,orient=VERTICAL)
self.sbarh=Scrollbar(self,orient=HORIZONTAL)
self.sbarv.config(command=self.canvas.yview)
self.sbarh.config(command=self.canvas.xview)
self.canvas.config(yscrollcommand=self.sbarv.set)
self.canvas.config(xscrollcommand=self.sbarh.set)
self.canvas.grid(row=0,column=0,sticky=N+S+E+W)
self.sbarv.grid(row=0,column=1,stick=N+S)
self.sbarh.grid(row=1,column=0,sticky=E+W)
self.canvas.bind("<ButtonPress-1>", self.on_button_press)
self.canvas.bind("<B1-Motion>", self.on_move_press)
self.canvas.bind("<ButtonRelease-1>", self.on_button_release)
self.rect = None
self.start_x = None
self.start_y = None
self.im = PIL.Image.open(self.file_path)
self.wazil,self.lard=self.im.size
self.canvas.config(scrollregion=(0,0,self.wazil,self.lard))
self.tk_im = ImageTk.PhotoImage(self.im)
self.canvas.create_image(0,0,anchor="nw",image=self.tk_im)
def on_button_press(self,event):
# save mouse drag start position
self.start_x = self.canvas.canvasx(event.x)
self.start_y = self.canvas.canvasy(event.y)
# create rectangle if not yet exist
if not self.rect:
self.rect = self.canvas.create_rectangle(self.x, self.y, 1, 1, outline='red')
def on_move_press(self,event):
curX = self.canvas.canvasx(event.x)
curY = self.canvas.canvasy(event.y)
w, h = self.canvas.winfo_width(), self.canvas.winfo_height()
if event.x > 0.9*w:
self.canvas.xview_scroll(1, 'units')
elif event.x < 0.1*w:
self.canvas.xview_scroll(-1, 'units')
if event.y > 0.9*h:
self.canvas.yview_scroll(1, 'units')
elif event.y < 0.1*h:
self.canvas.yview_scroll(-1, 'units')
# expand rectangle as you drag the mouse
self.canvas.coords(self.rect, self.start_x, self.start_y, curX, curY)
self.im2 = self.im.crop((self.start_x, self.start_y, curX, curY))
self.tk_im2 = ImageTk.PhotoImage(self.im2)
def on_button_release(self, event):
self.canvas.delete("all")
self.canvas.create_image(0,0,anchor="nw",image=self.tk_im2)
if __name__ == "__main__":
file_path = 'images.jpg'
root=Tk()
app = ExampleApp(root, file_path = file_path)
app.pack()
root.mainloop()

Filling colour logically while drawing rectangle Tkinter

While using PIL draw rectangles on an image in the canvas. I wish to change the fill rectangle depending on the pixel color of my image in the canvas.
I have referred one of the cross posts (link pasted below) to create rectangles with mouse events in Tkinter.
create_rectangle fills the rectangle with a color specified with mouse events (example: the rectangle is filled with black in this example). Is there a way to logically change the fill color depending on the existing pixel color of the background image? I mean, while drawing a rectangle, I need only the white colored pixels of the background image to be turning red and the rest with a different color.
Drawing rectangle using mouse events in Tkinter
[1]: https://i.stack.imgur.com/W41kX.jpg
def rec_on_button_press(self,event):
self.start_x = event.x
self.start_y = event.y
self.rect=self.image_canvas.create_rectangle(self.x, self.y, 1, 1,fill=self.python_red)
def rec_on_move(self, event):
curX, curY = (event.x, event.y)
imagenp= np.array(image)
if imagenp[curY,curX]==255:
self.python_red="#EE204D"
else:
self.python_red=None
self.image_canvas.coords(self.rect, self.start_x, self.start_y, curX, curY)
def rec_on_button_release(self, event):
pass
Principle:
Crop the image you select,create it and show it.
The code need to be modified,the example of full code(the example is convert 255(white) to 0(black)):
import tkinter as tk # this is in python 3.4. For python 2.x import Tkinter
from PIL import Image, ImageTk
import numpy as np
import ctypes
ctypes.windll.shcore.SetProcessDpiAwareness(2) # for windows 10
class ExampleApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.x = self.y = 0
self.canvas = tk.Canvas(self, width=512, height=512, cursor="cross")
self.canvas.pack(side="top", fill="both", expand=True)
self.canvas.bind("<ButtonPress-1>", self.on_button_press)
self.canvas.bind("<B1-Motion>", self.on_move_press)
self.canvas.bind("<ButtonRelease-1>", self.on_button_release)
self.rect = None
self.start_x = None
self.start_y = None
self.python_red = None
self.im = Image.open(r'ImagePath').convert("L")
self.move_imageID = None
self.move_image = None
self.tk_im = ImageTk.PhotoImage(self.im)
self.canvas.create_image(0,0,anchor="nw",image=self.tk_im)
def on_button_press(self, event):
# save mouse drag start position
self.start_x = event.x
self.start_y = event.y
self.rect = self.canvas.create_rectangle(self.x, self.y, 0, 0, outline="black")
def on_move_press(self, event):
self.canvas.delete(self.move_imageID)
crop_image = self.im.crop((self.start_x,self.start_y,event.x,event.y))
imageArray = np.array(crop_image)
if imageArray.shape:
for i in range(imageArray.shape[0]):
for j in range(imageArray.shape[1]): # convert the white pixel to black
if imageArray[i,j] == 255:
imageArray[i,j] = 0
self.move_image = Image.fromarray(imageArray)
self.move_image = ImageTk.PhotoImage(self.move_image)
self.move_imageID = self.canvas.create_image(self.start_x,self.start_y,anchor="nw",image=self.move_image)
self.canvas.coords(self.rect,self.start_x,self.start_y,event.x,event.y)
self.canvas.lift(self.rect)
def on_button_release(self, event):
pass
if __name__ == "__main__":
app = ExampleApp()
app.mainloop()
Now:
Select area:
Principle: Convert image to numpy array and use opencv to draw rectangles. After processing pixel colors convert back to image.
def rec_on_button_press(self,event):
self.start_x = event.x
self.start_y = event.y
self.rect=self.image_canvas.create_rectangle(self.x, self.y, 1, 1,fill=self.python_red)
def rec_on_move(self, event):
curX, curY = (event.x, event.y)
cropimage=originalimage.crop((self.start_x,self.start_y,curX,curY))
cropimagergb=cropimage.convert('RGB')
cropimagergb.save('crop1.png')
imagergbcv2=cv2.imread('crop1.png')
if imagergbcv2.shape[:2]:
for i in range(imagergbcv2.shape[0]):
for j in range(imagergbcv2.shape[1]):
color=imagergbcv2[i,j]
if (color[0]==255 and color[1]==255 and color[2]==255):
imagergbcv2[i,j]=(0,255,0)
cv2.imwrite('crop2.png',imagergbcv2)
croppedimage=cv2.imread('crop2.png')
originalimage[self.start_y:self.start_y+croppedimage.shape[0],self.start_x:self.start_x+croppedimage.shape[1]]=croppedimage
cv2.imwrite('originalpluscropped.png',originalimage)
self.image_canvas.coords(self.rect, self.start_x, self.start_y, curX, curY)
def rec_on_button_release(self, event):
pass
self.image=Image.open('originalpluscropped.png')

Basic image editing on Python tkinter working but unwanted image scrolling course when dragging mouse

The following code produces a nice Canvas with an image and I can draw a square on top of it. However:
a) I can't get the Canvas to not scroll.
b) I only want the image to appear and nothing else and can't get the sizes right
As you will see, I have even tried to stop the scrolling but it does not work all the time. In addition the image is never fully aligned with the Canvas nor the window even though I set the sizes to be the same for the three (root, canvas and image).
Here is the code (partly taken already from another example with some portions commented out):
try:
from PIL import Image
except ImportError:
import Image
from PIL import ImageTk
try:
import Tkinter as tk # Python2
except ImportError:
import tkinter as tk # Python3s
import Tkinter
from Tkinter import *
import PIL as PILAll
class ExampleApp(Frame):
def __init__(self,master):
Frame.__init__(self,master=None)
self.x = 0
self.y = 0
self.canvas = Canvas(self, cursor="cross", width=640, height=480, confine=True, scrollregion=(10, 10, 10, 10), relief="groove", bg="blue")# and I have experimented with a few other options
#self.sbarv=Scrollbar(self,orient=VERTICAL)
#self.sbarh=Scrollbar(self,orient=HORIZONTAL)
#self.sbarv.config(command=self.canvas.yview)
#self.sbarh.config(command=self.canvas.xview)
self.canvas.config()#yscrollcommand=self.sbarv.set)
self.canvas.config()#xscrollcommand=self.sbarh.set)
self.canvas.config(scrollregion=self.canvas.bbox(ALL))
self.canvas.grid(row=0,column=0,sticky=N+S+E+W)
#self.sbarv.grid(row=0,column=1,stick=N+S)
#self.sbarh.grid(row=1,column=0,sticky=E+W)
self.canvas.bind("<ButtonPress-1>", self.on_button_press)
self.canvas.bind("<B1-Motion>", self.on_move_press)
self.canvas.bind("<ButtonRelease-1>", self.on_button_release)
self.canvas.bind("<Leave>", self.on_button_leave)
self.canvas.bind("<Enter>", self.on_button_enter)
self.canvas.bind("<Double-Button-1>", self.on_double_click)
self.canvas.create_line(0, 0, 200, 100)
self.canvas.create_line(0, 100, 200, 0, fill="red", dash=(4, 4))
self.canvas.create_rectangle(50, 25, 150, 75, fill="blue")
self.rect = None
self.text = None
self.start_x = None
self.start_y = None
self.im = PILAll.Image.open("../../" + "image6.JPG")
self.wazil,self.lard=self.im.size
self.canvas.config() #scrollregion=(0,0,self.wazil,self.lard))
self.tk_im = ImageTk.PhotoImage(self.im)
self.canvas.create_image(0,0,anchor="nw",image=self.tk_im)
out_of_scope = 1
def on_button_leave(self, event):
self.out_of_scope = 2
print "out_of_scope....", self.out_of_scope
def on_button_enter(self, event):
print("entering...")
self.out_of_scope = 1
def on_double_click(self, event):
print("double click")
def on_button_press(self, event):
# save mouse drag start position
self.start_x = self.canvas.canvasx(event.x)
self.start_y = self.canvas.canvasy(event.y)
# create rectangle if not yet exist
if not self.rect:
if self.out_of_scope == 1:
self.rect = self.canvas.create_rectangle(self.x, self.y, 1, 1, outline='blue', fill='yellow') #since it's only created once it always remains at the bottom
def get_out_of_scope(self, x, y):
return self.out_of_scope
def on_move_press(self, event):
curX = self.canvas.canvasx(event.x)
curY = self.canvas.canvasy(event.y)
var=self.get_out_of_scope(event.x, event.y)
print(var, event.x, event.y)
if var == 1:
w, h = self.canvas.winfo_width(), self.canvas.winfo_height()
if event.x > 0.9*w:
self.canvas.xview_scroll(1, 'units')
elif event.x < 0.1*w:
self.canvas.xview_scroll(-1, 'units')
if event.y > 0.9*h:
self.canvas.yview_scroll(1, 'units')
elif event.y < 0.1*h:
self.canvas.yview_scroll(-1, 'units')
# expand rectangle as you drag the mouse
self.canvas.coords(self.rect, self.start_x, self.start_y, curX, curY)
def on_button_release(self, event):
print(event.x, event.y)
pass
root=Tk()
root.geometry("640x480")
app = ExampleApp(root)
app.grid()
root.mainloop()
I think your code would benefit from beingreviewed but I will try to limit myself to the question...
If the canvas needs to be the same size as the image why is it constructed with width=640, height=480? You figure out the width and height of the image further down:
self.im = PILAll.Image.open("../../" + "image6.JPG")
self.wazil,self.lard=self.im.size
(interesting variable name choice btw) so if self.wazil and self.lard represent the width and height of the image why don't you make that the width and height of the canvas?
self.im = PILAll.Image.open("../../" + "image6.JPG")
self.wazil,self.lard=self.im.size
self.canvas = Canvas(self, width=self.wazil, height=self.lard) #, ...)
then the canvas will be the correct size but the root window is still forcing itself to be 640x480 from:
root.geometry("640x480")
but since widgets will automatically scale themselves to the contents you can just comment that line out and it should be the correct size.
#root.geometry("640x480")
I should note that I was experiencing some very odd behaviour about the position of the image being 3 pixels too high and 3 pixels to the left, drawing the image with:
self.canvas.create_image(3,3,anchor="nw",image=self.tk_im)
fixed it for me but I have no idea why...
As for the scrolling you removed the parts about the scroll bars but you left in this in on_move_press:
w, h = self.canvas.winfo_width(), self.canvas.winfo_height()
if event.x > 0.9*w:
self.canvas.xview_scroll(1, 'units')
elif event.x < 0.1*w:
self.canvas.xview_scroll(-1, 'units')
if event.y > 0.9*h:
self.canvas.yview_scroll(1, 'units')
elif event.y < 0.1*h:
self.canvas.yview_scroll(-1, 'units')
This is the section of code that is occasionally scrolling the canvas (happened when I tried to resize the window) so again you can comment that section out and it shouldn't scroll anymore.
Last note which is unrelated to question, you have:
def __init__(self,master):
Frame.__init__(self,master=None)
But I'm pretty sure you mean to have:
def __init__(self,master=None):
Frame.__init__(self,master)
since the first way you require a master argument but do not pass it to Frame.__init__. When Frame.__init__ receives a master of None it just uses the Tk instance which in your case is the same thing but if you used any other master it would cause very odd issues.

Drawing rectangle using mouse events in Tkinter

This is with respect to Draw rectangle on mouse click [Python]. I tried out the first solution and it works perfectly. Can someone please tell me, what to do if I want to see the rectangle being drawn and the rectangle gets fixed once the mouse button is released as I can only see the drawn rectangle once the button is released.
Any kind of help will be appreciated.
I amended the code from the referenced question. Now it shows the rectangle when mouse is being dragged on the canvas. The canvas displays Lena image.
import tkinter as tk # this is in python 3.4. For python 2.x import Tkinter
from PIL import Image, ImageTk
class ExampleApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.x = self.y = 0
self.canvas = tk.Canvas(self, width=512, height=512, cursor="cross")
self.canvas.pack(side="top", fill="both", expand=True)
self.canvas.bind("<ButtonPress-1>", self.on_button_press)
self.canvas.bind("<B1-Motion>", self.on_move_press)
self.canvas.bind("<ButtonRelease-1>", self.on_button_release)
self.rect = None
self.start_x = None
self.start_y = None
self._draw_image()
def _draw_image(self):
self.im = Image.open('./resource/lena.jpg')
self.tk_im = ImageTk.PhotoImage(self.im)
self.canvas.create_image(0,0,anchor="nw",image=self.tk_im)
def on_button_press(self, event):
# save mouse drag start position
self.start_x = event.x
self.start_y = event.y
# create rectangle if not yet exist
#if not self.rect:
self.rect = self.canvas.create_rectangle(self.x, self.y, 1, 1, fill="black")
def on_move_press(self, event):
curX, curY = (event.x, event.y)
# expand rectangle as you drag the mouse
self.canvas.coords(self.rect, self.start_x, self.start_y, curX, curY)
def on_button_release(self, event):
pass
if __name__ == "__main__":
app = ExampleApp()
app.mainloop()
The black square is drawn on top of the image. Hope this helps.

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

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):
window.bind( '<Button-1>', onMouseDown ) to capture the initial position of the mouse.
window.bind( '<Motion-1>', onMouseMove ) to track position of mouse once it starts to move.
Calculate how much mouse has moved and calculate newX, newY positions.
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?
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()
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()
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
Try this, and it surely works;
Create an event function to move window:
def movewindow(event):
root.geometry('+{0}+{1}'.format(event.x_root, event.y_root))
Bind window:
root.bind('', movewindow)
Now you can touch the the window and drag
This code is the same as Bryan's solution but it does not use overridedirect.
It was tested with: python 3.7, Debian GNU/Linux 10 (buster), Gnome 3.30
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.resizable(0, 0) # Window not resizable
self.wm_attributes('-type', 'splash') # Hide title bar (Linux)
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.StartMove)
self.grip.bind("<ButtonRelease-1>", self.StopMove)
self.grip.bind("<B1-Motion>", self.OnMotion)
def StartMove(self, event):
self.x = event.x
self.y = event.y
def StopMove(self, event):
self.x = None
self.y = None
def OnMotion(self, event):
deltax = event.x - self.x
deltay = event.y - self.y
x = self.winfo_x() + deltax
y = self.winfo_y() + deltay
self.geometry("+%s+%s" % (x, y))
app = App()
app.mainloop()

Categories