Reputation: 159
I want to create a custom rectangle widget(as shown below) that can resize just by clicking and motioning the mouse. I created a class for widget:
from tkinter import *
class Rect(Canvas):
def __init__(self, parent,x1,y1,x2,y2,color = 'yellow',transparentcolor = 'grey', default="", **kwargs):
Canvas.__init__(self, parent)
self.parent=parent
self.canvas = Canvas(parent, width = x2+10,height = y2+10,bg='grey',cursor='hand2')
self.current= self.canvas
self.rect = self.canvas.create_rectangle(x1,y1,x2,y2, width=5,outline=color)
self.corner1 = self.canvas.create_oval(x1-10,y1-10,x1+10,y1+10,fill=color) # Top-left
self.corner2 = self.canvas.create_oval(x2-10,y1-10,x2+10,y1+10,fill=color) # Top-right
self.corner3 = self.canvas.create_oval(x1-10,y2-10,x1+10,y2+10,fill=color) # Below-left
self.corner4 = self.canvas.create_oval(x2-10,y2-10,x2+10,y2+10,fill=color) # Below-right
self.canvas.grid()
Output:
Here is my full code:
from tkinter import *
from custom_rect import Rect
from tkinter import Canvas
x1 = 12
y1 = 12
x2 = 400
y2 = 400
class DrawCircles(Frame):
def __init__(self, master=None, **kwargs):
super().__init__(master, **kwargs)
self.image = Canvas(self, width=800,height=800)
self.rect = Rect(self.image,x1,y1,x2,y2,color='green')
self.image.tag_bind(self.rect, '<Button-1>', self.on_click_rectangle)
self.image.tag_bind(self.rect, '<Button1-Motion>', self.on_motion)
def on_click_rectangle(self, tag, event):
self.current = tag
global x1,x2,y1,y2
if abs(event.x-x1) < abs(event.x-x2):
x1, x2 = x2, x1
if abs(event.y-y1) < abs(event.y-y2):
y1, y2 = y2, y1
self.start = x1, y1
print(x1,y1,x2,y2)
def on_motion(self, event):
self.coords(self.rect, *self.start, event.x, event.y)
def main():
main = DrawCircles()
main.pack()
main.mainloop()
if __name__ == '__main__':
main()
But when I run this code, I got an error.
_tkinter.TclError: invalid boolean operator in tag search expression
I'm not sure but is the error occurs from the moving part?
Upvotes: 0
Views: 787
Reputation: 3079
For what you are trying to achieve you shouldn't be making your window transparent. You could just create a normal rectangle and points(ovals) on each corner of the rectangles and add tags.
You also, need to bind button press, motion, and keep checking if the mouse is inside the rectangle or inside the 4 points.
In the below code I'll be showing you how to create a resizable rectangle, most of the code is taken from my previous post :
import tkinter as tk
from PIL import Image, ImageTk
class Canvas(tk.Canvas):
TOP_LEFT = 0
TOP_RIGHT = 1
BOTTOM_LEFT = 3
BOTTOM_RIGHT = 4
cursors = {TOP_LEFT: 'size_nw_se', TOP_RIGHT: 'size_ne_sw', BOTTOM_LEFT: 'size_ne_sw', BOTTOM_RIGHT: 'size_nw_se'} # WINDOWS SPECIFIC CURSORS IN MAC IT MIGHT BE resizetopright, resizetopright etc
def __init__(self, *args, **kwargs):
super(Canvas, self).__init__(*args, **kwargs)
self.config(bg='#1e1e1e')
self._tag = 'resize' # not necessary you can remove all the tags
self.resizePoints = {} # stores the resize points
self.previous = (0, 0) # previous mouse coordinates
self.bind('<Motion>', self.updateCursor)
self.bind('<1>', self.setResizePoint)
self.bind('<ButtonRelease-1>', self.release)
self.createResizeRect()
def createResizeRect(self): # adds a rect around the canvas item
color = '#008000'
self._current_resize_rect = self.create_rectangle(80, 50, 100, 100, tags=(self._tag), outline=color, width=3) # draws rectangle
bbox = self.bbox(self._current_resize_rect)
# the below are the points at 4 corners of resize rect
self.resizePoints[self.TOP_LEFT] = self.create_oval(bbox[0]-5, bbox[1]-5, bbox[0]+5, bbox[1]+5, fill=color, tags=(self._tag))
self.resizePoints[self.TOP_RIGHT] = self.create_oval(bbox[2]-5, bbox[1]-5, bbox[2]+5, bbox[1]+5, fill=color, tags=(self._tag))
self.resizePoints[self.BOTTOM_RIGHT] = self.create_oval(bbox[2]-5, bbox[3]-5, bbox[2]+5, bbox[3]+5, fill=color, tags=(self._tag))
self.resizePoints[self.BOTTOM_LEFT] = self.create_oval(bbox[0]-5, bbox[3]-5, bbox[0]+5, bbox[3]+5, fill=color, tags=(self._tag))
def updateCursor(self, event): # method that updates cursor when hovering over resize points
point = self.checkInPoints(event.x, event.y)
if point:
key = list(self.resizePoints.keys())[list(self.resizePoints.values()).index(point)]
self.config(cursor=self.cursors[key])
else:
self.config(cursor='')
def checkInPoints(self, x, y): # checks if the mouse is over the resizePoints
for item in self.resizePoints.values():
if self.check_in_bbox(item, x, y):
return item
return None
def check_in_bbox(self, item, x, y): # checks if (x, y) points are inside the bounding box
box = self.bbox(item)
return box[0] < x < box[2] and box[1] < y < box[3]
def setResizePoint(self, event):
self._current_point = self.checkInPoints(event.x, event.y)
if self._current_point is not None:
self.bind('<B1-Motion>', self.resize)
else:
self.previous = (event.x, event.y)
self.bind('<B1-Motion>', self.moveItem)
def release(self, event):
self.tag_unbind(self._tag, '<B1-Motion>')
self.unbind('<B1-Motion>')
def moveItem(self, event): # moves the canvas item
xc, yc = self.canvasx(event.x), self.canvasy(event.y)
self.move(self._current_resize_rect, xc-self.previous[0], yc-self.previous[1])
self.updateResizeRect()
self.previous = (xc, yc)
def updateResizeRect(self): # updates the position of the resize rectangle
new_coord = self.bbox(self._current_resize_rect)
# note: depending on your tkinter version moveto might not be available. So use the .coords method
# eg: coords(self.resizePoints[self.TOP_LEFT], new_coords[0]-5, new_coords[1]-5, new_coords[0]+5,new_coords[1]+5)
# check how the coords are assigned in the addRect method and adjust accordingly if your tkinter version does't have `moveto`
self.moveto(self.resizePoints[self.TOP_LEFT], new_coord[0]-5, new_coord[1]-5)
self.moveto(self.resizePoints[self.TOP_RIGHT], new_coord[2]-5, new_coord[1]-5)
self.moveto(self.resizePoints[self.BOTTOM_RIGHT], new_coord[2]-5, new_coord[3]-5)
self.moveto(self.resizePoints[self.BOTTOM_LEFT], new_coord[0]-5, new_coord[3]-5)
def resize(self, event): # resizes the canvas item
item_coords = self.coords(self._current_resize_rect)
if self.resizePoints[self.TOP_LEFT] == self._current_point:
self.coords(self._current_resize_rect, event.x, event.y, item_coords[2], item_coords[3])
elif self.resizePoints[self.TOP_RIGHT] == self._current_point:
self.coords(self._current_resize_rect, item_coords[0], event.y, event.x, item_coords[3])
elif self.resizePoints[self.BOTTOM_RIGHT] == self._current_point:
self.coords(self._current_resize_rect, item_coords[0], item_coords[1], event.x, event.y)
elif self.resizePoints[self.BOTTOM_LEFT] == self._current_point:
self.coords(self._current_resize_rect, event.x, item_coords[1], item_coords[2], event.y)
self.updateResizeRect()
root = tk.Tk()
canvas = Canvas(root)
canvas.pack(fill='both', expand=True)
ph_image = tk.PhotoImage(file=r"image.png")
canvas.create_image(50, 50, image=ph_image)
canvas.tag_raise(canvas._tag)
root.mainloop()
tag_raise()
else the rectangle will be behind your image.output:
Upvotes: 1