6

I'm searching for a way to use custom widgets in Qt Designer written with Qt for Python (PySide2) effectively.

I found out, that it is possible to design the GUI with the base widget, then just swap out the class to my custom widget in the UI File and informing the QUiLoader about the subclass loader.registerCustomWidget(MyMainWindow), but then opening it again in Qt Designer doesn't work that well.

I read in this similar question for PyQt that you have to write a Plugin for the custom widget. Does this possibility also exist for PySide2?

Some example code:

custom_widget.py:

import sys
from PySide2.QtUiTools import QUiLoader
from PySide2.QtWidgets import QApplication, QMainWindow, QAction, QMessageBox, QFileDialog, QTextBrowser
from PySide2.QtCore import QFile
class MyMainWindow(QMainWindow):
 def __init__(self, *args, **kwargs):
 super().__init__(*args, **kwargs)
 self.setWindowTitle("Demo QtWidget App")
 def closeEvent(self, event):
 msgBox = QMessageBox()
 msgBox.setWindowTitle("Quit?")
 msgBox.setText("Exit application?")
 msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
 msgBox.setDefaultButton(QMessageBox.No)
 ret = msgBox.exec_()
 if ret == QMessageBox.Yes:
 event.accept()
 else:
 event.ignore()
if __name__ == '__main__':
 app = QApplication([])
 file = QFile("custom_widget_original.ui")
 #file = QFile("custom_widget_modified.ui")
 file.open(QFile.ReadOnly)
 loader = QUiLoader()
 loader.registerCustomWidget(MyMainWindow)
 main_window = loader.load(file)
 main_window.show()
 sys.exit(app.exec_())

custom_widget_original.ui

With this version the application is closed without question.

<?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>800</width>
 <height>600</height>
 </rect>
 </property>
 <property name="windowTitle">
 <string>MainWindow</string>
 </property>
 <widget class="QWidget" name="centralwidget">
 <layout class="QGridLayout" name="gridLayout">
 <item row="0" column="0">
 <widget class="QLabel" name="label_2">
 <property name="text">
 <string>TextLabel</string>
 </property>
 </widget>
 </item>
 <item row="0" column="2">
 <widget class="QLabel" name="label">
 <property name="text">
 <string>TextLabel</string>
 </property>
 </widget>
 </item>
 <item row="0" column="1">
 <widget class="QLabel" name="label_3">
 <property name="text">
 <string>TextLabel</string>
 </property>
 </widget>
 </item>
 </layout>
 </widget>
 <widget class="QMenuBar" name="menubar">
 <property name="geometry">
 <rect>
 <x>0</x>
 <y>0</y>
 <width>800</width>
 <height>21</height>
 </rect>
 </property>
 <widget class="QMenu" name="menuFile">
 <property name="title">
 <string>File</string>
 </property>
 <addaction name="actionExit"/>
 </widget>
 <addaction name="menuFile"/>
 </widget>
 <widget class="QStatusBar" name="statusbar"/>
 <action name="actionExit">
 <property name="text">
 <string>Exit</string>
 </property>
 </action>
 </widget>
 <resources/>
 <connections>
 <connection>
 <sender>actionExit</sender>
 <signal>triggered()</signal>
 <receiver>MainWindow</receiver>
 <slot>close()</slot>
 <hints>
 <hint type="sourcelabel">
 <x>-1</x>
 <y>-1</y>
 </hint>
 <hint type="destinationlabel">
 <x>399</x>
 <y>299</y>
 </hint>
 </hints>
 </connection>
 </connections>
</ui>

custom_widget_modified.ui

