qtbricks.mainwindow module
Main GUI window.
There is usually one main window of a GUI, and this window consists of all the default parts provided by the Qt framework, namely menu bar and toolbars at the top, status bar at the bottom, and areas for dockable windows/widgets on all four sides of the central widget.
While the actual contents of both, the central widget and the dock areas
and the other components are more or less unique for your application,
a main window of a GUI comes with a lot of features and basic
functionality that are rather general. Hence you can greatly reduce the
amount of code to write when inheriting from the MainWindow
class
implemented here.
Note
Some ideas are borrowed and adapted from Mark Summerfield and his excellent book “Rapid GUI Programming with Python and Qt” (Prentice Hall, Upper Saddle River, 2008). While written for Python 2 and PyQt4, both the concepts and the thorough introduction to GUI programming are probably still the best that is available for creating GUIs with Python and Qt.
How to call and where to instantiate
Rather than creating an instance of MainWindow
yourself, you will
usually call the app.main()
function of the app
module,
or simply call the GUI by means of the respective gui_scripts entry point
defined in setup.py
.
Note
Of course, you will usually not use the MainWindow
as
implemented here, as it lacks all contents, but rather create your own
MainWindow
class inheriting from MainWindow
and setting
the central widgets and everything else as appropriate.
An example of an app
module is shown below. Note the fictitious
name mypackage
that you would need to replace with the actual name of
your package:
import sys
from PySide6.QtWidgets import QApplication
from PySide6.QtGui import QIcon
from qtbricks import utils
from mypackage import mainwindow
def main():
app = QApplication(sys.argv)
app.setOrganizationName("demoapp")
app.setOrganizationDomain("example.org")
app.setApplicationName("Demo application")
app.setWindowIcon(QIcon(utils.image_path("icon.svg")))
window = mainwindow.MainWindow()
window.show()
app.exec()
if __name__ == "__main__":
main()
Note that the setting of the organisation name
(app.setOrganizationName()
) is crucial for storing settings, as this
determines the name of the directory the settings are stored under. The
exact location of the configuration depends on your operating system.
Furthermore, setting the application name (app.setApplicationName()
)
is relevant for programmatically obtaining the application name, e.g.,
for window titles and alike. As you can see from the above code,
Qt is capable of handling SVG files directly, very convenient for icons
and logos of your application.
The corresponding section in the setup.py
file with the gui entrypoint
may look similar to the following:
setuptools.setup(
# ...
entry_points={
"gui_scripts": [
"demoapp = demoapp.gui.app:main"
]
},
# ...
In this particular case, both, your package and the respective GUI entrypoint, i.e. the callable from the terminal, are named “demoapp”. Of course, you will need to change this to some sensible name for your package/GUI application.
Some notes for developers
GUI programming can be quite complex, and the MainWindow
class
provides only a small set of (sensible) defaults for the most common tasks.
However, to help with creating GUIs and to keep the code as readable as
possible, some general advice and an overview of the functionality
implemented are given below.
First step: creating your own mainwindow module
The first step is always to create your own mainwindow
module,
presumably in the gui
subpackage of your package. A rather minimal
structure of this module is shown below:
from PySide6 import QtWidgets
import qtbricks.mainwindow
import mypackage.gui.model as model
class MainWindow(qtbricks.mainwindow.MainWindow):
def __init__(self):
# Customise your main window, setting at least a central widget.
# Try to keep methods as short and concise as possible.
A few comments, beyond the obvious:
You will want/need to import further modules containing the definition of the widget(s) used as central widget and possibly in dockable areas.
You should always follow the model-view paradigm for larger applications, and come up with a model of your application containing the business logic. This is reflected in the import of a model module of your fictitious
mypackage
package.
Saving and restoring GUI settings
By default, window geometry and state will be saved on close and restored on
startup. This creates a file, typically in the user’s home directory,
and depending on the respective platform. Directory and file name depend on
the settings of organisation and application name on the application level.
For details, see the app.main()
function in the app
module.
To this end, a private method MainWindow._restore_settings()
gets
called from the constructor to restore the settings, and the
:meth.`MainWindow.closeEvent` method is overridden to take care of saving
the current settings on closing the GUI.
Adding dockable windows
Generally, dockable windows need to inherit from
PySide6.QtWidgets.QDockWidget
. A prototypical class for a dockable
window is shown below. Adapt to your own needs. Of course, a dockable window
can (and usually will) contain arbitrarily complex widgets.
class GeneralDockWindow(QtWidgets.QDockWidget):
def __init__(self, title="General Dock Window"):
super().__init__()
self.setObjectName("GeneralDockWindow")
self.setWindowTitle(title)
self.list_widget = QtWidgets.QListWidget()
self.setWidget(self.list_widget)
Note that a very similar implementation is provided in the
GeneralDockWindow
class. See its documentation for further details.
The possible docking areas the window can be attached to can be restricted as well:
self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
To add such a dockable window to your main window, extend the
MainWindow._create_dock_windows()
method in a way similar to the
following:
def _create_dock_windows(self):
general_dock_window_1 = GeneralDockWindow("general 1")
self.addDockWidget(Qt.LeftDockWidgetArea, general_dock_window_1)
self._view_menu.addAction(general_dock_window_1.toggleViewAction())
Important here is to add the action to the View menu in order to be able to
restore a closed dockable window. To make things easier, the same can be
achieved using the convenience function MainWindow._add_dock_window()
:
dock_window = GeneralDockWindow()
self._add_dock_window(
dock_window,
"general 1",
Qt.LeftDockWidgetArea
)
In case you want to layout two or more dock widgets as tabbed windows, use the same method, but assign the output to a variable of your choosing, and afterwards programmatically “tabify” the respective docked windows:
dock1 = self._add_dock_window(...)
dock2 = self._add_dock_window(...)
self.tabifyDockWidget(dock1, dock2)
Just make sure that both windows you want to appear in tabs are set to the same docking area. Otherwise, the first one will “win” and drag the second one to the same docking area.
Preventing loss of work on closing the GUI
As soon as the user can change contents from within the GUI, it may no
longer be safe to simply close the window and hence quit the entire
application. The same is true for changes that need to be applied or
discarded before switching to some other dataset or else. To detect (and
handle) the situation when changes are present that have not yet been saved,
reimplement the method MainWindow._ok_to_continue()
according to your
needs. Furthermore, this method allows for convenient and readable code
similar to
if self._ok_to_continue():
...
For an actual example, you may have a look at the
MainWindow.closeEvent()
method.
Module documentation
- class qtbricks.mainwindow.MainWindow(parent=None)
Bases:
QMainWindow
Main GUI window of the application.
There is usually one main window of a GUI, and this window consists of all the default parts provided by the Qt framework, namely menu bar and toolbars at the top, status bar at the bottom, and areas for dockable windows/widgets on all four sides of the central widget.
While any more complex dialogs and widgets are usually designed using the QtDesigner tool, the main window, consisting typically of one central widget, is laid out programmatically.
Rather than creating an instance of
MainWindow
yourself, you will usually call theapp.main()
function of theapp
module, or simply call the GUI by means of the respective gui_scripts entry point defined insetup.py
.By default, window geometry and state will be saved on close and restored on startup. This creates a file, typically in the user’s home directory, and depending on the respective platform. Directory and file name depend on the settings of organisation and application name on the application level. For details, see the
app.main()
function in theapp
module.- package_name
Name of the package the mainwindow belongs to.
This information is required, i.a., for the “Help About” window.
- Type:
- _setup_ui()
Create the elements of the main window.
The elements created are:
The central widget
The status bar
The menu bar
The dockable windows
All these tasks are delegated to (non-public) methods. The toolbar will usually be created from within the menus, as the actions will be defined therein.
In its default configuration, three menus are created: “File”, “View”, and “Help”. The “File” menu is populated with a “Quit” action and associated keyboard shortcut “Ctrl + Q”, the “Help” menu with an “About” action and associated keyboard shortcut “F1”. The “View” menu is a reminder that in case of dockable windows, you need a way to restore windows closed by the user.
Additionally, the minimum size and title of the main GUI window is set. The window title is identical to the application name set in the
app.main()
function in theapp
module.
- help_about()
Show a dialog with basic information about the application.
Presenting the user with a concise summary of what the entire application is about, perhaps together with the author names, a license information, and some very basic system settings, is a sensible thing to do. Typically, this can be found in the “Help -> About” menu.
- closeEvent(event)
Actions performed when attempting to close the window.
By default, both geometry and state of the main window are saved to the settings file, to be restored on startup, using the standard Qt machinery for this purpose.
- _ok_to_continue()
Helper method determining whether it is safe to continue.
Often, certain actions shall not be performed if the application is in an unsaved state (e.g., closing the application). Therefore, implement this method according to your needs. In its default implementation, it always returns “True”.
- Returns:
status – Whether it is safe to continue
- Return type:
- class qtbricks.mainwindow.GeneralDockWindow(title='General dock window', object_name='', widget=None)
Bases:
QDockWidget
Convenience class for dockable windows.
This is a thin wrapper for widgets that should appear as dockable windows in a main application window. Note that this dock window is not restricted with respect to the dockable areas it can be positioned in. You may, however, restrict this afterwards. See the examples section for details.
- Parameters:
title (
str
) –Window title
The window title should be comprehensive, as it is used both, as window title in the dock and in the view menu of the main window.
widget (
PySide6.QtWidgets.QWidget
) – Widget to be set as (central) widget of the dockable window
Examples
To add a dockable window to your main application window, you first need to create a dockable window containing the (complex) widget of your choosing, and afterwards add it to your main application window.
If you subclass
MainWindow
for your main application window, you can make use of a series of convenience methods provided. Dockable windows should be defined within the methodMainWindow._create_dock_windows()
. You can add the dockable window using the methodMainWindow._add_dock_window()
.def _create_dock_windows(self): dock_window = qtbricks.mainwindow.GeneralDockWindow( title="My fancy dockable window", widget=QtWidgets.QListWidget(), object_name="MyDockWindow" ) self._add_dock_window(dock_window=dock_window)
This will add a dock window to the main application window. Make sure to set the object name, as otherwise, Qt will produce warnings if you try to save the application state.
If you would like to restrict the possible docking areas the dock window can be added to, you may set the respective property after initialising the object:
dock_window = qtbricks.mainwindow.GeneralDockWindow( title="My fancy dockable window", widget=QtWidgets.QListWidget(), object_name="MyDockWindow" ) dock_window.setAllowedAreas( Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea )
Note that you need to import
PySide6.QtCore.Qt
for this to work.