从 TkInter Canvas 中删除所有内容并在主循环中放入新项目

Delete all from TkInter Canvas and put new items while in mainloop

目标是在TkInter中实现不同的"screens",并在它们之间进行转换。最容易想象的是想到一个移动应用程序,点击图标,例如 "Add new",然后打开新屏幕。该应用程序共有 7 个屏幕,它应该能够根据用户操作更改屏幕。

设置已开启 Raspberry Pi,并附有 LCD+触摸屏。我在 Python3 中使用 tkinter。 Canvas 用于在屏幕上显示元素。 由于我来自嵌入式硬件世界并且在 Python 和一般高级语言方面的经验很少,所以我使用 switch-case 逻辑来解决这个问题。在 Python 这是 if-elif-elif...

我尝试过各种方法:

  1. 制作全局 canvas 对象。有一个变量 programState 决定当前显示哪个屏幕。这显然不起作用,因为它只会 运行 一次并卡在下面的主循环中。
from tkinter import * 
import time

root = Tk()

programState = 0
canvas = Canvas(width=320, height=480, bg='black')
canvas.pack(expand=YES, fill=BOTH)

if(programState == 0):
       backgroundImage = PhotoImage(file="image.gif")
       canvas.create_image(0,0, image=backgroundImage, anchor=NW);

       time.sleep(2)

       canvas.delete(ALL) #delete all objects from canvas
       programState = 1
elif(programState == 1):
....
....
....
root.mainloop()

  1. 使用 root.after 函数但是失败了并且不会在屏幕上显示任何内容,它只会创建 canvas。我可能没有在正确的地方使用它。

  2. 正在尝试创建另一个线程来更改屏幕,只是为了测试线程选项。它卡在第一张图片上,永远不会移动到第二张图片。

from tkinter import *
from threading import Thread
from time import sleep

def threadFun():
        while True:
                backgroundImage = PhotoImage(file="image1.gif")
                backgroundImage2 = PhotoImage(file="image2.gif")
                canvas.create_image(0,0,image=backgroundImage, anchor=NW)
                sleep(2)
                canvas.delete(ALL)
                canvas.create_image(0,0,image=backgroundImage2, anchor=NW)

root = Tk()

canvas = Canvas(width=320, height=480, bg='black')
canvas.pack(expand=YES, fill=BOTH)

# daemon=True kills the thread when you close the GUI, otherwise it would continue to run and raise an error.
Thread(target=threadFun, daemon=True).start()
root.mainloop()

我希望这个应用程序可以使用一个特殊的线程来改变屏幕,该线程会调用一个函数来重绘 canvas 上的元素,但到目前为止一直失败。据我现在的理解,线程可能是最好的选择。它们最接近我的无限循环思维方式(虽然为真)并且最接近我的逻辑。

这里有哪些选项?如何实现删除整个屏幕并重新绘制它(我称之为制作新的"screen")?

像大多数 GUI 工具包一样,Tkinter 是事件驱动的。您只需要创建一个函数来删除旧屏幕并创建新屏幕,然后响应事件(按钮单击、计时器等)执行此操作。

使用您的第一个 canvas 示例

在您的第一个示例中,您希望在两秒后自动切换页面。这可以通过使用 after 在超时后将函数安排到 运行 来完成。然后只需将重绘逻辑移动到一个函数中即可。

例如:

def set_programState(new_state):
    global programState
    programState = new_state
    refresh()

def refresh():
    canvas.delete("all")

    if(programState == 0):
        backgroundImage = PhotoImage(file="image.gif")
        canvas.create_image(0,0, image=backgroundImage, anchor=NW);
        canvas.after(2000, set_programState, 1)
    elif(programState == 1):
        ...

使用 python 个对象

可以说,更好的解决方案是让每个页面都成为 class 基于小部件的页面。这样做可以轻松地通过添加或删除一个小部件来一次添加或删除所有内容(因为销毁一个小部件也会销毁其所有子项)

那么就只需要删除旧对象并实例化新对象即可。如果您喜欢状态驱动的概念,您可以创建状态编号到 class 名称的映射,并使用该映射来确定实例化哪个 class。

例如:

class ThisPage(tk.Frame):
    def __init__(self):
        <code to create everything for this page>

class ThatPage(tk.Frame):
    def __init__(self):
        <code to create everything for this page>

page_map = {0: ThisPage, 1: ThatPage}
current_page = None
...
def refresh():
    global current_page

    if current_page:
        current_page.destroy()

    new_page_class = page_map[programstate]     
    current_page = new_page_class()
    current_page.pack(fill="both", expand=True)

以上代码有些笨拙,但希望它能说明基本技术。

就像第一个示例一样,您可以从任何类型的事件调用 update():按钮单击、计时器或 tkinter 支持的任何其他类型的事件。例如,要绑定转义键始终将您带到初始状态,您可以这样做:

def reset_state(event):
    global programState
    programState = 0
    refresh()

root.bind("<Escape>", reset_state)