Меню – неизменная составляющая любого сложного графического интерфейса. Это простой способ организовать навигацию по программе и выполнять ряд других важных действий, которые не всегда находятся под рукой. Нередко этот подход используется и для группировки операций, тесно связанных между собой. Типичный пример такой организации – меню «Файл» в большинстве текстовых редакторов.
В функционал библиотеки входят строки меню. При этом они по-прежнему являются мультиплатформенными. Следовательно, они отображаются так, как это предусмотрено в операционной системе. Это позволяет избежать необходимости использовать фреймы в случае малейшей необходимости.
Давайте попробуем добавить меню в программу, которая будет демонстрироваться на примере Windows 10. Она будет выглядеть приблизительно так.
Чтобы реализовать этот функционал, в Tkinter используется класс Menu. Точно так же, как и с другими элементами, для их создания необходимо в качестве первого аргумента передать родительский контейнер, а опциональные параметры – в качестве второго.
Рассмотрим это на примере следующего кода.
import tkinter as tk class App(tk.Tk): def __init__(self): super().__init__() menu = tk.Menu(self) file_menu = tk.Menu(menu, tearoff=0) file_menu.add_command(label="Новый файл") file_menu.add_command(label="Открыть") file_menu.add_separator() file_menu.add_command(label="Сохранить") file_menu.add_command(label="Сохранить как...") menu.add_cascade(label="Файл", menu=file_menu) menu.add_command(label="О программе") menu.add_command(label="Выйти", command=self.destroy) self.config(menu=menu) if __name__ == "__main__": app = App() app.mainloop()
При запуске этой последовательности команд вы обнаружите, что после нажатия на пункт меню «Файл» отображается еще одно меню. Если же нажать на кнопку «Выйти», программа закрывается.
Принцип работы верхнего меню
Давайте для начала выполним генерацию элемента каждого меню, включая родительский блок. Поскольку мы передали параметру tearoff значение 1, то соответствующий пункт меню открепляется от всех остальных с использованием пунктирной линии. В верхнем меню такое используется довольно редко. Поэтому если нужно выключить эту опцию, можно передать параметр 0 при генерации меню.
Для управления пунктами в подменю существуют команды add_command, app_separator и add_cascade..
С их синтаксисом можно ознакомиться в этом примере кода.
menu.add_cascade(label="Файл", menu=file_menu) menu.add_command(label="О программе") menu.add_command(label="Выйти", command=self.destroy)
Как правило, вместе с методом add_command добавляется также сама команда, которую нужно выполнить. Для этого используется параметр command, как видно с примера выше. Фактически каждое подменю является кнопкой, хоть и выглядит не так. При нажатии по соответствующему пункту выполняется то действие, которое прописано в качестве значения этого параметра.
В качестве действия используется функция или метод. В нашем примере это метод self.destroy, который закрывает программу.
А чтобы прикрепить подменю к основному меню, необходимо использовать команду self.config(menu=menu). Следует заметить, что исключительно одна строка меню возможно для этого окна.
Можно ли использовать переменные в меню?
Да, конечно, можно. Любые переменные Tkinter могут подключаться к элементам меню. Например, попробуем добавить кнопку-флажок и три переключателя в качестве меню «Опции». При этом разделим Checkbutton и радиокнопки между собой с помощью линии. В конечном итоге, все должно выглядеть приблизительно так.
Здесь будут храниться две переменные, хранящие значения таким образом, что их можно будет получить в других методах программы.
Чтобы реализовать это на практике, используются методы add_checkbutton для добавления чекбокса и add_radiobutton для добавления радиокнопок. Все эти методы находятся в классе виджета Menu.
Аналогично обычным переключателям, здесь они все связаны одной переменной, но вот значения отличаются между собой.
В целом, код, реализующий эту программу, следующий.
import tkinter as tk class App(tk.Tk): def __init__(self): super().__init__() self.checked = tk.BooleanVar() self.checked.trace("w", self.mark_checked) self.radio = tk.StringVar() self.radio.set("1") self.radio.trace("w", self.mark_radio) menu = tk.Menu(self) submenu = tk.Menu(menu, tearoff=0) submenu.add_checkbutton(label="Checkbutton", onvalue=True, offvalue=False, variable=self.checked) submenu.add_separator() submenu.add_radiobutton(label="Radio 1", value="1", variable=self.radio) submenu.add_radiobutton(label="Radio 2", value="2", variable=self.radio) submenu.add_radiobutton(label="Radio 3", value="3", variable=self.radio) menu.add_cascade(label="Опции", menu=submenu) menu.add_command(label="Выход", command=self.destroy) self.config(menu=menu) def mark_checked(self, *args): print(self.checked.get()) def mark_radio(self, *args): print(self.radio.get()) if __name__ == "__main__": app = App() app.mainloop()
Все значения переменных будут отслеживаться таким образом, чтобы была возможность наблюдать за ними в консоли в ходе выполнения программы.
Принцип создания переменных в меню
Насколько мы знаем, любой чекбокс имеет два состояния – True и False. Первое активируется, когда флажок стоит, а второе – когда нет. Чтобы была возможность получать состояние меню с флажком, используется BooleanVar. Потом же нужно использовать параметр variable использующийся в качестве аргумента метода add_checkbutton.
Необходимо учесть то, что параметры onvalue и offvalue в норме совпадают с типами переменных Tkinter.
self.checked = tk.BooleanVar() self.checked.trace("w", self.mark_checked) # ... submenu.add_checkbutton(label="Checkbutton", onvalue=True, offvalue=False, variable=self.checked)
Создание радиокнопок мало чем отличается, только используется переменная строкового типа для хранения состояния нажатой радиокнопки. В свою очередь, пользователь может задавать свой вариант переменной в случае активации той или иной кнопки. Не обязательно использовать цифровые значения, как в этом примере.
self.radio = tk.StringVar() self.radio.set("1") self.radio.trace("w", self.mark_radio) # ... submenu.add_radiobutton(label="Radio 1", value="1", variable=self.radio) submenu.add_radiobutton(label="Radio 2", value="2", variable=self.radio) submenu.add_radiobutton(label="Radio 3", value="3", variable=self.radio)
При этом для того, чтобы переменные изменялись при активации той или иной радиокнопки или чекбокса, используются методы mark_radio и mark_checked соответственно. С их помощью можно вывести значения соответствующих переменных в консоль.
Реализация контекстного меню в Tkinter
Насколько мы знаем, строка меню – не единственный способ реализации меню в Python. Также можно реализовать контекстное меню, которое реализуется путем нажатия правой кнопки мыши по соответствующему элементу.
Контекстные меню являются очень популярными в приложениях с графическим интерфейсом. Например, невозможно представить файлового менеджера без них, поскольку так реализуется интуитивность процесса работы с приложением.
Давайте попробуем сгенерировать контекстное меню для текстового поля, позволяющего выполнять стандартные действия с текстом: вырезать часть, копировать, вставлять или удалять.
Выглядеть эта программа будет так.
Чтобы не настраивать меню в качестве контейнера, можно просто использовать функцию post, чтобы задать положение. Поскольку это метод, то она должна вызываться непосредственно из меню. Посмотрите, как это реализовано.
import tkinter as tk class App(tk.Tk): def __init__(self): super().__init__() self.menu = tk.Menu(self, tearoff=0) self.menu.add_command(label="Вырезать", command=self.cut_text) self.menu.add_command(label="Копировать", command=self.copy_text) self.menu.add_command(label="Вставить", command=self.paste_text) self.menu.add_command(label="Удалить", command=self.delete_text) self.text = tk.Text(self, height=10, width=50) self.text.bind("", self.show_popup) self.text.pack() def show_popup(self, event): self.menu.post(event.x_root, event.y_root) def cut_text(self): self.copy_text() self.delete_text() def copy_text(self): selection = self.text.tag_ranges(tk.SEL) if selection: self.clipboard_clear() self.clipboard_append(self.text.get(*selection)) def paste_text(self): self.text.insert(tk.INSERT, self.clipboard_get()) def delete_text(self): selection = self.text.tag_ranges(tk.SEL) if selection: self.text.delete(*selection) if __name__ == "__main__": app = App() app.mainloop()
Здесь мы во всех командах использовали метод, который определяет выделение или позицию для вставки, актуальную для настоящего момента.
Принцип реализации контекстного меню
Осуществляем связь события нажатия правой кнопкой мышью с обработчиком show_popup. После этого после клика будет выводиться меню. Меню будет одинаковым, независимо от того, сколько раз произойдет это событие.
А чтобы взаимодействовать с буфером обмена, мы использовали следующие функции.
- clipboard_clear() — убирает любую информацию из буфера обмена.
- clipboard_append(string) — добавляет информацию из буфера.
- clipboard_get() — получает информацию.
Как видим, нет ничего сложного в том, чтобы работать с меню в Tkinter.