连接来自 UI 文件的信号

Posted

技术标签:

【中文标题】连接来自 UI 文件的信号【英文标题】:Connecting signals from an UI file 【发布时间】:2018-10-09 07:03:38 【问题描述】:

按照一些教程,我设法将这个小型 Python 程序成功拉入.ui 文件并在屏幕上显示:

from PySide2.QtWidgets import QApplication
from PySide2.QtWidgets import QDialog, QMessageBox, QVBoxLayout
from PySide2.QtWidgets import QPushButton, QLineEdit
from PySide2.QtCore import QFile, Slot
from PySide2.QtUiTools import QUiLoader

class Dialog(QDialog):
    def __init__(self, parent = None):
        super(Dialog, self).__init__(parent)
        print('hey')

    def alert(self):
        print('Alert')

if __name__ == '__main__':
    app = QApplication([])

    loader = QUiLoader()
    loader.registerCustomWidget(Dialog)

    ui_file = QFile('alert-quit.ui')
    ui_file.open(QFile.ReadOnly)
    dialog = loader.load(ui_file)
    ui_file.close()

    dialog.show()

对话框正确显示,但我收到错误 QObject::connect: No such slot QDialog::alert() 并且按钮什么也不做。 (hey 文本也未显示。)

.ui 文件包含 QDialog 的定义,其中包含来自“警报”按钮的信号:

我不确定registerCustomWidget() 是什么,从另一个回复来看,这似乎是要做的事情。可悲的是,official documentation 失败了 circle.ui 包含的内容。

并且official documentation on loading .ui files 没有显示如何与.ui 文件本身中定义的项目进行交互。

如何从.ui 文件加载完整的QDialog 并获取其中的按钮以触发我的代码中的操作?

P.S.:由于我无法附加 .ui 文件,这里是它的 XML:

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Dialog</class>
 <widget class="QDialog" name="Dialog">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>344</width>
    <height>300</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Dialog</string>
  </property>
  <layout class="QVBoxLayout" name="verticalLayout">
   <item>
    <widget class="QLineEdit" name="lineEdit"/>
   </item>
   <item>
    <widget class="QPushButton" name="alert_button">
     <property name="maximumSize">
      <size>
       <width>100</width>
       <height>16777215</height>
      </size>
     </property>
     <property name="text">
      <string>Alert</string>
     </property>
    </widget>
   </item>
   <item>
    <widget class="QPushButton" name="quit_button">
     <property name="maximumSize">
      <size>
       <width>100</width>
       <height>16777215</height>
      </size>
     </property>
     <property name="text">
      <string>Quit</string>
     </property>
    </widget>
   </item>
   <item>
    <spacer name="verticalSpacer">
     <property name="orientation">
      <enum>Qt::Vertical</enum>
     </property>
     <property name="sizeHint" stdset="0">
      <size>
       <width>20</width>
       <height>40</height>
      </size>
     </property>
    </spacer>
   </item>
  </layout>
 </widget>
 <resources/>
 <connections>
  <connection>
   <sender>alert_button</sender>
   <signal>clicked()</signal>
   <receiver>Dialog</receiver>
   <slot>alert()</slot>
   <hints>
    <hint type="sourcelabel">
     <x>91</x>
     <y>30</y>
    </hint>
    <hint type="destinationlabel">
     <x>133</x>
     <y>51</y>
    </hint>
   </hints>
  </connection>
 </connections>
 <slots>
  <slot>alert()</slot>
  <slot>quit()</slot>
 </slots>
</ui>

【问题讨论】:

好的,我并没有停止尝试,this post,对我昨天阅读的一些帖子的回忆,帮助我走上了正轨。很快我就能回答我自己的问题了:-) 不,还没有。现在我可以将信号链接到 my python 类中的函数,但我仍然无法从 slot 函数引用对话框中的文本字段...任何想法? 您在评论中作为参考指出的问题是另一个方向。另一方面,在 PySide2 中没有执行此类任务的 loadUI 方法,其想法是实现此方法,该方法涉及解析 .ui 并获取所有元素并建立连接 感谢@eyllanesc 我通过稍微手动调整.ui 让它工作。我现在正在写我自己问题的答案,我会很快发布。直到 .ui 的调整,它看起来像一个干净的分析器......但我欢迎进一步的 cmets 和改进(最重要的是,如果我能避免 .ui 文件中的黑客攻击!) 你将不得不解析文件,这就是 PyQt5 所做的,我建议你查看它以便你理解逻辑。 【参考方案1】:

正如我在 cmets 中针对我的问题所写的那样,我找到了一个可行的解决方案。我不能 100% 确定,它是正确的(至少对于与手动编辑 .ui 文件相关的部分),但我会显示对话框并按照我的预期行事。

首先我必须修改.ui 文件中的XML,并在.py.ui 文件中使用两个不同的类名。我已经将.ui 中的那个修改为AlertDialog 并且类型为Dialog 而不是QDialog。我找不到在 Qt Designer 中修改类型的方法。

3,4c3,4
<  <class>Dialog</class>
<  <widget class="QDialog" name="Dialog">
---
>  <class>AlertDialog</class>
>  <widget class="Dialog" name="AlertDialog">
18c18
<     <widget class="QLineEdit" name="lineEdit"/>
---
>     <widget class="QLineEdit" name="text_field"/>
66c66
<    <receiver>Dialog</receiver>
---
>    <receiver>AlertDialog</receiver>

正如您在上面看到的,输入框的名称也有一个小错字:这就是为什么,我在问题中粘贴了.ui 文件!)。

在 Python 代码中,我只是必须正确地将我的函数装饰为 Slot。完整代码如下:

from PySide2.QtWidgets import QApplication
from PySide2.QtWidgets import QDialog, QMessageBox
from PySide2.QtCore import QFile, Slot
from PySide2.QtUiTools import QUiLoader

class Dialog(QDialog):
    def __init__(self, parent = None):
        super(Dialog, self).__init__(parent)

    @Slot()
    def alert(self):
        alert = QMessageBox()
        alert.setText(self.text_field.text())
        alert.exec_()


if __name__ == '__main__':
    app = QApplication([])

    loader = QUiLoader()
    loader.registerCustomWidget(Dialog)

    ui_file = QFile('alert-quit.ui')
    ui_file.open(QFile.ReadOnly)
    dialog = loader.load(ui_file)
    ui_file.close()

    dialog.show()
    app.exec_()

整洁,不是吗?如果有人(包括我未来的自己......)知道如何摆脱.ui 调整或对此解决方案有其他改进,非常欢迎您的 cmets!

【讨论】:

我继续我的独白。虽然此解决方案中的代码非常简洁紧凑,但创建可执行文件(以 PyInstaller 为例)变得更加棘手。目前,如果你想分发你的代码,你可能应该坚持在 Python 代码中使用 .ui 代码与 pyside-uic 的良好旧转换。【参考方案2】:

由于我在 Pyside2 上也遇到了很多困难,所以我在这里分享我是如何做到这一点的,我不确定这是最好的方法,但它对我有用。

首先你必须安装pyqt5

pip3 install pyqt5

并假设您的文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Dialog</class>
 <widget class="QDialog" name="Dialog">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>344</width>
    <height>300</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Dialog</string>
  </property>
  <layout class="QVBoxLayout" name="verticalLayout">
   <item>
    <widget class="QLineEdit" name="lineEdit"/>
   </item>
   <item>
    <widget class="QPushButton" name="alert_button">
     <property name="maximumSize">
      <size>
       <width>100</width>
       <height>16777215</height>
      </size>
     </property>
     <property name="text">
      <string>Alert</string>
     </property>
    </widget>
   </item>
   <item>
    <widget class="QPushButton" name="quit_button">
     <property name="maximumSize">
      <size>
       <width>100</width>
       <height>16777215</height>
      </size>
     </property>
     <property name="text">
      <string>Quit</string>
     </property>
    </widget>
   </item>
   <item>
    <spacer name="verticalSpacer">
     <property name="orientation">
      <enum>Qt::Vertical</enum>
     </property>
     <property name="sizeHint" stdset="0">
      <size>
       <width>20</width>
       <height>40</height>
      </size>
     </property>
    </spacer>
   </item>
  </layout>
 </widget>
 <resources/>
 <connections>
  <connection>
   <sender>alert_button</sender>
   <signal>clicked()</signal>
   <receiver>Dialog</receiver>
   <slot>alert()</slot>
   <hints>
    <hint type="sourcelabel">
     <x>91</x>
     <y>30</y>
    </hint>
    <hint type="destinationlabel">
     <x>133</x>
     <y>51</y>
    </hint>
   </hints>
  </connection>
 </connections>
 <slots>
  <slot>alert()</slot>
  <slot>quit()</slot>
 </slots>
