【问题标题】:Tkinter canvas move leaves a pixel trailTkinter 画布移动留下像素痕迹
【发布时间】:2019-06-09 16:16:45
【问题描述】:

我正在 Tkinter 画布中开发一个游戏,其中点在屏幕上移动。我用tkinter.Canvas.create_oval(...) 将每个点放在一个位置,然后用tkinter.Canvas.move(pointID,delta_x,delta_y) 移动这些点。

我的问题是,这些点在移动时似乎会留下痕迹。我做了一个简化的例子来说明我的问题。

from tkinter import Canvas,mainloop,Tk
import numpy as np
import random
import traceback
import threading
import time
from queue import Queue

class Point:
    def __init__(self,the_canvas,uID):
        self.uID = uID
        self.location = np.ones((2)) * 200
        self.color = "#"+"".join([random.choice('0123456789ABCDEF') for j in range(6)])
        self.the_canvas = the_canvas
        self.the_canvas.create_oval(200,200,200,200,
                     fill=self.color,outline=self.color,width=6,
                     tags=('runner'+str(self.uID),'runner'))
    def move(self):
        delta = (np.random.random((2))-.5)*20
        self.the_canvas.move('runner'+str(self.uID),delta[0],delta[1])

def queue_func():
    while True:
        time.sleep(.25)
        try:
            next_action = the_queue.get(False)
            next_action()
        except Exception as e: 
            if str(e) != "": 
                print(traceback.format_exc())

the_queue = Queue()
the_thread = threading.Thread(target=queue_func)
the_thread.daemon = True
the_thread.start()

window = Tk()
window.geometry('400x400')
the_canvas = Canvas(window,width=400,height=400,background='black')
the_canvas.grid(row=0,column=0)

points = {}
for i in range(100):
    points[i] = Point(the_canvas,i)

def random_movement():
    while True:
        for point in points.values():
            point.move()

the_queue.put(random_movement)

mainloop()

结果是这样的:

我需要能够干净利落地移动点,不留下任何东西。

  • 我尝试更改move() 函数,以便根据其标记删除每个点并在新位置重新绘制,但这会导致相同的问题。
  • 我在Canvas.oval 配置中尝试了fill=''outline='',但这没有帮助。
  • 这些像素试验的行为似乎不稳定,就像它们会随着时间的推移而消失,只留下有限数量的足迹。
  • 我尝试从移动循环中删除time.sleep(.2),这似乎使问题更加明显。

  • 我发现清理这些流氓彩色像素的唯一方法是运行canvas.delete("all"),所以到目前为止,我唯一的解决方案是删除所有内容并不断重绘所有内容。这对我来说似乎不是一个很好的解决方案。

什么是避免这些“像素痕迹”的好方法?这对我来说真的只是一个错误,但也许我在某个地方犯了错误。

【问题讨论】:

  • FWIW,我无法在我的 Mac 上复制这个问题。我没有看到您的屏幕截图中的任何工件。这可能与您对线程的使用有关——tkinter 有时在线程方面遇到困难。除非您尝试移动数万个点,否则我认为您不需要线程的所有开销。
  • @BryanOakley FWIW 我可以在我的 Windows 7 机器上重现它,但我也认为线程是主要问题,尽管即使在删除线程之后我确实注意到有非常少在其中一次运行中出现重影。
  • 相同的结果。当您删除线程并改用after() 循环时,伪影确实会消失。不过还是有一些人为因素。
  • 看来问题可能至少与窗口中椭圆的边框有关。我通过移除边框对其进行了测试,所有伪影都消失了。

标签: python canvas tkinter


【解决方案1】:

经过一番挖掘,我在这里找到了这篇文章:Python3 tkinter.Canvas.move() method makes artifacts on screen

问题在于椭圆的边界。所以我所做的就是移除边框并使椭圆稍微变大以进行补偿,看起来就成功了。

如果你改变这一行:

self.the_canvas.create_oval(200, 200, 200, 200, 
                            fill=self.color, outline=self.color, width=6,
                            tags=('runner' + str(self.uID), 'runner'))

