Tkinter 自定义矩形小部件
Tkinter Custom Rectangle widget
我想创建一个自定义矩形小部件(如下所示),只需单击并移动鼠标即可调整大小。我为小部件创建了一个 class:
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()
输出:
这是我的完整代码:
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()
但是当我运行这段代码时,我得到了一个错误。
_tkinter.TclError: invalid boolean operator in tag search expression
我不确定,但错误是从运动部件发生的吗?
对于您想要实现的目标,您不应该让 window 透明。您可以只创建一个普通矩形并在矩形的每个角上添加点(椭圆)并添加标签。
您还需要绑定按钮按下、动作,并不断检查鼠标是在矩形内还是在 4 个点内。
在下面的代码中,我将向您展示如何创建一个可调整大小的矩形,大部分代码来自我的 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()
否则矩形会在你的图片后面。
输出:
我想创建一个自定义矩形小部件(如下所示),只需单击并移动鼠标即可调整大小。我为小部件创建了一个 class:
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()
输出:
这是我的完整代码:
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()
但是当我运行这段代码时,我得到了一个错误。
_tkinter.TclError: invalid boolean operator in tag search expression
我不确定,但错误是从运动部件发生的吗?
对于您想要实现的目标,您不应该让 window 透明。您可以只创建一个普通矩形并在矩形的每个角上添加点(椭圆)并添加标签。
您还需要绑定按钮按下、动作,并不断检查鼠标是在矩形内还是在 4 个点内。
在下面的代码中,我将向您展示如何创建一个可调整大小的矩形,大部分代码来自我的 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()
否则矩形会在你的图片后面。
输出: