Tkinter 自定义矩形小部件

问题描述

我想创建一个自定义矩形小部件(如下所示),只需单击并移动鼠标即可调整大小。我为小部件创建了一个类:

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,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,x2+10,fill=color) # Top-right
        self.corner3 = self.canvas.create_oval(x1-10,y2-10,y2+10,fill=color) # Below-left
        self.corner4 = self.canvas.create_oval(x2-10,fill=color) # Below-right
        self.canvas.grid()

输出

enter image description here

这是我的完整代码

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,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,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,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

我不确定,但错误是由运动部件引起的吗?

解决方法

对于您想要实现的目标,您不应该使窗口透明。您可以在矩形的每个角上创建一个普通矩形和点(椭圆)并添加标签。

您还需要绑定按钮按下、运动,并不断检查鼠标是在矩形内还是在 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,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[2]+5,tags=(self._tag))
        self.resizePoints[self.BOTTOM_RIGHT] = self.create_oval(bbox[2]-5,bbox[3]-5,bbox[3]+5,tags=(self._tag))
        self.resizePoints[self.BOTTOM_LEFT] = self.create_oval(bbox[0]-5,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,y):
                return item

        return None

    def check_in_bbox(self,item,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[3]-5)
        self.moveto(self.resizePoints[self.BOTTOM_LEFT],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],item_coords[3])

        elif self.resizePoints[self.BOTTOM_RIGHT] == self._current_point:
            self.coords(self._current_resize_rect,item_coords[1],event.y)

        elif self.resizePoints[self.BOTTOM_LEFT] == self._current_point:
            self.coords(self._current_resize_rect,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,image=ph_image)
canvas.tag_raise(canvas._tag)
root.mainloop()
  • 不要忘记 tag_raise() 否则矩形将在您的图像后面。

输出:

enter image description here