Summary: in this tutorial, you’ll learn how to use the PyQt QMenu
class to create a menu for the application.
Introduction to PyQt QMenu class
The QMenu
class allows you to create a menu widget in menu bars, context menus, and popup menus. This tutorial focuses on how to use the QMenu
class to create menus in menu bars.
To create a menu and add it to a menu bar, you follow these steps:
- Get the menu bar of the main window by calling the
menuBar()
method of theQMainWindow
object. - Add a menu to the menu bar using the
method. TheaddMenu()
returns a new instance of theaddMenu()
QMenu
class.
The following shows how to add three menus to the menu bar of the main window including File, Edit, and Help:
menu_bar = self.menuBar()
file_menu = menu_bar.addMenu('&File')
edit_menu = menu_bar.addMenu('&Edit')
help_menu = menu_bar.addMenu('&Help')
Code language: Python (python)
Note that the ampersand (&
) defines a shortcut to jump to the menu when pressing the Alt
key. For example, to jump to the File menu, you press the Alt-F keyboard shortcut.
Once having a menu, you can add items to it. Typically, you create a QAction
and use the addAction()
method of the QMenu
object to add actions to the menu.
To add a separator between menu items, you use the addSeparator()
method of the QMenu
object.
PyQt QMenu example
We’ll create a text editor application to demonstrate how to use the QMenu
class:
Note that the icons used in this application are from icon8.com website. Also, you can download them here.
Here’s the complete program:
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())
Code language: Python (python)
How it works.
First, create the main window using the QMainWindow
class:
class MainWindow(QMainWindow):
Code language: Python (python)
Second, set the window’s icon and geometry:
self.setWindowIcon(QIcon('./assets/editor.png'))
self.setGeometry(100, 100, 500, 300)
Code language: Python (python)
Third, initialize the text file filters, and window title, and call the set_title()
method to set the title for the window:
self.filters = 'Text Files (*.txt)'
self.title = 'Editor'
self.set_title()
Code language: Python (python)
The
method accepts a filename. If the filename is omitted, the set_title()
method sets the window’s title as set_title()
Untitled - Editor
. Otherwise, it sets the window title using the format filename - Editor
:
def set_title(self, filename=None):
title = f"{filename if filename else 'Untitled'} - {self.title}"
self.setWindowTitle(title)
Code language: Python (python)
For example, when you launch the program for the first time or create a new file, the window’s title will be:
If you open a file e.g., C:/temp/test.txt
, the window’s title will change to:
Fourth, initialize a variable that will hold the path of the file that is being opened for editing:
self.path = None
Code language: Python (python)
Note that we’ll use the Path
class from the pathlib
module to manage the file path, reading from a text file, and writing to the text file.
Fifth, create a QTextEdit
widget and set it as the central widget of the main window:
self.text_edit = QTextEdit(self)
self.setCentralWidget(self.text_edit)
Code language: Python (python)
Sixth, create a QMenuBar
object by calling the menuBar()
method of the QMainWindow
object:
menu_bar = self.menuBar()
Code language: Python (python)
Seventh, create new, open, save, and exit actions and add them to the file_menu
using the addAction()
method.
# 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)
Code language: Python (python)
It’ll result in the following menu:
Eighth, create undo and redo actions and add them to the edit menu:
# 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)
Code language: Python (python)
It’ll result in the following Edit menu:
Ninth, create the about action and add it to the Help menu:
about_action = QAction(QIcon('./assets/about.png'), 'About', self)
help_menu.addAction(about_action)
about_action.setStatusTip('About')
about_action.setShortcut('F1')
Code language: Python (python)
It’ll result in the following menu:
Tenth, add the status bar to the main window using the statusBar()
method of the QMainWindow
object:
self.status_bar = self.statusBar()
Code language: Python (python)
Note that you’ll learn more about the status bar widget in the QStatusBar
tutorial.
Eleventh, define the confirm_save()
method that prompts the user whether to save the document or not. If the user clicks the Yes button, call the save_document()
method to save the text of the QTextEdit
widget into a file.
The confirm_save()
method returns False
if the user clicks the Cancel button or True
if the user clicks either Yes
or No
button:
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
Code language: Python (python)
Twelfth, define the new_document()
method that runs when the user selects the New menu item:
def new_document(self):
if self.confirm_save():
self.text_edit.setText('')
self.set_title()
Code language: Python (python)
The new_document()
method calls the confirm_save()
method to save the document and set the text of the QTextEdit
to blank. Also, it resets the title of the main window.
Thirteenth, define the save_document()
method to save the text of the QTextEdit
widget to a text file:
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)
Code language: Python (python)
If the user opens a file, then the self.path
is not None
, it gets the text of the QTextEdit
widget by calling the toPlainText()
method and saves the text to the file specified by the Path object using the write_text()
method.
If the user has not opened a file, the method shows a Save File Dialog using the QFileDialog
and writes the text to the file that is currently opened.
Fourteenth, define the open_document()
method that shows the Open File Dialog and loads the contents from a text file into the QTextEdit
widget:
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)
Code language: Python (python)
Since the filename changes, it calls the set_title()
method to set the title of the QMainWindow
.
Fifteenth, define the quit()
method that runs when the user selects the Exit menu item:
def quit(self):
if self.confirm_save():
self.destroy()
Code language: Python (python)
Finally, if you run the program on Windows, the taskbar will not display the main window icon correctly. To fix it, you use the following code:
import ctypes
myappid = 'mycompany.myproduct.subproduct.version'
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid)
Code language: JavaScript (javascript)
If you execute the program in macOS or Linux, this code will raise an import error. Therefore, we wrap it into a try
block:
try:
import ctypes
myappid = 'mycompany.myproduct.subproduct.version'
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid)
finally:
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec())
Code language: JavaScript (javascript)
Summary
- Qt uses the
QMenu
class to represent a menu widget. - Use the
menuBar()
method of theQMainWindow
to create a menu bar andaddMenu()
method to add a new menu bar. - Use the
addAction()
method of theQMenu
object to add an item to a menu.