PyQt QMenu в Python — создание меню приложения
Класс PyQt QMenu позволяет создавать виджеты меню в строках меню, контекстных меню и всплывающих меню.
Знакомство с PyQt QMenu
В этом руководстве основное внимание уделяется использованию класса QMenu для создания меню в строках меню.
Чтобы создать меню и добавить его в строку меню, выполните следующие действия:
- Получите строку меню главного окна, вызвав метод menuBar() объекта QMainWindow.
- Добавьте меню в строку меню с помощью метода addMenu(). AddMenu() возвращает новый экземпляр класса QMenu.
Ниже показано, как добавить три меню в строку меню главного окна, включая «File», «Edit» и «Help»:
menu_bar = self.menuBar() file_menu = menu_bar.addMenu('&File') edit_menu = menu_bar.addMenu('&Edit') help_menu = menu_bar.addMenu('&Help')
Обратите внимание, что амперсанд(&) определяет сочетание клавиш для перехода в меню при нажатии клавиши Alt. Например, чтобы перейти в меню Файл, нажмите сочетание клавиш Alt-F.
После создания меню вы можете добавлять в него элементы. Обычно вы создаете QAction и используете метод addAction() объекта QMenu для добавления действий в меню.
Чтобы добавить разделитель между пунктами меню, используйте метод addSeparator() объекта QMenu.
Пример PyQt QMenu
Мы создадим приложение текстового редактора, чтобы продемонстрировать, как использовать класс QMenu:
Обратите внимание, что иконки, используемые в этом приложении, взяты с сайта icon8.com.
Вот полная программа:
import sys from pathlib import Path from PyQt6.QtWidgets import QApplication, QMainWindow, QTextEdit, QFileDialog, QMessageBox, QWidget, QVBoxLayout from PyQt6.QtGui import QIcon, QAction class MainWindow(QMainWindow): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.setWindowIcon(QIcon('./assets/editor.png')) self.setGeometry(100, 100, 500, 300) m = 30 self.title = 'Editor' self.filters = 'Text Files(*.txt)' self.set_title() self.path = None self.text_edit = QTextEdit(self) # self.setCentralWidget(self.text_edit) container = QWidget(self) container.setLayout(QVBoxLayout()) container.layout().addWidget(self.text_edit) self.setCentralWidget(container) # container.setContentsMargins(5, 5, 5, 5) menu_bar = self.menuBar() file_menu = menu_bar.addMenu('&File') edit_menu = menu_bar.addMenu('&Edit') help_menu = menu_bar.addMenu('&Help') # new menu item new_action = QAction(QIcon('./assets/new.png'), '&New', self) new_action.setStatusTip('Create a new document') new_action.setShortcut('Ctrl+N') new_action.triggered.connect(self.new_document) file_menu.addAction(new_action) # open menu item open_action = QAction(QIcon('./assets/open.png'), '&Open...', self) open_action.triggered.connect(self.open_document) open_action.setStatusTip('Open a document') open_action.setShortcut('Ctrl+O') file_menu.addAction(open_action) # save menu item save_action = QAction(QIcon('./assets/save.png'), '&Save', self) save_action.setStatusTip('Save the document') save_action.setShortcut('Ctrl+S') save_action.triggered.connect(self.save_document) file_menu.addAction(save_action) file_menu.addSeparator() # exit menu item exit_action = QAction(QIcon('./assets/exit.png'), '&Exit', self) exit_action.setStatusTip('Exit') exit_action.setShortcut('Alt+F4') exit_action.triggered.connect(self.quit) file_menu.addAction(exit_action) # edit menu undo_action = QAction(QIcon('./assets/undo.png'), '&Undo', self) undo_action.setStatusTip('Undo') undo_action.setShortcut('Ctrl+Z') undo_action.triggered.connect(self.text_edit.undo) edit_menu.addAction(undo_action) redo_action = QAction(QIcon('./assets/redo.png'), '&Redo', self) redo_action.setStatusTip('Redo') redo_action.setShortcut('Ctrl+Y') redo_action.triggered.connect(self.text_edit.redo) edit_menu.addAction(redo_action) about_action = QAction(QIcon('./assets/about.png'), 'About', self) help_menu.addAction(about_action) about_action.setStatusTip('About') about_action.setShortcut('F1') # status bar self.status_bar = self.statusBar() self.show() def set_title(self, filename=None): title = f"{filename if filename else 'Untitled'} - {self.title}" self.setWindowTitle(title) def confirm_save(self): if not self.text_edit.document().isModified(): return True message = f"Do you want to save changes to {self.path if self.path else 'Untitled'}?" MsgBoxBtn = QMessageBox.StandardButton MsgBoxBtn = MsgBoxBtn.Save | MsgBoxBtn.Discard | MsgBoxBtn.Cancel button = QMessageBox.question( self, self.title, message, buttons=MsgBoxBtn ) if button == MsgBoxBtn.Cancel: return False if button == MsgBoxBtn.Save: self.save_document() return True def new_document(self): if self.confirm_save(): self.text_edit.clear() self.set_title() def save_document(self): # save the currently openned file if(self.path): return self.path.write_text(self.text_edit.toPlainText()) # save a new file filename, _ = QFileDialog.getSaveFileName( self, 'Save File', filter=self.filters ) if not filename: return self.path = Path(filename) self.path.write_text(self.text_edit.toPlainText()) self.set_title(filename) def open_document(self): filename, _ = QFileDialog.getOpenFileName(self, filter=self.filters) if filename: self.path = Path(filename) self.text_edit.setText(self.path.read_text()) self.set_title(filename) def quit(self): if self.confirm_save(): self.destroy() if __name__ == '__main__': try: import ctypes myappid = 'mycompany.myproduct.subproduct.version' ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid) finally: app = QApplication(sys.argv) window = MainWindow() sys.exit(app.exec())
Как это работает.
- Сначала создайте главное окно с помощью класса QMainWindow:
class MainWindow(QMainWindow):
- Во-вторых, задайте значок и геометрию окна:
self.setWindowIcon(QIcon('./assets/editor.png')) self.setGeometry(100, 100, 500, 300)
- В-третьих, инициализируйте фильтры текстового файла и заголовок окна, а затем вызовите метод set_title(), чтобы задать заголовок окна:
self.filters = 'Text Files(*.txt)' self.title = 'Editor' self.set_title()
Метод set_title() принимает имя файла. Если имя файла опущено, метод set_title() устанавливает заголовок окна как Untitled — Editor. В противном случае он устанавливает заголовок окна, используя формат filename — Editor:
def set_title(self, filename=None): title = f"{filename if filename else 'Untitled'} - {self.title}" self.setWindowTitle(title)
Например, при первом запуске программы или создании нового файла заголовок окна будет следующим:
Если вы откроете файл, например, C:/temp/test.txt, заголовок окна изменится на:
- В-четвертых, инициализируйте переменную, которая будет содержать путь к файлу, открываемому для редактирования:
self.path = None
Обратите внимание, что мы будем использовать класс Path из модуля pathlib для управления путем к файлу, чтения из текстового файла и записи в текстовый файл.
- В-пятых, создайте виджет QTextEdit и установите его в качестве центрального виджета главного окна:
self.text_edit = QTextEdit(self) self.setCentralWidget(self.text_edit)
- В-шестых, создайте объект QMenuBar, вызвав метод menuBar() объекта QMainWindow:
menu_bar = self.menuBar()
- В-седьмых, создайте действия «new», «open», «save» и «exit» и добавьте их в file_menu с помощью метода addAction().
# new menu item new_action = QAction(QIcon('./assets/new.png'), '&New', self) new_action.setStatusTip('Create a new document') new_action.setShortcut('Ctrl+N') new_action.triggered.connect(self.new_document) file_menu.addAction(new_action) # open menu item open_action = QAction(QIcon('./assets/open.png'), '&Open...', self) open_action.triggered.connect(self.open_document) open_action.setStatusTip('Open a document') open_action.setShortcut('Ctrl+O') file_menu.addAction(open_action) # save menu item save_action = QAction(QIcon('./assets/save.png'), '&Save', self) save_action.setStatusTip('Save the document') save_action.setShortcut('Ctrl+S') save_action.triggered.connect(self.save_document) file_menu.addAction(save_action) file_menu.addSeparator() # exit menu item exit_action = QAction(QIcon('./assets/exit.png'), '&Exit', self) exit_action.setStatusTip('Exit') exit_action.setShortcut('Alt+F4') exit_action.triggered.connect(self.quit) file_menu.addAction(exit_action)
В результате появится следующее меню:
- В-восьмых, создайте действия отмены и повтора и добавьте их в меню редактирования:
# edit menu undo_action = QAction(QIcon('./assets/undo.png'), '&Undo', self) undo_action.setStatusTip('Undo') undo_action.setShortcut('Ctrl+Z') undo_action.triggered.connect(self.text_edit.undo) edit_menu.addAction(undo_action) redo_action = QAction(QIcon('./assets/redo.png'), '&Redo', self) redo_action.setStatusTip('Redo') redo_action.setShortcut('Ctrl+Y') redo_action.triggered.connect(self.text_edit.redo) edit_menu.addAction(redo_action)
В результате появится следующее меню «Edit»:
- В-девятых, создайте действие «about action» и добавьте его в меню «Help»:
about_action = QAction(QIcon('./assets/about.png'), 'About', self) help_menu.addAction(about_action) about_action.setStatusTip('About') about_action.setShortcut('F1')
В результате появится следующее меню:
- В-десятых, добавьте строку состояния в главное окно, используя метод statusBar() объекта QMainWindow:
self.status_bar = self.statusBar()
Обратите внимание, что более подробную информацию о виджете строки состояния вы найдете в руководстве QStatusBar.
- Одиннадцатое, определите метод confirmed_save(), который запрашивает у пользователя, сохранять документ или нет. Если пользователь нажимает кнопку Yes, вызовите метод save_document(), чтобы сохранить текст виджета QTextEdit в файл.
Метод confirmed_save() возвращает значение False, если пользователь нажимает кнопку «Отмена», или значение True, если пользователь нажимает кнопку «Да» или «Нет»:
def confirm_save(self): if not self.text_edit.document().isModified(): return True message = f"Do you want to save changes to {self.path if self.path else 'Untitled'}?" MsgBoxBtn = QMessageBox.StandardButton MsgBoxBtn = MsgBoxBtn.Save | MsgBoxBtn.Discard | MsgBoxBtn.Cancel button = QMessageBox.question( self, self.title, message, buttons=MsgBoxBtn ) if button == MsgBoxBtn.Cancel: return False if button == MsgBoxBtn.Save: self.save_document() return True
- Двенадцатое, определите метод new_document(), который запускается, когда пользователь выбирает пункт меню «New»:
def new_document(self): if self.confirm_save(): self.text_edit.setText('') self.set_title()
Метод new_document() вызывает метод confirmed_save() для сохранения документа и установки текста QTextEdit пустым. Также он сбрасывает заголовок главного окна.
- Тринадцатое, определите метод save_document() для сохранения текста виджета QTextEdit в текстовый файл:
def save_document(self): # save the currently openned file if(self.path): return self.path.write_text(self.text_edit.toPlainText()) # save a new file filename, _ = QFileDialog.getSaveFileName( self, 'Save File', filter=self.filters ) if not filename: return self.path = Path(filename) self.path.write_text(self.text_edit.toPlainText()) self.set_title(filename)
Если пользователь открывает файл, то self.path не равен None, он получает текст виджета QTextEdit, вызывая метод toPlainText(), и сохраняет текст в файле, указанном объектом Path, с помощью метода write_text().
Если пользователь не открыл файл, метод отображает диалоговое окно сохранения файла с помощью QFileDialog и записывает текст в файл, который в данный момент открыт.
- В-четырнадцатых, определите метод open_document(), который отображает диалоговое окно открытия файла и загружает содержимое из текстового файла в виджет QTextEdit:
def open_document(self): filename, _ = QFileDialog.getOpenFileName(self, filter=self.filters) if filename: self.path = Path(filename) self.text_edit.setText(self.path.read_text()) self.set_title(filename)
Поскольку имя файла изменяется, вызывается метод set_title() для установки заголовка QMainWindow.
- Пятнадцатое, определите метод quit(), который запускается, когда пользователь выбирает пункт меню «Exit»:
def quit(self): if self.confirm_save(): self.destroy()
Наконец, если вы запустите программу на Windows, на панели задач не будет правильно отображаться значок главного окна. Чтобы исправить это, используйте следующий код:
import ctypes myappid = 'mycompany.myproduct.subproduct.version' ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid)
Если вы запустите программу в macOS или Linux, этот код вызовет ошибку импорта. Поэтому мы обернем его в блок try:
try: import ctypes myappid = 'mycompany.myproduct.subproduct.version' ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid) finally: app = QApplication(sys.argv) window = MainWindow() sys.exit(app.exec())