Tkinter. Всплывающие окна

Работа со всплывающими окнами – неотъемлемая составляющая работы с любым приложением. В некоторых ситуациях нет никакой необходимости в том, чтобы информационные блоки изменяли окно. Значительно проще воспользоваться отдельным окном, предназначенным для этой задачи. В том числе, сюда входит создание дополнительного окна, а также ряд других операций, которые мы рассмотрим более подробно.

Открытие дополнительного окна

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

А что если необходимо создать дополнительные окна? Какой класс используется для них? Для этого применяется элемент Toplevel. С помощью этого класса возможно отображение самых разных окон, в том числе, и сложных.

Давайте для начала попробуем создать простое окошко, которое открывается по факту нажатия человеком кнопки в основном окне. В свою очередь, дополнительное окно будет закрываться по нажатию соответствующей кнопки. Выглядеть программа будет следующим образом.Tkinter. Всплывающие окна

Эта задача реализуется с помощью следующего кода.

import tkinter as tk




class About(tk.Toplevel):

    def __init__(self, parent):

        super().__init__(parent)

        self.label = tk.Label(self, text="Это всплывающее окно")

        self.button = tk.Button(self, text="Закрыть", command=self.destroy)




        self.label.pack(padx=20, pady=20)

        self.button.pack(pady=5, ipadx=2, ipady=2)




class App(tk.Tk):

    def __init__(self):

        super().__init__()

        self.btn = tk.Button(self, text="Открыть новое окно",

                             command=self.open_window)

        self.btn.pack(padx=50, pady=20)




    def open_window(self):

        about = About(self)

        about.grab_set()




if __name__ == "__main__":

    app = App()

    app.mainloop()

Мы здесь использовали класс Toplevel, чтобы создать еще одно окно верхнего уровня. Количество таких окон неограниченное, и в нем могут содержаться те объекты, которые требуются. 

Принцип работы всплывающих окон

Обозначаем подкласс Toplevel, с помощью которого мы создаем дополнительное окно, являющееся дочерним по отношению к классу Tk. То, как оно будет взаимодействовать с родительским, определяется методом __init__

Чтобы добавить виджеты, не нужно делать ничего экстраординарного. Все обычно. Приведем фрагмент кода, который это демонстрирует.

class Window(tk.Toplevel):

    def __init__(self, parent):

        super().__init__(parent)

Открытие окна происходит путем создания нового экземпляра. Тем не менее, для возможности обрабатывать все события приложения, необходимо воспользоваться методом grab_set. Это не даст возможность выполнять какие-либо операции в основном окне, пока дополнительное не будет закрыто.

Обработка закрытия окна

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

Здесь мы снова будем применять класс App, который демонстрировался в примере выше, но при этом таким образом отредактируем класс Window, чтобы он отображал диалог для подтверждения закрытия.

Ну то есть, чтобы программа выглядела следующим образом.Tkinter. Всплывающие окна

Библиотека Tkinter дает возможность определить готовность окна к закрытию с использованием функции для протокола WM_DELETE_WINDOW. Она используется, как обработчик. Чтобы ее запустить, необходимо нажать на клавишу закрытия окна в правом верхнем углу любой программы, написанной под Windows. То есть, эта функция может быть связана не только с созданной нами кнопкой закрытия, но и стандартной кнопкой системы.

Соответственно, если используются другие операционные системы, то эта кнопка может располагаться в другой части окна. Например, в Mac они размещаются слева и сверху, с противоположной от Windows стороны.

import tkinter as tk

import tkinter.messagebox as mb


class Window(tk.Toplevel):

    def __init__(self, parent):

        super().__init__(parent)

        self.protocol("WM_DELETE_WINDOW", self.confirm_delete)

        self.label = tk.Label(self, text="Это всплывающее окно")

        self.button = tk.Button(self, text="Закрыть", command=self.destroy)

        self.label.pack(padx=20, pady=20)

        self.button.pack(pady=5, ipadx=2, ipady=2)


    def confirm_delete(self):

        message = "Вы уверены, что хотите закрыть это окно?"

        if mb.askyesno(message=message, parent=self):

            self.destroy()


class App(tk.Tk):

    def __init__(self):

        super().__init__()

        self.btn = tk.Button(self, text="Открыть новое окно",

                             command=self.open_about)

        self.btn.pack(padx=50, pady=20)


    def open_about(self):

        window = Window(self)

        window.grab_set()