With this version you are asked if you really want to quit.

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="MyMainWindow" name="MainWindow">
 <property name="geometry">
 <rect>
 <x>0</x>
 <y>0</y>
 <width>800</width>
 <height>600</height>
 </rect>
 </property>
 <property name="windowTitle">
 <string>MainWindow</string>
 </property>
 <widget class="QWidget" name="centralwidget">
 <property name="geometry">
 <rect>
 <x>0</x>
 <y>0</y>
 <width>155</width>
 <height>15</height>
 </rect>
 </property>
 <layout class="QGridLayout" name="gridLayout">
 <item row="0" column="0">
 <widget class="QLabel" name="label_2">
 <property name="text">
 <string>TextLabel</string>
 </property>
 </widget>
 </item>
 <item row="0" column="2">
 <widget class="QLabel" name="label">
 <property name="text">
 <string>TextLabel</string>
 </property>
 </widget>
 </item>
 <item row="0" column="1">
 <widget class="QLabel" name="label_3">
 <property name="text">
 <string>TextLabel</string>
 </property>
 </widget>
 </item>
 </layout>
 </widget>
 <widget class="QMenuBar" name="menubar">
 <property name="geometry">
 <rect>
 <x>0</x>
 <y>0</y>
 <width>800</width>
 <height>21</height>
 </rect>
 </property>
 <widget class="QMenu" name="menuFile">
 <property name="title">
 <string>File</string>
 </property>
 <addaction name="actionExit"/>
 </widget>
 <addaction name="menuFile"/>
 </widget>
 <widget class="QStatusBar" name="statusbar">
 <property name="geometry">
 <rect>
 <x>0</x>
 <y>0</y>
 <width>3</width>
 <height>18</height>
 </rect>
 </property>
 </widget>
 <action name="actionExit">
 <property name="text">
 <string>Exit</string>
 </property>
 </action>
 </widget>
 <customwidgets>
 <customwidget>
 <class>MyMainWindow</class>
 <extends>QWidget</extends>
 <header>mymainwindow.h</header>
 <container>1</container>
 </customwidget>
 </customwidgets>
 <resources/>
 <connections>
 <connection>
 <sender>actionExit</sender>
 <signal>triggered()</signal>
 <receiver>MainWindow</receiver>
 <slot>close()</slot>
 <hints>
 <hint type="sourcelabel">
 <x>-1</x>
 <y>-1</y>
 </hint>
 <hint type="destinationlabel">
 <x>399</x>
 <y>299</y>
 </hint>
 </hints>
 </connection>
 </connections>
</ui>

The <customwidgets> segment is added by Qt Designer after opening it again.

After modification Qt Designer doesn't place the three labels correctly.

eyllanesc
245k19 gold badges204 silver badges282 bronze badges
asked Sep 1, 2018 at 12:40

1 Answer 1

6

There are 2 main ways to use a custom widget in Qt Designer:

1. Promoting the widget:

This is the simplest and least laborious way, if it's an internal widget you just have to right click on the widget and select Promote To ..., then in:

  • Base Class Name choose the class from which you inherit
  • Promoted Class Name place the name of the class a
  • Header File place the path of the file changing the extension .py to .h

enter image description here

What the above does is generate the following:

...
 <widget class="RadialBar" name="widget" native="true"/>
...
 <customwidgets>
 <customwidget>
 <class>RadialBar</class>
 <extends>QWidget</extends>
 <header>radialbar.h</header>
 <container>1</container>
 </customwidget>
 </customwidgets>
...

That is, change class, and add a field in customwidget pointing to the name of the class: <class> NAME_OF_CLASS </class>, name of the class they inherit <extends>CLASS_EXTENDS</extends> and file path changing the file extension from .py to .h <header>PATH_OF_FILE.h</header>, that is, the same data as You entered the form.

In the case of the root widget it can not be done through Qt Designer, but I see that you have understood the logic but you have not modified everything correctly, your main mistake is to point out the class from which they inherit

 <customwidgets>
 <customwidget>
 <class>MyMainWindow</class>
 <extends>QMainWindow</extends> <----
 <header>mymainwindow.h</header>
 <container>1</container>
 </customwidget>
 </customwidgets>

So the correct file is:

