diff options
Diffstat (limited to 'sources/pyside6/doc/tutorials/basictutorial/uifiles.rst')
-rw-r--r-- | sources/pyside6/doc/tutorials/basictutorial/uifiles.rst | 304 |
1 files changed, 304 insertions, 0 deletions
diff --git a/sources/pyside6/doc/tutorials/basictutorial/uifiles.rst b/sources/pyside6/doc/tutorials/basictutorial/uifiles.rst new file mode 100644 index 000000000..cb945908d --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/uifiles.rst @@ -0,0 +1,304 @@ +.. _using_ui_files: + +Using ``.ui`` files from Designer or QtCreator with ``QUiLoader`` and ``pyside6-uic`` +************************************************************************************* + +This page describes the use of +`Qt Widgets Designer <https://doc.qt.io/qt-6/qtdesigner-manual.html>`_ 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 <https://doc.qt.io/qtcreator>`_. Its use within *Qt Creator* +is described at +`Using Qt Widgets Designer <https://doc.qt.io/qtcreator/creator-using-qt-designer.html>`_. + +.. image:: uifiles.png + :alt: 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 <https://doc.qt.io/qt-6/uic.html>`_ 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: + +.. code-block:: xml + + <?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: + +.. code-block:: python + + 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: + +.. code-block:: python + + 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: + +.. code-block:: python + + from PySide6.QtUiTools import QUiLoader + +The ``QUiLoader`` lets us load the **ui file** dynamically +and use it right away: + +.. code-block:: python + + 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: + +.. code-block:: python + + # 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: + +.. code-block:: bash + + 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. + +.. _designer_custom_widgets: + +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 <https://doc.qt.io/qt-6/designer-using-custom-widgets.html>`_ +). 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 +:meth:`registerCustomWidget()<PySide6.QtUiTools.QUiLoader.registerCustomWidget>`. + +The widget needs to be provided as a Python module, as shown by +the :ref:`widgetbinding-example` (file ``wigglywidget.py``) or +the :ref:`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: + +.. code-block:: python + + # 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 +:meth:`registerCustomWidget()<PySide6.QtDesigner.QPyDesignerCustomWidgetCollection.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 +:meth:`addCustomWidget()<PySide6.QtDesigner.QPyDesignerCustomWidgetCollection.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 <https://doc.qt.io/qt-6/qtdesigner-taskmenuextension-example.html>`_ . + +.. _QDesignerCustomWidgetCollectionInterface: https://doc.qt.io/qt-6/qdesignercustomwidgetcollectioninterface.html +.. _QDesignerCustomWidgetInterface: https://doc.qt.io/qt-6/qdesignercustomwidgetinterface.html + +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. |