到这里:

self.the_canvas.create_oval(200,200,206,206,
                            fill=self.color,outline='', width=0,
                            tags=('runner'+str(self.uID),'runner'))

无论是否使用线程,问题都会消失。

如果你想看看你的代码在没有线程的情况下会是什么样子,这里有一个例子:

import tkinter as tk
import numpy as np
import random


class Point:
    def __init__(self, the_canvas, uID):
        self.uID = uID
        self.location = np.ones((2)) * 200
        self.color = "#"+"".join([random.choice('0123456789ABCDEF') for j in range(6)])
        self.the_canvas = the_canvas
        self.the_canvas.create_oval(200, 200, 206, 206,
                                    fill=self.color, outline='', width=0,
                                    tags=('runner'+str(self.uID), 'runner'))

    def move(self):
        delta = (np.random.random((2))-.5)*20

        self.the_canvas.move('runner'+str(self.uID), delta[0], delta[1])

window = tk.Tk()
window.geometry('400x400')
the_canvas = tk.Canvas(window, width=400, height=400, background='black')
the_canvas.grid(row=0, column=0)

points = {}
for i in range(100):
    points[i] = Point(the_canvas, i)

def random_movement():
    for point in points.values():
        point.move()
    window.after(50, random_movement)

random_movement()
window.mainloop()

结果:

【讨论】:

  • @Idlehands 我在粘贴代码时确实错过了window.mainloop(),谢谢。
  • 啊不抱歉我有点困惑,因为我把它作为window.after(25, random_movement); window.mainloop() 并且试图弄清楚为什么random_movement() 没有实时绘制任何东西。我错过了递归的after() 调用。感觉像个笨蛋。
  • @Idlehands 哈哈。它发生了。众所周知,我错过了一些明显的事情,后来才从有人指出的地方发现。
【解决方案2】:

奇怪的是,问题似乎来自 width=6create_oval() 摆脱它似乎可以解决问题。 您最初设置create_oval() 的方式是制作一个面积为零(即不存在)且边框宽度为 6 的椭圆形......这显然是一个麻烦的组合。

所以这是新代码,有一些额外的修改:

from tkinter import Canvas,mainloop,Tk
import numpy as np
import random
import traceback
import time

class Point:
    def __init__(self,uID):
        self.uID = uID
        self.location = np.ones((2)) * 200
        self.color = "#" + "".join([random.choice('0123456789ABCDEF') for j in range(6)])

    def move(self):
        delta = (np.random.random((2)) - .5) * 20
        self.location += delta

    def draw(self,canv):
        x0, y0 = self.location
        canv.create_oval(
             x0, y0, x0 + 2, y0 + 2,
             fill=self.color, outline=self.color,
             tags=('runner'+str(self.uID),'runner')
        )

def repeater(window):
    the_canvas.delete('all')
    for i in points:
        i.move()
        i.draw(the_canvas)
    window.after(25, repeater, window)

window = Tk()
window.geometry('400x400')
the_canvas = Canvas(window,width=400,height=400,background='black')
the_canvas.grid(row=0,column=0)

points = []
for i in range(100):
    points.append(Point(i))

repeater(window)
window.mainloop()

另外,顺便说一句,删除画布上的所有元素是清除它的方法。这并不浪费,因为无论如何您都在更新它上面的所有元素。

【讨论】:

  • 是的,问题与边界有关。然而,最好将颜色设置为空,宽度设置为零。这应该可以防止在 Windows 中出现任何伪像。我之所以说设置 width=0 而不是将其全部删除,是因为如果您没有定义宽度,则宽度默认为 1。
  • 只要宽度不大于对象(即椭圆形)本身就可以了?
猜你喜欢
  • 2023-04-05
  • 2018-05-13
  • 2011-12-30
  • 2013-12-14
  • 2019-04-16
  • 2021-07-07
  • 1970-01-01
  • 1970-01-01
  • 2012-05-11
相关资源
最近更新 更多