Удалите все из TkInter Canvas и поместите новые элементы в основной цикл

Цель состоит в том, чтобы добиться разных «экранов» в TkInter и переключаться между ними. Проще всего представить это в виде мобильного приложения, в котором можно щелкнуть значок, например, «Добавить новый», и открывается новый экран. Всего в приложении 7 экранов, и оно должно иметь возможность менять экраны в зависимости от действий пользователя.

Настройка выполняется на Raspberry Pi с подключенным ЖК-дисплеем и сенсорным экраном. Я использую tkinter в Python3. Холст используется для отображения элементов на экране. Поскольку я пришел из мира встраиваемого оборудования и у меня очень мало опыта работы с Python и, как правило, с языками высокого уровня, я подошел к этому с логикой переключения регистров. В 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()

Я ожидаю, что это приложение может изменять экраны с помощью специального потока, который будет вызывать функцию, которая перерисовывает элементы на холсте, но пока это не удается. Насколько я понимаю сейчас, потоки могут быть лучшим вариантом. Они ближе всего к моему мышлению с бесконечным циклом (пока True) и ближе всего к моей логике.

Какие тут варианты? Как можно удалить весь экран и перерисовать его (то, что я называю созданием нового «экрана»)?

все, что находится перед строкой с mainloop, выполняется до открытия окна. mainloop запускает окно и работает, пока вы его не закроете. Вы должны использовать кнопку или after для выполнения функции, когда окно открыто, и тогда оно может изменять элементы в окне.

furas 09.04.2019 01:49

если вы хотите изменить экран, как в мобильном приложении, с помощью кнопок, создайте кнопки и назначьте функции кнопкам Button(..., command=function_name). Использование threads может создать некоторые проблемы, потому что только основной поток должен изменять элементы в графическом интерфейсе. Использование after может быть лучшим методом, если вы не используете кнопки и вам нужно менять экран с некоторой задержкой.

furas 09.04.2019 01:55

вместо изменения элементов в холсте вы можете создать два холста с разными элементами и холст pack()/unpack().

furas 09.04.2019 01:56

давным-давно на Stackoverflow был пример того, как использовать Frame для создания нескольких страниц и замены их в главном окне.

furas 09.04.2019 01:59

Лично я не рекомендую этот пример для начинающих. Слишком много людей копируют его и используют, не понимая, что вызывает еще большую путаницу. :-\

Bryan Oakley 09.04.2019 02:39
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
6
948
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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):
        ...

Использование объектов 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. Например, чтобы связать клавишу escape, чтобы всегда возвращать вас в начальное состояние, вы можете сделать что-то вроде этого:

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

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

Отличный ответ, спасибо. Теперь я лучше разбираюсь в Tkinter, но также могу добиться того, чего изначально хотел. Оба метода проверены и работают. Я буду использовать классы, чтобы писать лучший код. Для других, не забудьте добавить mainloop после каждого изменения, которое вы хотите показать!

davaradijator 10.04.2019 01:15

@davaradijator: я не уверен, что вы имеете в виду под последним предложением. Вы должны вызывать mainloop() ровно один раз в своей программе. Это правило можно нарушить, но только тогда, когда вы поймете, почему никогда не должны нарушать это правило.

Bryan Oakley 10.04.2019 01:33

для меня код не работал, пока я не поставил root.main() в конце каждой функции if..enif или в конце функции в этом каждого класса. У меня был mainloop() в конце кода. Что я делаю неправильно?

davaradijator 10.04.2019 23:51

@davaradijator: если вы вызываете mainloop более одного раза, это, что вы делаете неправильно. Могут быть и другие вещи, которые вы делаете неправильно, но не видя вашего кода, это невозможно сказать.

Bryan Oakley 11.04.2019 00:35

Другие вопросы по теме