custom_widget_modified.ui

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="MyMainWindow" name="MainWindow">
 <property name="geometry">
 <rect>
 <x>0</x>
 <y>0</y>
 <width>800</width>
 <height>600</height>
 </rect>
 </property>
 <property name="windowTitle">
 <string>MainWindow</string>
 </property>
 <widget class="QWidget" name="centralwidget">
 <property name="geometry">
 <rect>
 <x>0</x>
 <y>0</y>
 <width>155</width>
 <height>15</height>
 </rect>
 </property>
 <layout class="QGridLayout" name="gridLayout">
 <item row="0" column="0">
 <widget class="QLabel" name="label_2">
 <property name="text">
 <string>TextLabel</string>
 </property>
 </widget>
 </item>
 <item row="0" column="2">
 <widget class="QLabel" name="label">
 <property name="text">
 <string>TextLabel</string>
 </property>
 </widget>
 </item>
 <item row="0" column="1">
 <widget class="QLabel" name="label_3">
 <property name="text">
 <string>TextLabel</string>
 </property>
 </widget>
 </item>
 </layout>
 </widget>
 <widget class="QMenuBar" name="menubar">
 <property name="geometry">
 <rect>
 <x>0</x>
 <y>0</y>
 <width>800</width>
 <height>21</height>
 </rect>
 </property>
 <widget class="QMenu" name="menuFile">
 <property name="title">
 <string>File</string>
 </property>
 <addaction name="actionExit"/>
 </widget>
 <addaction name="menuFile"/>
 </widget>
 <widget class="QStatusBar" name="statusbar">
 <property name="geometry">
 <rect>
 <x>0</x>
 <y>0</y>
 <width>3</width>
 <height>18</height>
 </rect>
 </property>
 </widget>
 <action name="actionExit">
 <property name="text">
 <string>Exit</string>
 </property>
 </action>
 </widget>
 <customwidgets>
 <customwidget>
 <class>MyMainWindow</class>
 <extends>QMainWindow</extends>
 <header>mymainwindow.h</header>
 <container>1</container>
 </customwidget>
 </customwidgets>
 <resources/>
 <connections>
 <connection>
 <sender>actionExit</sender>
 <signal>triggered()</signal>
 <receiver>MainWindow</receiver>
 <slot>close()</slot>
 <hints>
 <hint type="sourcelabel">
 <x>-1</x>
 <y>-1</y>
 </hint>
 <hint type="destinationlabel">
 <x>399</x>
 <y>299</y>
 </hint>
 </hints>
 </connection>
 </connections>
</ui>

mymainwindow.py

import sys
from PySide2.QtUiTools import QUiLoader
from PySide2.QtWidgets import QApplication, QMainWindow, QMessageBox
from PySide2.QtCore import QFile
class MyMainWindow(QMainWindow):
 def __init__(self, *args, **kwargs):
 super().__init__(*args, **kwargs)
 self.setWindowTitle("Demo QtWidget App")
 def closeEvent(self, event):
 msgBox = QMessageBox()
 msgBox.setWindowTitle("Quit?")
 msgBox.setText("Exit application?")
 msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
 msgBox.setDefaultButton(QMessageBox.No)
 ret = msgBox.exec_()
 if ret == QMessageBox.Yes:
 event.accept()
 else:
 event.ignore()
if __name__ == '__main__':
 app = QApplication([])
 file = QFile("custom_widget_modified.ui")
 file.open(QFile.ReadOnly)
 loader = QUiLoader()
 loader.registerCustomWidget(MyMainWindow)
 main_window = loader.load(file)
 main_window.show()
 sys.exit(app.exec_())

2. Promoting the widget:

answered Sep 1, 2018 at 19:16
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks! Right in this moment while you posted this i stumbled about this weird mechanism that the c++ header .h extension is converted to .py! And it's like that at least since 2013!!! Insane!
Just tested, if you use registerCustomWidget with the exact class name, and you use the exact same class name in the XML <widget> definition, then the <customwidgets/> section can be omitted.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.