【问题标题】:How to move multiple objects together in tkinter canvas? [duplicate]如何在 tkinter 画布中一起移动多个对象? [复制]
【发布时间】:2021-08-27 15:32:13
【问题描述】:

我正在尝试使用鼠标拖放在画布周围移动一些带有文本的矩形。我使用 find_overlapping 来选择要移动的矩形。这意味着最初作为类对象 Rect 的一部分创建的文本不会被移动。有没有办法修改我的代码以移动类对象中的所有对象,或者使用 find_overlapping 找到类对象 ID?

矩形上的文本可以相同,如示例所示。使用随机标签标记类对象中的所有元素以将它们组合在一起是我的第一个想法,但使用 find_ovelapping 检索此类标签信息并未成功。

import tkinter as tk

root=tk.Tk()
PAB=tk.Canvas(width=400, height=400)

#checks if a certain canvas object has a certain tag
def hastag(tag, id):
    if any(tag in i for i in PAB.gettags(id)):return True
    else:return False


class Rect:
    def __init__(self, x1, y1, name):
        rec = PAB.create_rectangle(x1,y1,x1+40,y1+40, fill='#c0c0c0', tag=('movable', name))
        text = PAB.create_text(x1+20,y1+20, text=name)
#mouse click find object to move
def get_it(event):
    delta=5
    global cur_rec
    for i in PAB.find_overlapping(event.x-delta, event.y-delta, event.x+delta, event.y-delta):
        if hastag('movable', i):
            cur_rec = i
    
PAB.bind('<Button-1>', get_it)

#mouse movement moves object
def move_it(event):
    xPos, yPos = event.x, event.y
    xObject, yObject = PAB.coords(cur_rec)[0],PAB.coords(cur_rec)[1]
    PAB.move(cur_rec, xPos-xObject, yPos-yObject)
PAB.bind('<B1-Motion>', move_it)

#test rects
bob = Rect(20,20,'Bob')
rob = Rect(80,80,'Rob')
different_bob = Rect(160,160,'Bob')

PAB.pack()
root.mainloop()

谢谢。如果需要任何澄清,我很乐意提供帮助。

【问题讨论】:

    标签: python tkinter canvas


    【解决方案1】:

    更好的方法是对要一起移动的所有项目使用相同的标签,因此在您的情况下,矩形和文本必须具有相同的标签。

    import tkinter as tk
    
    root=tk.Tk()
    PAB=tk.Canvas(width=400, height=400, bg="gray")
    
    class Rect:
        def __init__(self, x1, y1, name):
    
            tag = f"movable{id(self)}"
            rec = PAB.create_rectangle(x1,y1,x1+40,y1+40, fill='#c0c0c0', tag=(tag, ))
            text = PAB.create_text(x1+20,y1+20, text=name, tag=(tag,))
    
    def in_bbox(event, item):  # checks if the mouse click is inside the item
        bbox = PAB.bbox(item)
    
        return bbox[0] < event.x < bbox[2] and bbox[1] < event.y < bbox[3]
        
    #mouse click find object to move
    def get_it(event):
        delta=5
        global cur_rec
        cur_rec = PAB.find_closest(event.x, event.y)  # returns the closest object
    
        if not in_bbox(event, cur_rec):  # if its not in bbox then sets current_rec as None
            cur_rec = None
    
    #mouse movement moves object
    def move_it(event):
        if cur_rec:
            xPos, yPos = event.x, event.y
            xObject, yObject = PAB.coords(cur_rec)[0],PAB.coords(cur_rec)[1]
                    
            PAB.move(PAB.gettags(cur_rec)[0], xPos-xObject, yPos-yObject) 
    
    PAB.bind('<Button-1>', get_it)
    PAB.bind('<B1-Motion>', move_it)
    #test rects
    bob = Rect(20,20,'Bob')
    rob = Rect(80,80,'Rob')
    different_bob = Rect(160,160,'Bob')
    
    PAB.pack()
    root.mainloop()
    
    

    【讨论】:

    • 这正是我想要的!能否详细说明为什么要重写 get_it 函数?
    【解决方案2】:

    这会起作用,但我不确定这是不是最好的方法。

    基本上,它使用文本添加到画布中矩形之后的事实,因此可以使用cur_rec+1 识别它。

    def move_it(event):
        xPos, yPos = event.x, event.y
        xObject, yObject = PAB.coords(cur_rec)[0],PAB.coords(cur_rec)[1]
        # move rectangle
        PAB.move(cur_rec, xPos-xObject, yPos-yObject)
        # move text associated with rectangle
        PAB.move(cur_rec+1, xPos-xObject, yPos-yObject)
    

    【讨论】:

    • 首先,这非常适合我的问题示例!如此简洁而简短的解决方案!我只是担心:1)如果我需要移动一个没有文本的矩形,这也会移动下一个元素 2)这意味着所有矩形中必须有相同数量的文本实例
    • @PēterisAndrušaitis 你在这里添加新的额外要求对回答提供者来说是不公平的。
    • @acw1668 绝对公平,就提供的代码示例而言,答案是 100% 足够的。我应该编辑问题以反映额外要求吗?
    • @PēterisAndrušaitis 如果答案 100% 足够,您应该接受它作为您问题的答案。然后,您可以针对额外要求提出另一个问题。
    • @acw1668 我理解你的逻辑,但我认为通过给画布对象标签 self.id 来接受一般情况的答案对于未来寻找同类问题答案的人更有利。
    【解决方案3】:

    对文本和矩形应用相同的标签。然后,在调用move 时使用该标签。这是一种方法:

    class Rect:
        def __init__(self, x1, y1, name):
            identifier = f"id:{id(self)}"
            rec = PAB.create_rectangle(x1,y1,x1+40,y1+40, fill='#c0c0c0', tags=('movable', name, identifier))
            text = PAB.create_text(x1+20,y1+20, text=name, tags=('movable', identifier))
    

    然后您可以返回标识符而不是所选项目的索引:

    def get_it(event):
        delta=5
        global cur_rec
        for i in PAB.find_overlapping(event.x-delta, event.y-delta, event.x+delta, event.y-delta):
            if hastag('movable', i):
                identifier = [tag for tag in PAB.gettags(i) if tag.startswith("id:")][0]
                cur_rec = identifier
    

    【讨论】:

    • 谢谢,这很好用!为什么[tag for tag in PAB.gettags(i) if tag.startswith("id:")] 必须是一个列表,如果我知道它只会是一个元素? ps 你在这里的修补匠相关答案是这个项目的天赐之物
    • @PēterisAndrušaitis:你可能会得到多个标签 - 例如,如果你点击元素,除了你添加的任何其他标签之外,它还会有标签“当前”。
    • 这可能只是我对python语法的了解不足,但这和写for tag in PAB.gettags(id): if tag.startswith('id:'): identifier = tag一样吧?
    • @PēterisAndrušaitis 叫做列表理解
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多