</ui>

你只需输入命令:

pyuic5 path_to_your_ui_file -o path_to_the_output_python_file # don't forget the extension .py for the output

正常有输出:

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'test.ui'
#
# Created by: PyQt5 UI code generator 5.11.3
#
# WARNING! All changes made in this file will be lost!

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_Dialog(object):
    def setupUi(self, Dialog):
        Dialog.setObjectName("Dialog")
        Dialog.resize(344, 300)
        self.verticalLayout = QtWidgets.QVBoxLayout(Dialog)
        self.verticalLayout.setObjectName("verticalLayout")
        self.lineEdit = QtWidgets.QLineEdit(Dialog)
        self.lineEdit.setObjectName("lineEdit")
        self.verticalLayout.addWidget(self.lineEdit)
        self.alert_button = QtWidgets.QPushButton(Dialog)
        self.alert_button.setMaximumSize(QtCore.QSize(100, 16777215))
        self.alert_button.setObjectName("alert_button")
        self.verticalLayout.addWidget(self.alert_button)
        self.quit_button = QtWidgets.QPushButton(Dialog)
        self.quit_button.setMaximumSize(QtCore.QSize(100, 16777215))
        self.quit_button.setObjectName("quit_button")
        self.verticalLayout.addWidget(self.quit_button)
        spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
        self.verticalLayout.addItem(spacerItem)

        self.retranslateUi(Dialog)
        self.alert_button.clicked.connect(Dialog.alert)
        QtCore.QMetaObject.connectSlotsByName(Dialog)

    def retranslateUi(self, Dialog):
        _translate = QtCore.QCoreApplication.translate
        Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
        self.alert_button.setText(_translate("Dialog", "Alert"))
        self.quit_button.setText(_translate("Dialog", "Quit"))

您所要做的就是更正导入并加载生成的文件:

from PySide2.QtUiTools import QUiLoader
from PySide2.QtWidgets import *
from PySide2.QtGui import *
from PySide2.QtCore import QFile, QObject 
import mainwindow  # if your generated file name is mainWindow.py

class MainDialog(QDialog, mainwindow.Ui_Dialog):

    def __init__(self, parent=None):
        super(MainDialog, self).__init__(parent)
        self.setupUi(self)

app = QApplication(sys.argv)
form = MainDialog()
form.show()
app.exec_()

【讨论】:

好吧,这里您使用的是 pyqt5 而不是 pyside2...并且您正在使用 uic 工具在 Python 代码中转换 .ui 文件。这也适用于 pyisde2。正如我后来发现的,这也适用于 pyside2。但我的目标是首先转换文件.... :-) "摆脱 .ui 调整"这部分答案让我很困惑,但我明白了你的意思,谢谢我不知道 pyside2 的 uic 工具的存在 好吧,我们的想法是摆脱 .ui 调整保持 .ui 文件的动态加载:-)跨度>

以上是关于连接来自 UI 文件的信号的主要内容,如果未能解决你的问题,请参考以下文章

如何在 .ui 文件中手动将信号连接到插槽?

Qt5 UI信号槽自动连接的控件重名

当连接类型 = Qt.DirectConnection 时,来自线程对象的 PyQt5 信号会导致段错误

Qt4中的信号和插槽 - 不工作

无法连接来自 QApplication 的 aboutToQuit 信号

Qt 4.8:来自不同线程的两个信号和一个插槽之间的连接行为