Как правильно реализовать метод tkinter .after () в контексте моей программы? - PullRequest
0 голосов
/ 14 апреля 2019

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

Программа имеет три основных кадра / экрана, по которым я бы хотел, чтобы пользователь перемещался. Навигация между экранами должна быть основана на времени. Другими словами, я бы хотел, чтобы пользователь перенаправлялся с основного экрана на дополнительный экран после нажатия кнопки Tkinter (которую я уже установил с помощью аргумента 'command' виджетов Tkinter), а затем автоматически перенаправлял вернуться к главному экрану после 5-секундной задержки.

Я понимаю, что использование time.sleep() не рекомендуется в программах с графическим интерфейсом. Однако у меня возникли проблемы с реализацией метода Tkinter .after(), и я не смог достичь желаемого результата.

Я приложил упрощенный пример кода моей программы, который моделирует мою проблему:

import tkinter as tk


class mainApplication(tk.Tk):

    def __init__(self, *args, **kwargs):

        tk.Tk.__init__(self, *args, **kwargs)

        container = tk.Frame(self)
        container.pack(side="top", fill="both", expand=True)
        self.frames = {}

        for F in (MainScreen, AcknowledgeScreen):

            frame = F(container, self)
            self.frames[F] = frame
            frame.grid(row=0, column=0, sticky="nsew")

        self.show_frame(MainScreen)

    def show_frame(self, cont):

        frame = self.frames[cont]
        frame.tkraise()


class MainScreen(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        label = tk.Label(self, text="Press Button to Log Time")
        label.pack()

        button = tk.Button(self, text="Confirm", command=lambda: controller.show_frame(AcknowledgeScreen))
        button.pack()


class AcknowledgeScreen(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        label = tk.Label(self, text="Logging Completed. Please Wait.")
        label.pack()

        # The implementation below is giving me trouble.

        self.after(5000, controller.show_frame(MainScreen))


root = mainApplication()
root.mainloop()

Другие решения, которые я попытался (для интересующей линии), включают:

# Attempt 1
self.after(5000, controller.show_frame(MainScreen))  # This code waits 5 seconds before opening the GUI window; does not redirect back to MainScreen from AcknowledgeScreen.

# Attempt 2
root.after(5000, controller.show_frame(MainScreen))  # This code throws an error 'NameError: name 'root' is not defined.

# Attempt 3
label.after(5000, controller.show_frame(MainScreen))  # This code waits 5 seconds before opening the GUI window; does not redirect back to MainScreen from AcknowledgeScreen.

К сожалению, я никогда не сталкивался с объектно-ориентированным программированием до начала работы с Tkinter, поэтому я считаю, что мои ошибки могут быть связаны с фундаментальным неправильным пониманием того, как работает ООП. Тем не менее, я был бы признателен, если бы кто-нибудь мог указать мне правильное направление или уточнить мои ошибки.

Ответы [ 3 ]

1 голос
/ 14 апреля 2019

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

Начиная с упорядочения ваших операций, MainScreen следует повышать в порядке размещения после AcknowledgeScreen отображается.

Эта операция не должна находиться в AcknowledgeScreen.__init__, за исключением случаев, когда вы инициализируете экраны в то время, когда они необходимы.

Вы хотите переместить это в MainScreen.Вы можете выполнить рефакторинг MainScreen следующим образом.

class MainScreen(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller
        label = tk.Label(self, text="Press Button to Log Time")
        label.pack()
        button = tk.Button(self, text="Confirm", command=self.confirm)
        button.pack()

    def confirm(self):
        self.controller.show_frame(AcknowledgeScreen)
        self.after(5000, self.back)

    def back(self):
        self.controller.show_frame(MainScreen)
1 голос
/ 14 апреля 2019

Давайте посмотрим на этот код:

self.after(5000, controller.show_frame(MainScreen))  

Приведенный выше код делает то же самое, что и этот:

result = controller.show_frame(MainScreen)
self.after(5000, result)

Обратите внимание, что происходит?Функция controller.show_frame(MainScreen) выполняется немедленно, а не after.

Вместо этого вам нужно дать after a callable - грубо говоря, ссылку на функцию,Если эта функция требует дополнительных аргументов, вы можете добавить эти аргументы при вызове after:

self.after(5000, controller.show_frame, MainScreen)

Приведенный выше код говорит after выполнить controller.show_frame через пять секунд и передать ему аргумент MainScreen когда это называется.

0 голосов
/ 14 апреля 2019

after() (например, bind() и command=) необходимо callback - это означает имя функции без () и без аргументов.

Использование lambda

  self.after(5000, lambda:controller.show_frame(MainScreen))

Но я вижу другую проблему.

Когда вы запускаете программу, она создает экземпляры всех кадров в mainApplication.__init__, поэтомуон также запускает AcknowledgeScreen.__init__ при запуске - и after() при запуске.Он не ждет вашего клика.

Кстати: поскольку кадры создаются внутри mainApplication.__init__, поэтому они не могут использовать root, который будет создан после того, как mainApplication.__init__ завершит свою работу.

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

class MainScreen(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller

        label = tk.Label(self, text="Press Button to Log Time")
        label.pack()

        button = tk.Button(self, text="Confirm", command=self.change)
        button.pack()

    def change(self):
        self.controller.show_frame(AcknowledgeScreen)
        self.controller.frames[AcknowledgeScreen].change_after()      

class AcknowledgeScreen(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller

        label = tk.Label(self, text="Logging Completed. Please Wait.")
        label.pack()

    def change_after(self):
        # The implementation below is giving me trouble.

        #both works
        #root.after(5000, lambda:self.controller.show_frame(MainScreen))
        self.after(5000, lambda:self.controller.show_frame(MainScreen))
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...