【问题标题】:Delete all from TkInter Canvas and put new items while in mainloop从 TkInter Canvas 中删除所有内容并在 mainloop 中放置新项目
【发布时间】:2019-04-08 23:42:07
【问题描述】:

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

安装在 Raspberry Pi 上,连接了 LCD+触摸屏。我在 Python3 中使用 tkinter。画布用于在屏幕上显示元素。 由于我来自嵌入式硬件世界并且在 Python 和一般高级语言方面的经验很少,因此我使用 switch-case 逻辑来解决这个问题。在 Python 中,这是 if-elif-elif ...

我尝试了各种方法:

  1. 制作全局画布对象。有一个变量 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 函数,但这失败了并且不会在屏幕上显示任何内容,它只会创建画布。我可能没有在正确的地方使用它。

  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()

我希望这个应用程序可以使用一个特殊线程来更改屏幕,该线程会调用一个函数来重绘画布上的元素,但到目前为止这一直失败。据我所知,线程可能是最好的选择。它们最接近我的无限循环思维方式(当真),也最接近我的逻辑。

这里有什么选择?如何实现删除整个屏幕并重新绘制(我称之为制作新“屏幕”)?

【问题讨论】:

  • 在打开窗口之前执行mainloop 之前的所有操作。 mainloop 启动窗口并运行直到您关闭窗口。当窗口打开时,您必须使用按钮或after 执行功能 - 然后它可以更改窗口中的元素。
  • 如果您想像在移动应用程序中使用按钮更改屏幕,然后创建按钮并将功能分配给按钮Button(..., command=function_name)。使用threads 会产生一些问题,因为只有主线程才能更改 GUI 中的元素。如果您不使用按钮并且必须延迟更改屏幕,使用after 可能是更好的方法。
  • 您可以创建两个具有不同元素的画布和pack()/unpack() canvas,而不是更改画布中的元素。
  • 很久以前在 Stackoverflow 上是如何使用 Frame 创建几个页面并在主窗口中替换它们的示例。
  • 我个人不建议初学者使用该示例。太多的人在不了解的情况下复制并使用它,这导致更多的混乱。 :-\

标签: python tkinter tkinter-canvas


【解决方案1】:

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

使用您的第一个画布示例

在您的第一个示例中,您希望在两秒后自动切换页面。这可以通过使用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 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)

【讨论】:

  • 很好的答案,谢谢。现在我对 Tkinter 有了更好的了解,也可以实现我最初想要的。两种方法都经过测试并且有效。我将使用类来编写更好的代码。对其他人来说,不要忘记在您想要显示的每个更改之后添加 mainloop!
  • @davaradijator:我不确定你说的最后一句话是什么意思。您只能在程序中调用mainloop() 一次。这是一条可以打破的规则,但只有当你明白为什么你永远不应该打破这条规则时。
  • 对我来说,在我将 root.main() 放在每个 if..enif 的末尾或 init 函数的末尾之前,代码不起作用每节课。我在代码末尾有 mainloop() 。我做错了什么?
  • @davaradijator:如果你不止一次调用 mainloop,那就是你做错了什么。可能还有其他事情你做错了,但没有看到你的代码就不可能说出来。
猜你喜欢
  • 2018-09-04
  • 2023-02-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-10
  • 2019-05-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多