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() 否则矩形会在你的图片后面。

输出: