Цель состоит в том, чтобы добиться разных «экранов» в TkInter и переключаться между ними. Проще всего представить это в виде мобильного приложения, в котором можно щелкнуть значок, например, «Добавить новый», и открывается новый экран. Всего в приложении 7 экранов, и оно должно иметь возможность менять экраны в зависимости от действий пользователя.
Настройка выполняется на Raspberry Pi с подключенным ЖК-дисплеем и сенсорным экраном. Я использую tkinter в Python3. Холст используется для отображения элементов на экране. Поскольку я пришел из мира встраиваемого оборудования и у меня очень мало опыта работы с Python и, как правило, с языками высокого уровня, я подошел к этому с логикой переключения регистров. В Python это if-elif-elif...
Я пробовал разные вещи:
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()
Используя функцию root.after, но это не удалось и ничего не отобразилось на экране, это только создало холст. Я, вероятно, не использовал его в нужном месте.
Попытка создать еще один поток для смены экранов, просто для проверки возможности потоковой передачи. Он застревает на первом изображении и никогда не переходит на второе.
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()
Я ожидаю, что это приложение может изменять экраны с помощью специального потока, который будет вызывать функцию, которая перерисовывает элементы на холсте, но пока это не удается. Насколько я понимаю сейчас, потоки могут быть лучшим вариантом. Они ближе всего к моему мышлению с бесконечным циклом (пока True) и ближе всего к моей логике.
Какие тут варианты? Как можно удалить весь экран и перерисовать его (то, что я называю созданием нового «экрана»)?
если вы хотите изменить экран, как в мобильном приложении, с помощью кнопок, создайте кнопки и назначьте функции кнопкам Button(..., command=function_name)
. Использование threads
может создать некоторые проблемы, потому что только основной поток должен изменять элементы в графическом интерфейсе. Использование after
может быть лучшим методом, если вы не используете кнопки и вам нужно менять экран с некоторой задержкой.
вместо изменения элементов в холсте вы можете создать два холста с разными элементами и холст pack()
/unpack()
.
давным-давно на Stackoverflow был пример того, как использовать Frame
для создания нескольких страниц и замены их в главном окне.
Лично я не рекомендую этот пример для начинающих. Слишком много людей копируют его и используют, не понимая, что вызывает еще большую путаницу. :-\
Tkinter, как и большинство инструментов GUI, управляется событиями. Вам просто нужно создать функцию, которая удаляет старый экран и создает новый, а затем делает это в ответ на событие (нажатие кнопки, таймер, что угодно).
В первом примере вы хотите автоматически переключать страницы через две секунды. Это можно сделать, используя 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):
...
Возможно, лучшим решением будет сделать каждую страницу классом, основанным на виджете. Это позволяет легко добавлять или удалять все сразу, добавляя или удаляя этот виджет (поскольку удаление виджета также уничтожает все его дочерние элементы).
Тогда остается только удалить старый объект и создать новый. Вы можете создать сопоставление номера состояния с именем класса, если вам нравится концепция, управляемая состоянием, и использовать это сопоставление для определения экземпляра класса.
Например:
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. Например, чтобы связать клавишу escape, чтобы всегда возвращать вас в начальное состояние, вы можете сделать что-то вроде этого:
def reset_state(event):
global programState
programState = 0
refresh()
root.bind("<Escape>", reset_state)
Отличный ответ, спасибо. Теперь я лучше разбираюсь в Tkinter, но также могу добиться того, чего изначально хотел. Оба метода проверены и работают. Я буду использовать классы, чтобы писать лучший код. Для других, не забудьте добавить mainloop после каждого изменения, которое вы хотите показать!
@davaradijator: я не уверен, что вы имеете в виду под последним предложением. Вы должны вызывать mainloop()
ровно один раз в своей программе. Это правило можно нарушить, но только тогда, когда вы поймете, почему никогда не должны нарушать это правило.
для меня код не работал, пока я не поставил root.main() в конце каждой функции if..enif или в конце функции в этом каждого класса. У меня был mainloop() в конце кода. Что я делаю неправильно?
@davaradijator: если вы вызываете mainloop более одного раза, это, что вы делаете неправильно. Могут быть и другие вещи, которые вы делаете неправильно, но не видя вашего кода, это невозможно сказать.
все, что находится перед строкой с
mainloop
, выполняется до открытия окна.mainloop
запускает окно и работает, пока вы его не закроете. Вы должны использовать кнопку илиafter
для выполнения функции, когда окно открыто, и тогда оно может изменять элементы в окне.