if __name__ == "__main__":

    app = App()

    app.mainloop()

Этот код наглядно демонстрирует работу этого метода-обработчика. С его помощью мы демонстрируем диалоговое для подтверждения закрытия дополнительного окна пользователем. Если же приложения более сложные, то используются еще и проверки. Например, сохранены ли данные. 

Принцип проверки закрытия окна

Для того, чтобы зарегистрировать обработчик событий, мы использовали функцию bind, которая является методом по факту. Что касается протоколов менеджеров окна, то эта же самая задача реализуется с помощью метода protocol.

Если программа обнаруживает, что окно должно закрываться, то включается обработчик WM_DELETE_WINDOW. Так, как эти действия перезаписываются с использованием обработчика confirm_delete, то программа уничтожает окно только после того, как человек дал добро.

Еще один полезный протокол, который используется – WM_TAKE_FOCUS. Каждый раз, когда окно получает фокус, он вызывается. 

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

if mb.askyesno(message=message, parent=self):

    self.destroy()

Кстати, использоваться может любая другая диалоговая функция. 

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

Обмен переменными между окнами

Время от времени окна должны обмениваться информацией между собой непосредственно в ходе выполнения программы. Конечно, можно сохранить нужную информацию на диске, а потом передавать приложению. Недостаток такого подхода – низкая скорость. Поэтому значительно эффективнее обработку данных осуществлять в оперативной памяти, к которой будут иметь доступ оба окна. 

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

Каждое из них содержит по кнопке, позволяющей подтвердить введенные данные. 

Выглядеть приложение будет следующим образом.

Tkinter. Всплывающие окна

Обратите внимание, что в этом примере тип пользователя указан в соответствующей строке над полем ввода логина.

Чтобы введенные пользователем данные были сохранены, необходимо создать namedtuple с теми полями, которые бы представляли экземпляр каждого из пользователей. Эта функция входит в модуль collections, и с ее помощью мы получаем имя типа и последовательность имен полей. Возвращаемый объект – подкласс кортежа, используемый нами для генерации простого объекта с указанными полями.

import tkinter as tk

from collections import namedtuple




User = namedtuple("User", ["username", "password", "user_type"])




class UserForm(tk.Toplevel):

    def __init__(self, parent, user_type):

        super().__init__(parent)

        self.username = tk.StringVar()

        self.password = tk.StringVar()

        self.user_type = user_type




        label = tk.Label(self, text="Создать пользователя " + user_type.lower())

        entry_name = tk.Entry(self, textvariable=self.username)

        entry_pass = tk.Entry(self, textvariable=self.password, show="*")

        btn = tk.Button(self, text="Submit", command=self.destroy)




        label.grid(row=0, columnspan=2)

        tk.Label(self, text="Логин:").grid(row=1, column=0)

        tk.Label(self, text="Пароль:").grid(row=2, column=0)

        entry_name.grid(row=1, column=1)

        entry_pass.grid(row=2, column=1)

        btn.grid(row=3, columnspan=2)




    def open(self):

        self.grab_set()

        self.wait_window()

        username = self.username.get()

        password = self.password.get()

        return User(username, password, self.user_type)




class App(tk.Tk):

    def __init__(self):

        super().__init__()

        user_types = ("Админ", "Менеджер", "Клиент")

        self.user_type = tk.StringVar()

        self.user_type.set(user_types[0])




        label = tk.Label(self, text="Пожалуйста, выберите роль пользователя")

        radios = [tk.Radiobutton(self, text=t, value=t,

                                 variable=self.user_type) for t in user_types]

        btn = tk.Button(self, text="Создать", command=self.open_window)




        label.pack(padx=10, pady=10)

        for radio in radios:

            radio.pack(padx=10, anchor=tk.W)

        btn.pack(pady=10)




    def open_window(self):

        window = UserForm(self, self.user_type.get())

        user = window.open()

        print(user)




if __name__ == "__main__":

    app = App()

    app.mainloop()

Принцип работы передачи данных между окнами довольно простой. Приведенный в этом примере код мы уже рассматривали, в целом, ранее. Единственное отличие здесь в том, что мы использовали метод open() класса UserForm, а туда переместили вызов grab_set(). Тем не менее, за остановку исполнения приложения до тех пор, пока не будут изменены данные в форме, отвечает именно метод wait_windows().

ОфисГуру
Adblock
detector