【问题标题】:keeping track of own objects on tkinter canvas在 tkinter 画布上跟踪自己的对象
【发布时间】:2017-07-13 01:41:09
【问题描述】:

到目前为止,每当我需要在 tkinter 画布上处理多个形状时,它们都只是形状。我用canvas.find_all() 获取他们的标签,并通过移动、调整大小等来操纵他们的几何形状。
但是我遇到了这个问题,我似乎无法像这样解决。
如果我定义了一个自己的类并将这个对象绘制到画布上,我如何跟踪画布上的所有对象,以便调用它们的方法?

假设我定义了一个Bubble 类,它在屏幕上绘制了一个气泡。每一秒后,我希望它使用他们的change_colour 方法将所有气泡的颜色更改为另一种颜色。

my_list = []
for n in range(10):
    bubble = Bubble()
    my_list.append(bubble)

while True:
    time.sleep(1)
    for item in my_list:
        item.change_colour()

我可以将它附加到一个大的 'ol 列表中,然后像我在这里做的那样迭代它,但是对于有更多对象的情况,这太慢了!
这样做的正确方法是什么?
像往常一样,感谢您的帮助!

正如所指出的,time.sleep() 没有任何意义,但这不是我要解决的问题。

【问题讨论】:

  • 一般来说,在你的 Tkinter 程序中使用无限循环或sleep 命令是个坏主意。窗口不会更新,用户命令也不会被注册,直到你的函数结束并且控制返回到主循环。如果您希望定期发生某些事情,请使用root.afterroot.after_idle。试试看,看看你的 for item in my_list 方法是否真的是问题所在。
  • 另外,如果您在此处共享的代码实际上不是您正在运行的代码,它只是您在认为效率太低之前考虑编写的代码:过早优化是万恶之根。除非您编写并运行它并对其进行测量,否则您无法确定代码到底有多快。
  • 感谢您的建议,这不是我正在使用的实际代码。我用它来尽可能清楚地展示我的问题,但它与问题并不真正相关。但是感谢您指出这一点。
  • 我正在编写的代码是一个星际模拟器,我在画布上最多可以有 1000 个项目,并且列表变得不切实际。但是,我想这应该仍然可以以某种方式进行管理,因为它们是许多基本操作
  • 为什么包含 1000 个项目的列表不切实际?保留一个列表绝对是最简单的解决方案,1000 个项目完全在 python 可以处理的范围内。

标签: python object canvas tkinter


【解决方案1】:

我的建议是给你创建的每个项目至少两个标签。一个标签是“气泡”,以便您可以一次引用所有气泡,第二个标签是每个气泡的唯一标签。

例如:

class Bubble():
    def __init__(...):
        self.tag = "b-%d" % id(self)
        ...
        canvas.create_oval(..., tags=("bubble", self.tag))
        ...

这样,您可以在Bubble 类上实现change_color 方法,如下所示,这将更改由该类实例创建的所有画布项:

def change_color(self, color):
    canvas.itemconfigure(self.tag, fill=color)

然后您可以像这样创建一个红色气泡:

bubble = Bubble()
bubble.change_color("red")

这还允许您使用“气泡”标签一次更改所有气泡:

canvas.itemconfigure("bubble", outline="blue")

如果您希望气泡闪烁,您应该创建while 循环。相反,利用已经在运行的循环。

通过创建一个可以执行任何操作的函数来实现此目的,然后通过after 让该函数自行安排再次运行。例如:

def blink(color="red"):
    canvas.itemconfigure("bubble", fill=color)
    new_color = "red" if color == "white" else "white"
    canvas.after(1000, blink, new_color)

只要程序运行,这将导致所有气泡每秒闪烁一次。

【讨论】:

    【解决方案2】:

    如果您想对每个项目执行自定义的单独更改(例如,将每个项目的颜色更改为全新的随机颜色),那么您最好的办法就是遍历每个项目并单独调用 itemconfig .

    但是,如果您想对每个项目进行相同的更改,您可以标记您的项目并调用itemconfig 一次,使用该标记作为您的说明符。

    例子:

    import Tkinter
    import random
    
    root = Tkinter.Tk()
    canvas = Tkinter.Canvas(root, width=400, height=400)
    canvas.pack()
    
    for i in range(1000):
        x = random.randint(0, 400)
        y = random.randint(0, 400)
        canvas.create_oval((x-5,y-5,x+5,y+5), fill="white", tags=("bubble"))
    
    current_color = "white"
    def change_colors():
        global current_color
        current_color = "white" if current_color == "black" else "black"
        canvas.itemconfig("bubble", fill = current_color)
        root.after(1000, change_colors)
    
    root.after(1000, change_colors)
    root.mainloop()
    

    结果:

    但是,正如我在之前的评论中指出的那样,我仍然认为这是一种过早的优化。即使您有一千个项目,遍历它们并单独配置它们也不会比使用标签慢得多。示例:

    import Tkinter
    import random
    
    root = Tkinter.Tk()
    canvas = Tkinter.Canvas(root, width=400, height=400)
    canvas.pack()
    
    items = []
    for i in range(1000):
        x = random.randint(0, 400)
        y = random.randint(0, 400)
        id = canvas.create_oval((x-5,y-5,x+5,y+5), fill="white")
        items.append(id)
    
    current_color = "white"
    def change_colors():
        global current_color
        current_color = "white" if current_color == "black" else "black"
        for id in items:
            canvas.itemconfig(id, fill = current_color)
        root.after(1000, change_colors)
    
    root.after(1000, change_colors)
    root.mainloop()
    

    【讨论】:

    • 感谢凯文的回答,但也许我的问题不清楚。我试图在画布上调用我自己的对象的方法。我也可以在示例中使用另一种方法,例如,将我的气泡分成两个较小的气泡
    【解决方案3】:

    Canvas.find_withtag() 方法将返回第一个参数指定的所有匹配对象的 ID 列表。您可以将其与字典结合使用,将它们映射回类的相应实例。一旦你有了它,你就可以调用它的任何方法。

    import Tkinter
    import random
    
    BUBBLE_TAG = 'Bubble'
    current_color = 'white'
    
    class Bubble(object):
        def __init__(self, canvas, x, y, size, color):
            self.canvas = canvas
            self.id = canvas.create_oval((x-5,y-5,x+5,y+5), fill=color, 
                                         tags=BUBBLE_TAG)
    
        def change_color(self, new_color):
            self.canvas.itemconfigure(self.id, fill=new_color)
    
    root = Tkinter.Tk()
    canvas = Tkinter.Canvas(root, width=400, height=400)
    canvas.pack()
    
    mapping = {}
    for i in range(1000):
        x, y = random.randint(0, 400), random.randint(0, 400)
        color = 'black' if random.randint(0, 1) else 'white'
        obj = Bubble(canvas, x, y, 5, color)
        mapping[obj.id] = obj
    
    def change_colors():
        for id in canvas.find_withtag(BUBBLE_TAG):
            current_color = canvas.itemcget(id, 'fill')
            new_color = 'black' if current_color == 'white' else 'white'
            mapping[id].change_color(new_color)  # calls method of object
        root.after(1000, change_colors)
    
    root.after(1000, change_colors)
    root.mainloop()
    

    这是一个运行的例子:

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-11-05
      • 1970-01-01
      • 2018-08-12
      相关资源
      最近更新 更多