Using .ui files from Designer or QtCreator with QUiLoader and pyside6-uic#

This page describes the use of Qt Widgets Designer to create graphical interfaces based on Qt Widgets for your Qt for Python project. Qt Widgets Designer is a graphical UI design tool which is available as a standalone binary (pyside6-designer) or embedded into the Qt Creator IDE. Its use within Qt Creator is described at Using Qt Widgets Designer.

Designer and the equivalent code

The designs are stored in .ui files, which is an XML-based format. It will be converted to Python or C++ code populating a widget instance at project build time by the pyside6-uic tool.

To create a new Qt Design Form in Qt Creator, choose File/New File Or Project and “Main Window” for template. Save it as mainwindow.ui. Add a QPushButton to the center of the centralwidget.

Your file mainwindow.ui should look something like this:

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>400</width>
    <height>300</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralWidget">
   <widget class="QPushButton" name="pushButton">
    <property name="geometry">
     <rect>
      <x>110</x>
      <y>80</y>
      <width>201</width>
      <height>81</height>
     </rect>
    </property>
    <property name="text">
     <string>PushButton</string>
    </property>
   </widget>
  </widget>
  <widget class="QMenuBar" name="menuBar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>400</width>
     <height>20</height>
    </rect>
   </property>
  </widget>
  <widget class="QToolBar" name="mainToolBar">
   <attribute name="toolBarArea">
    <enum>TopToolBarArea</enum>
   </attribute>
   <attribute name="toolBarBreak">
    <bool>false</bool>
   </attribute>
  </widget>
  <widget class="QStatusBar" name="statusBar"/>
 </widget>
 <layoutdefault spacing="6" margin="11"/>
 <resources/>
 <connections/>
</ui>

Now we are ready to decide how to use the UI file from Python.

Option A: Generating a Python class#

The standard way to interact with a UI file is to generate a Python class from it. This is possible thanks to the pyside6-uic tool. To use this tool, you need to run the following command on a console:

pyside6-uic mainwindow.ui -o ui_mainwindow.py

We redirect all the output of the command to a file called ui_mainwindow.py, which will be imported directly:

from ui_mainwindow import Ui_MainWindow

Now to use it, we should create a personalized class for our widget to setup this generated design.

To understand the idea, let’s take a look at the whole code:

import sys
from PySide6.QtWidgets import QApplication, QMainWindow
from PySide6.QtCore import QFile
from ui_mainwindow import Ui_MainWindow

class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

if __name__ == "__main__":
    app = QApplication(sys.argv)

    window = MainWindow()
    window.show()

    sys.exit(app.exec())

What is inside the if statement is already known from the previous examples, and our new basic class contains only two new lines that are in charge of loading the generated python class from the UI file:

self.ui = Ui_MainWindow()
self.ui.setupUi(self)

Note

You must run pyside6-uic again every time you make changes to the UI file.

Option B: Loading it directly#

To load the UI file directly, we will need a class from the QtUiTools module:

from PySide6.QtUiTools import QUiLoader

The QUiLoader lets us load the ui file dynamically and use it right away:

ui_file = QFile("mainwindow.ui")
ui_file.open(QFile.ReadOnly)

loader = QUiLoader()
window = loader.load(ui_file)
window.show()

The complete code of this example looks like this:

# File: main.py
import sys
from PySide6.QtUiTools import QUiLoader
from PySide6.QtWidgets import QApplication
from PySide6.QtCore import QFile, QIODevice

if __name__ == "__main__":
    app = QApplication(sys.argv)

    ui_file_name = "mainwindow.ui"
    ui_file = QFile(ui_file_name)
    if not ui_file.open(QIODevice.ReadOnly):
        print(f"Cannot open {ui_file_name}: {ui_file.errorString()}")
        sys.exit(-1)
    loader = QUiLoader()
    window = loader.load(ui_file)
    ui_file.close()
    if not window:
        print(loader.errorString())
        sys.exit(-1)
    window.show()

    sys.exit(app.exec())

Then to execute it we just need to run the following on a command prompt:

python main.py

Note

QUiLoader uses connect() calls taking the function signatures as string arguments for signal/slot connections. It is thus unable to handle Python types like str or list from custom widgets written in Python since these types are internally mapped to different C++ types.

Custom Widgets in Qt Widgets Designer#

Qt Widgets Designer is able to use user-provided (custom) widgets. They are shown in the widget box and can be dragged onto the form just like Qt’s widgets (see Using Custom Widgets with Qt Widgets Designer ). Normally, this requires implementing the widget as a plugin to Qt Widgets Designer written in C++ implementing its QDesignerCustomWidgetInterface .

Qt for Python provides a simple interface for this which is similar to registerCustomWidget().

The widget needs to be provided as a Python module, as shown by the WigglyWidget Example (file wigglywidget.py) or the Task Menu Extension Example (file tictactoe.py).

Registering this with Qt Widgets Designer is done by providing a registration script named register*.py and pointing the path-type environment variable PYSIDE_DESIGNER_PLUGINS to the directory.

The code of the registration script looks as follows:

# File: registerwigglywidget.py
from wigglywidget import WigglyWidget

import QtDesigner


TOOLTIP = "A cool wiggly widget (Python)"
DOM_XML = """
<ui language='c++'>
    <widget class='WigglyWidget' name='wigglyWidget'>
        <property name='geometry'>
            <rect>
                <x>0</x>
                <y>0</y>
                <width>400</width>
                <height>200</height>
            </rect>
        </property>
        <property name='text'>
            <string>Hello, world</string>
        </property>
    </widget>
</ui>
"""

QPyDesignerCustomWidgetCollection.registerCustomWidget(WigglyWidget, module="wigglywidget",
                                                       tool_tip=TOOLTIP, xml=DOM_XML)

QPyDesignerCustomWidgetCollection provides an implementation of QDesignerCustomWidgetCollectionInterface exposing custom widgets to Qt Widgets Designer with static convenience functions for registering types or adding instances of QDesignerCustomWidgetInterface .

The function registerCustomWidget() is used to register a widget type with Qt Widgets Designer. In the simple case, it can be used like QUiLoader.registerCustomWidget(). It takes the custom widget type and some optional keyword arguments passing values that correspond to the getters of QDesignerCustomWidgetInterface :

When launching Qt Widgets Designer via its launcher pyside6-designer, the custom widget should be visible in the widget box.

For advanced usage, it is also possible to pass the function an implementation of the class QDesignerCustomWidgetInterface instead of the type to addCustomWidget(). This is shown in taskmenuextension example, where a custom context menu is registered for the custom widget. The example is a port of the corresponding C++ Task Menu Extension Example .

Troubleshooting the Qt Widgets Designer Plugin#

  • The launcher pyside6-designer must be used. The standalone Qt Widgets Designer will not load the plugin.

  • The menu item Help/About Plugin brings up a dialog showing the plugins found and potential load error messages.

  • Check the console or Windows Debug view for further error messages.

  • Due to the buffering of output by Python, error messages may appear only after Qt Widgets Designer has terminated.

  • When building Qt for Python, be sure to set the --standalone option for the plugin to be properly installed.