在Qt Designer中使用自定义的PySide2小部件

5

我正在寻找一种有效地使用Qt for Python(PySide2)中自定义小部件的方法。

我发现,可以使用基本小部件设计GUI,然后在UI文件中仅交换类以使用自定义小部件,并通知QUiLoader子类loader.registerCustomWidget(MyMainWindow),但是在Qt Designer中再次打开它并不那么有效。

我在这个类似问题的PyQt方面的回答中读到,您必须为自定义小部件编写插件。 PySide2中是否也存在此可能性?

以下是一些示例代码:

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

使用这个版本会在不提示的情况下关闭应用程序。

<?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

这个版本会在您尝试退出时询问您是否真的想要退出。

<?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>
<customwidgets> 段是在重新打开Qt Designer后添加的。 修改后,Qt Designer无法正确放置这三个标签。
1个回答

6

在Qt设计师中,使用自定义小部件有两种主要方法:

1. 提升小部件:

如果是内部小部件,则这是最简单、最省力的方法,只需在小部件上右键单击,选择 提升到...,然后选择:

  • 基类名:选择从中继承的类
  • 提升类名称:将类的名称放在此处
  • 头文件:将文件路径放在此处,更改扩展名 .py 为 .h

enter image description here

上述操作会生成以下内容:

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

即,在 customwidget 中更改类,添加一个字段指向类的名称:<class> NAME_OF_CLASS </class>, 继承类的名称 <extends> CLASS_EXTENDS </extends>,并将文件路径从.py更改为.h <header> PATH_OF_FILE.h </header>,也就是与您在表单中输入的相同数据。

对于根widget,无法通过Qt Designer进行操作,但我看到您已经理解了逻辑,但您没有正确修改所有内容,您的主要错误是指出继承的类。

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

所以正确的文件是:
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. 推广小部件:


谢谢!就在您发布这篇文章的同时,我偶然发现了一个奇怪的机制,即将 C++ 头文件的 .h 扩展名转换为 .py!而且至少从 2013 年以来一直是这样!疯狂! - Michael K.
刚刚测试了一下,如果你在 registerCustomWidget 中使用完全相同的类名,并且在 XML 的 <widget> 定义中也使用完全相同的类名,那么 <customwidgets/> 部分可以省略。 - karolyi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接