Application Example¶
The Application example shows how to implement a standard GUI application with menus, toolbars, and a status bar. The example itself is a simple text editor program built around
QPlainTextEdit
.Nearly all of the code for the Application example is in the
MainWindow
class, which inheritsQMainWindow
.QMainWindow
provides the framework for windows that have menus, toolbars, dock windows, and a status bar. The application provides File, Edit, and Help entries in the menu bar, with the following popup menus:The status bar at the bottom of the main window shows a description of the menu item or toolbar button under the cursor.
To keep the example simple, recently opened files aren’t shown in the File menu, even though this feature is desired in 90% of applications. Furthermore, this example can only load one file at a time. The SDI and MDI examples show how to lift these restrictions and how to implement recently opened files handling.
MainWindow Class Definition¶
Here’s the class definition:
class MainWindow(QMainWindow): def __init__(self, parent=None): self.textEdit = QPlainTextEdit() self.curFile = "" # ... def loadFile(self, fileName): pass def closeEvent(self, event): pass def newFile(self): pass def open(self): pass def save(self): pass def saveAs(self): pass def about(self): pass def documentWasModified(self): pass # Enable this only if QT_NO_SESSIONMANAGER is not defined # def commitData(self): # pass def createActions(self): pass def createStatusBar(self): pass def readSettings(self): pass def writeSettings(self): pass def maybeSave(self): pass def saveFile(self, fileName): pass def setCurrentFile(self, fileName): pass def strippedName(self, fullFileName): passThe public API is restricted to the constructor. In the
protected
section, we reimplementcloseEvent()
to detect when the user attempts to close the window, and warn the user about unsaved changes. In theprivate slots
section, we declare slots that correspond to menu entries, as well as a mysteriousdocumentWasModified()
slot. Finally, in theprivate
section of the class, we have various members that will be explained in due time.
MainWindow Class Implementation¶
from PySide2.QtCore import Qt, QFile, QFileInfo, QSettings, QTextStream from PySide2.QtGui import QIcon from PySide2.Widgets import (QAction, QApplication, QFileDialog, QMainWindow, QPlainTextEdit, QFileDialog, QMessageBox, )We start by including
<QtWidgets>
, a header file that contains the definition of all classes in the Qt Core, Qt GUI and Qt Widgets modules. This saves us from the trouble of having to include every class individually. We also includemainwindow.h
.You might wonder why we don’t include
<QtWidgets>
inmainwindow.h
and be done with it. The reason is that including such a large header from another header file can rapidly degrade performances. Here, it wouldn’t do any harm, but it’s still generally a good idea to include only the header files that are strictly necessary from another header file.def __init__(self, parent=None): QMainWindow.__init__(self) self.textEdit = QPlainTextEdit() self.setCentralWidget(textEdit) self.createActions() self.createMenus() self.createToolBars() self.createStatusBar() self.readSettings() self.textEdit.document().contentsChanged.connect(self.documentWasModified) self.setCurrentFile("") self.setUnifiedTitleAndToolBarOnMac(True)In the constructor, we start by creating a
QPlainTextEdit
widget as a child of the main window (thethis
object). Then we callsetCentralWidget()
to tell that this is going to be the widget that occupies the central area of the main window, between the toolbars and the status bar.Then we call
createActions()
andcreateStatusBar()
, two private functions that set up the user interface. After that, we callreadSettings()
to restore the user’s preferences.We establish a signal-slot connection between the
QPlainTextEdit
‘s document object and ourdocumentWasModified()
slot. Whenever the user modifies the text in theQPlainTextEdit
, we want to update the title bar to show that the file was modified.At the end, we set the window title using the private
setCurrentFile()
function. We’ll come back to this later.def closeEvent(self, event): if maybeSave(): writeSettings() event.accept() else: event.ignore()When the user attempts to close the window, we call the private function
maybeSave()
to give the user the possibility to save pending changes. The function returns true if the user wants the application to close; otherwise, it returns false. In the first case, we save the user’s preferences to disk and accept the close event; in the second case, we ignore the close event, meaning that the application will stay up and running as if nothing happened.def File(self): if maybeSave(): textEdit.clear() setCurrentFile("")The
newFile()
slot is invoked when the user selects File|New from the menu. We callmaybeSave()
to save any pending changes and if the user accepts to go on, we clear theQPlainTextEdit
and call the private functionsetCurrentFile()
to update the window title and clear thewindowModified
flag.def open(self): if maybeSave(): fileName = QFileDialog.getOpenFileName(self) if not fileName.isEmpty(): loadFile(fileName)The
open()
slot is invoked when the user clicks File|Open. We pop up aQFileDialog
asking the user to choose a file. If the user chooses a file (i.e.,fileName
is not an empty string), we call the private functionloadFile()
to actually load the file.def save(self): if curFile.isEmpty(): return saveAs() else: return saveFile(curFile)The
save()
slot is invoked when the user clicks File|Save. If the user hasn’t provided a name for the file yet, we callsaveAs()
; otherwise, we call the private functionsaveFile()
to actually save the file.def saveAs(self): fileName = QFileDialog.getSaveFileName(self) if fileName.isEmpty(): return False return saveFile(fileName)In
saveAs()
, we start by popping up aQFileDialog
asking the user to provide a name. If the user clicks Cancel, the returned file name is empty, and we do nothing.def about(self): QMessageBox.about(self, tr("About Application"), tr("The <b>Application</b> example demonstrates how to " "write modern GUI applications using Qt, with a menu bar, " "toolbars, and a status bar."))The application’s About box is done using one statement, using the
about()
static function and relying on its support for an HTML subset.The
tr()
call around the literal string marks the string for translation. It is a good habit to calltr()
on all user-visible strings, in case you later decide to translate your application to other languages. The Internationalization with Qt overview coverstr()
in more detail.def documentWasModified(self): setWindowModified(textEdit.document().isModified())The
documentWasModified()
slot is invoked each time the text in theQPlainTextEdit
changes because of user edits. We callsetWindowModified()
to make the title bar show that the file was modified. How this is done varies on each platform.def MainWindow.createActions(self): Act = QAction(QIcon(":/images/new.png"), tr("&New"), self) Act.setShortcuts(QKeySequence.New) Act.setStatusTip(tr("Create a new file")) Act.triggered.connect(newFile) openAct = QAction(QIcon(":/images/open.png"), tr("&Open..."), self) openAct.setShortcuts(QKeySequence.Open) openAct.setStatusTip(tr("Open an existing file")) openAct.triggered.connect(open) ... aboutQtAct = QAction(tr("About &Qt"), self) aboutQtAct.setStatusTip(tr("Show the Qt library's About box")) aboutQtAct.triggered.connect(qApp.aboutQt)The
createActions()
private function, which is called from theMainWindow
constructor, createsQAction
s and populates the menus and two toolbars. The code is very repetitive, so we show only the actions corresponding to File|New, File|Open, and Help|About Qt.A
QAction
is an object that represents one user action, such as saving a file or invoking a dialog. An action can be put in aQMenu
or aQToolBar
, or both, or in any other widget that reimplementsactionEvent()
.An action has a text that is shown in the menu, an icon, a shortcut key, a tooltip, a status tip (shown in the status bar), a “What’s This?” text, and more. It emits a
triggered()
signal whenever the user invokes the action (e.g., by clicking the associated menu item or toolbar button).Instances of
QAction
can be created by passing a parentQObject
or by using one of the convenience functions ofQMenu
,QMenuBar
orQToolBar
. We create the actions that are in a menu as well as in a toolbar parented on the window to prevent ownership issues. For actions that are only in the menu, we use the convenience functionaddAction()
, which allows us to pass text, icon and the target object and its slot member function.Creating toolbars is very similar to creating menus. The same actions that we put in the menus can be reused in the toolbars. After creating the action, we add it to the toolbar using
addAction()
.The code above contains one more idiom that must be explained. For some of the actions, we specify an icon as a
QIcon
to theQAction
constructor. We usefromTheme()
to obtain the correct standard icon from the underlying window system. If that fails due to the platform not supporting it, we pass a file name as fallback. Here, the file name starts with:
. Such file names aren’t ordinary file names, but rather path in the executable’s stored resources. We’ll come back to this when we review theapplication.qrc
file that’s part of the project.cutAct.setEnabled(False) copyAct.setEnabled(False) textEdit.copyAvailable[bool].connect(cutAct.setEnabled) textEdit.copyAvailable[bool].connect(copyAct.setEnabled) }The Edit|Cut and Edit|Copy actions must be available only when the
QPlainTextEdit
contains selected text. We disable them by default and connect thecopyAvailable()
signal to thesetEnabled()
slot, ensuring that the actions are disabled when the text editor has no selection.Just before we create the Help menu, we call
addSeparator()
. This has no effect for most widget styles (e.g., Windows and macOS styles), but for some styles this makes sure that Help is pushed to the right side of the menu bar.def createStatusBar(self): statusBar().showMessage(tr("Ready"))
statusBar()
returns a pointer to the main window’sQStatusBar
widget. Like withmenuBar()
, the widget is automatically created the first time the function is called.def readSettings(self): settings("Trolltech", "Application Example") pos = settings.value("pos", QPoint(200, 200)).toPoint() size = settings.value("size", QSize(400, 400)).toSize() resize(size) move(pos)The
readSettings()
function is called from the constructor to load the user’s preferences and other application settings. TheQSettings
class provides a high-level interface for storing settings permanently on disk. On Windows, it uses the (in)famous Windows registry; on macOS, it uses the native XML-based CFPreferences API; on Unix/X11, it uses text files.The
QSettings
constructor takes arguments that identify your company and the name of the product. This ensures that the settings for different applications are kept separately.We use
value()
to extract the value of the geometry setting. The second argument tovalue()
is optional and specifies a default value for the setting if there exists none. This value is used the first time the application is run.We use
saveGeometry()
and Widget::restoreGeometry() to save the position. They use an opaqueQByteArray
to store screen number, geometry and window state.def writeSettings(self): settings = QSettings("Trolltech", "Application Example") settings.setValue("pos", pos()) settings.setValue("size", size())The
writeSettings()
function is called fromcloseEvent()
. Writing settings is similar to reading them, except simpler. The arguments to theQSettings
constructor must be the same as inreadSettings()
.def maybeSave(self): if textEdit.document()->isModified(): ret = QMessageBox.warning(self, tr("Application"), tr("The document has been modified.\n" "Do you want to save your changes?"), QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel) if ret == QMessageBox.Save: return save() elif ret == QMessageBox.Cancel: return False return TrueThe
maybeSave()
function is called to save pending changes. If there are pending changes, it pops up aQMessageBox
giving the user to save the document. The options areYes
,No
, andCancel
. The Yes button is made the default button (the button that is invoked when the user presses Return) using theDefault
flag; the Cancel button is made the escape button (the button that is invoked when the user presses Esc) using theEscape
flag.The
maybeSave()
function returnstrue
in all cases, except when the user clicks Cancel or saving the file fails. The caller must check the return value and stop whatever it was doing if the return value isfalse
.def loadFile(self, fileName): file = QFile(fileName) if !file.open(QFile.ReadOnly | QFile.Text): QMessageBox.warning(self, tr("Application"), tr("Cannot read file " "{}:\n{}.".format(fileName, file.errorString()))) return in = QTextStream(file) QApplication.setOverrideCursor(Qt::WaitCursor) textEdit.setPlainText(in.readAll()) QApplication.restoreOverrideCursor() self.setCurrentFile(fileName) self.statusBar().showMessage(tr("File loaded"), 2000)In
loadFile()
, we useQFile
andQTextStream
to read in the data. TheQFile
object provides access to the bytes stored in a file.We start by opening the file in read-only mode. The
Text
flag indicates that the file is a text file, not a binary file. On Unix and macOS, this makes no difference, but on Windows, it ensures that the “\r\n” end-of-line sequence is converted to “\n” when reading.If we successfully opened the file, we use a
QTextStream
object to read in the data.QTextStream
automatically converts the 8-bit data into a UnicodeQString
and supports various encodings. If no encoding is specified,QTextStream
assumes the file is written using the system’s default 8-bit encoding (for example, Latin-1; seecodecForLocale()
for details).Since the call to
readAll()
might take some time, we set the cursor to beWaitCursor
for the entire application while it goes on.At the end, we call the private
setCurrentFile()
function, which we’ll cover in a moment, and we display the string “File loaded” in the status bar for 2 seconds (2000 milliseconds).def saveFile(self, fileName): file = QFile(fileName) if !file.open(QFile.WriteOnly | QFile::Text): QMessageBox.warning(self, tr("Application"), tr("Cannot write file %1:\n%2.") .arg(fileName) .arg(file.errorString())) return False out = QTextStream(file) QApplication.setOverrideCursor(Qt.WaitCursor) out << textEdit.toPlainText() QApplication.restoreOverrideCursor() setCurrentFile(fileName) statusBar().showMessage(tr("File saved"), 2000) return TrueSaving a file is similar to loading one. We use
QSaveFile
to ensure all data are safely written and existing files are not damaged should writing fail. We use theText
flag to make sure that on Windows, “\n” is converted into “\r\n” to conform to the Windows convention.def setCurrentFile(fileName): curFile = fileName textEdit.document().setModified(False) setWindowModified(False) if curFile.isEmpty(): shownName = "untitled.txt" else: shownName = strippedName(curFile) setWindowTitle(tr("%1[*] - %2").arg(shownName).arg(tr("Application")))The
setCurrentFile()
function is called to reset the state of a few variables when a file is loaded or saved, or when the user starts editing a new file (in which casefileName
is empty). We update thecurFile
variable, clear themodified
flag and the associatedQWidget:windowModified
flag, and update the window title to contain the new file name (oruntitled.txt
).The
strippedName()
function call aroundcurFile
in thesetWindowTitle()
call shortens the file name to exclude the path. Here’s the function:def strippedName(self, fullFileName): return QFileInfo(fullFileName).fileName()
The main() Function¶
The
main()
function for this application is typical of applications that contain one main window:#include <QApplication> #include <QCommandLineParser> #include <QCommandLineOption> #include "mainwindow.h" int main(int argc, char *argv[]) { Q_INIT_RESOURCE(application); #ifdef Q_OS_ANDROID QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); #endif QApplication app(argc, argv); QCoreApplication::setOrganizationName("QtProject"); QCoreApplication::setApplicationName("Application Example"); QCoreApplication::setApplicationVersion(QT_VERSION_STR); QCommandLineParser parser; parser.setApplicationDescription(QCoreApplication::applicationName()); parser.addHelpOption(); parser.addVersionOption(); parser.addPositionalArgument("file", "The file to open."); parser.process(app); MainWindow mainWin; if (!parser.positionalArguments().isEmpty()) mainWin.loadFile(parser.positionalArguments().first()); mainWin.show(); return app.exec(); }The main function uses
QCommandLineParser
to check whether some file argument was passed to the application and loads it via MainWindow::loadFile().
The Resource File¶
As you will probably recall, for some of the actions, we specified icons with file names starting with
:
and mentioned that such file names aren’t ordinary file names, but path in the executable’s stored resources. These resources are compiledThe resources associated with an application are specified in a
.qrc
file, an XML-based file format that lists files on the disk. Here’s theapplication.qrc
file that’s used by the Application example:<Code snippet "/data/snapshot-qt5full-5.15/qt5/qtbase/mainwindows/application/application.qrc" not found>The
.png
files listed in theapplication.qrc
file are files that are part of the Application example’s source tree. Paths are relative to the directory where theapplication.qrc
file is located (themainwindows/application
directory).The resource file must be mentioned in the
application.pro
file so thatqmake
knows about it:<Code snippet "mainwindows/application/application.pro:0" not found>
qmake
will produce make rules to generate a file calledqrc_application.cpp
that is linked into the application. This file contains all the data for the images and other resources as static C++ arrays of compressed binary data. See The Qt Resource System for more information about resources.
© 2022 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.