如何使用 python 函数连接信号槽 (qt-designer)

How to connect a signal slot (qt-designer) with a python function

我创建了一个应用程序,其主 window 包括一个 MDI 区域,以及一个子 window MDI 区域。 windows 都是通过 QT Designer 创建并保存为 ui 文件。我的 python 脚本加载主 window 并提供打开子 window 的功能。到目前为止有效!

现在我有例如子 window 中的一个按钮,它应该触发一个影响主 window 中元素的函数(例如,在 MDI 区域旁边的“PlainTextEdit”元素中显示文本)。 在Qt-Designer中我可以定义信号和自定义插槽。

pushButton -> clicked() -> MainWindow -> printText()

我的问题是: 我必须在我的 python 代码中写入什么才能捕获“printText()”插槽上的信号,以执行函数在下面 ?

我正在使用 Python 3.7 和 Pyside2。

如果我 运行 脚本,终端中会显示以下信息:

QObject::connect: No such slot QMainWindow::printText()

QObject::connect: (sender name: 'pushButton')

QObject::connect: (receiver name: 'MainWindow')

  1. 默认方式通过... self.pushButton.clicked.connect(self.function) ... 不起作用,因为按钮在另一个 class 中定义为主 window。 (子windowclass) 而且我也无法在子 window class 中添加此代码,因为使用调用函数 (self.function) 我无法访问主 window.[=17 中的元素=]

  2. 主要windowclass中用于捕获信号的插槽声明(目前我已找到)也不起作用:

@Slot()
def printText(self): # name of the slot

    # function which should be executed if the button is clicked
    self.ui.textOutput.setPlainText("This is a test !")

[编辑] 如果添加了所有三个文件的代码。 该示例包含 2 个 subwindows。第一个包含在主 ui 文件中(始终通过执行激活)。第二个子window是独立的,可以通过主菜单按钮显示。

py文件:

import sys

from PySide2.QtUiTools import QUiLoader
from PySide2.QtWidgets import QApplication, QMainWindow, QWidget, QMdiSubWindow, QMdiArea
from PySide2.QtCore import QFile, Slot, Signal

# Variable which contains the subwindow ID
# Required to determine if a subwindow is already open
state_limitedSubWindow = None

# Main class for loading the UI
class MyUI(QMainWindow):
    def __init__(self, ui_file, parent = None):
        super(MyUI, self).__init__(parent)

        # (1) Open UI file
        ui_file = QFile(ui_file)
        ui_file.open(QFile.ReadOnly)

        # (2) Loading UI file ...
        uiLoader = QUiLoader()
        # ... and creating an instance of the content
        self.ui = uiLoader.load(ui_file)

        # (3) Close file
        ui_file.close()

        # (4) Optional: Customize loaded UI
        # E.g. Set a window title
        self.ui.setWindowTitle("Test")

        # (5) Show the loaded and optionally customized UI
        self.ui.show()

        # A limited subwindow (only on instance can be active)
        self.ui.actionOpenSubWindow.triggered.connect(self.func_limitedSubWindow)

        @Slot()
        def printText():
            print("Debug: Inside the __init__.")

    @Slot()
    def printText(self):
        print("Debug: Inside the MainWindow class")
        self.printing()

    # Limited subwindow via action
    @Slot()
    def func_limitedSubWindow(self):

        # loading global var which contains the subwindow ID
        global state_limitedSubWindow

        if state_limitedSubWindow == None:
            limitedSubWindow = LimitedSubWindow("test_sub.ui")
            self.ui.mdiArea.addSubWindow(limitedSubWindow)
            limitedSubWindow.show()
            # Save ID of the new created subwindow in the global variable
            state_limitedSubWindow = limitedSubWindow.winId()
            # Console output subwindow ID

            print(state_limitedSubWindow)
        else:
            print("Window already exists !")

    @Slot()
    def printing(self):
        self.ui.textOutput.setPlainText("Test")

@Slot()
def printText():
    print("Debug: Outside of the class file")


# Class for the limited second window (only 1 instance can be active)
# This class can of course be in a separate py file
# The base widget of the UI file must be QWidget !!!
class LimitedSubWindow(QWidget):
    def __init__(self, ui_limitedSubWindow_file, parent = None):
        super(LimitedSubWindow, self).__init__(parent)

        # (1) Open UI file
        ui_limitedSubWindow_file = QFile(ui_limitedSubWindow_file)
        ui_limitedSubWindow_file.open(QFile.ReadOnly)

        # (2) Loading UI file ...
        ui_limitedSubWindow_Loader = QUiLoader()
        # ... and creating an instance of the content
        self.ui_limitedSubWindow = ui_limitedSubWindow_Loader.load(ui_limitedSubWindow_file, self)

        # (3) Close file
        ui_limitedSubWindow_file.close()

        self.setMinimumSize(400, 200)

        self.setWindowTitle("Limited subwindow")

        self.ui_limitedSubWindow.pushButton.clicked.connect(self.test)

    # Close event resets the variable which contains the ID
    def closeEvent(self, event):
        global state_limitedSubWindow
        # Reset the global variable
        state_limitedSubWindow = None
        event.accept()


if __name__ == "__main__":

    app = QApplication(sys.argv)

    # Creating an instance of the loading class
    frame = MyUI("test.ui")

    sys.exit(app.exec_())

主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">
   <widget class="QWidget" name="horizontalLayoutWidget">
    <property name="geometry">
     <rect>
      <x>0</x>
      <y>0</y>
      <width>791</width>
      <height>551</height>
     </rect>
    </property>
    <layout class="QHBoxLayout" name="horizontalLayout">
     <item>
      <widget class="QPlainTextEdit" name="textInput"/>
     </item>
     <item>
      <widget class="QMdiArea" name="mdiArea">
       <property name="enabled">
        <bool>true</bool>
       </property>
       <property name="maximumSize">
        <size>
         <width>517</width>
         <height>16777215</height>
        </size>
       </property>
       <widget class="QWidget" name="subwindow">
        <property name="minimumSize">
         <size>
          <width>400</width>
          <height>400</height>
         </size>
        </property>
        <property name="windowTitle">
         <string>Subwindow</string>
        </property>
        <widget class="QPushButton" name="pushButton">
         <property name="geometry">
          <rect>
           <x>160</x>
           <y>200</y>
           <width>90</width>
           <height>28</height>
          </rect>
         </property>
         <property name="text">
          <string>PushButton</string>
         </property>
        </widget>
       </widget>
      </widget>
     </item>
     <item>
      <widget class="QPlainTextEdit" name="textOutput"/>
     </item>
    </layout>
   </widget>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>800</width>
     <height>25</height>
    </rect>
   </property>
   <widget class="QMenu" name="menuWorkbench">
    <property name="title">
     <string>Workbench</string>
    </property>
    <addaction name="actionOpenSubWindow"/>
   </widget>
   <addaction name="menuWorkbench"/>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
  <action name="actionOpenSubWindow">
   <property name="text">
    <string>Caesar Cipher</string>
   </property>
  </action>
  <action name="actionTestText">
   <property name="text">
    <string>Test text</string>
   </property>
  </action>
 </widget>
 <resources/>
 <connections>
  <connection>
   <sender>pushButton</sender>
   <signal>clicked()</signal>
   <receiver>MainWindow</receiver>
   <slot>printText()</slot>
   <hints>
    <hint type="sourcelabel">
     <x>386</x>
     <y>263</y>
    </hint>
    <hint type="destinationlabel">
     <x>399</x>
     <y>299</y>
    </hint>
   </hints>
  </connection>
 </connections>
 <slots>
  <slot>printText()</slot>
 </slots>
</ui>

子ui-文件:

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Form</class>
 <widget class="QWidget" name="Form">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>315</width>
    <height>242</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Form</string>
  </property>
  <widget class="QPushButton" name="pushButton">
   <property name="geometry">
    <rect>
     <x>90</x>
     <y>80</y>
     <width>90</width>
     <height>28</height>
    </rect>
   </property>
   <property name="text">
    <string>PushButton</string>
   </property>
  </widget>
 </widget>
 <resources/>
 <connections/>
</ui>

printText 插槽属于 MyUI class,但 requires 的插槽 .ui 必须属于 self.ui 但不幸的是在 PySide2 中使用QUiLoader 无法从 .ui 创建 class。

因此解决方案是使用 pyside2-uic 将 .ui 转换为 .py,因为它会生成 class.

而文件夹中有以下文件:

├── main.py
├── test_sub.ui
└── test.ui

然后你必须打开位于项目文件夹中的终端或CMD并执行:

pyside2-uic test_sub.ui -o test_sub_ui.py -x
pyside2-uic test.ui -o test_ui.py -x

所以你必须得到如下结构:

.
├── main.py
├── test_sub.ui
├── test_sub_ui.py
├── test.ui
└── test_ui.py

之后您必须修改 main.py(有关更多信息,请阅读此 ):

main.py

import sys
from PySide2 import QtCore, QtWidgets

from test_ui import Ui_MainWindow
from test_sub_ui import Ui_Form

# Variable which contains the subwindow ID
# Required to determine if a subwindow is already open
state_limitedSubWindow = None


class MyUI(QtWidgets.QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
        super(MyUI, self).__init__(parent)
        self.setupUi(self)
        self.setWindowTitle("Test")
        self.mdiArea.addSubWindow(self.subwindow)
        self.actionOpenSubWindow.triggered.connect(self.func_limitedSubWindow)

    @QtCore.Slot()
    def printText(self):
        print("Debug: Inside the MainWindow class")
        self.printing()

    @QtCore.Slot()
    def func_limitedSubWindow(self):
        # loading global var which contains the subwindow ID
        global state_limitedSubWindow
        if state_limitedSubWindow == None:
            limitedSubWindow = LimitedSubWindow()
            self.mdiArea.addSubWindow(limitedSubWindow)
            limitedSubWindow.show()
            # Save ID of the new created subwindow in the global variable
            state_limitedSubWindow = limitedSubWindow.winId()
            # Console output subwindow ID
            print(state_limitedSubWindow)
        else:
            print("Window already exists !")
        pass

    @QtCore.Slot()
    def printing(self):
        self.textOutput.setPlainText("Test")


# Class for the limited second window (only 1 instance can be active)
# This class can of course be in a separate py file
# The base widget of the UI file must be QWidget !!!
class LimitedSubWindow(QtWidgets.QWidget, Ui_Form):
    def __init__(self, parent = None):
        super(LimitedSubWindow, self).__init__(parent)
        self.setupUi(self)
        self.setMinimumSize(400, 200)
        self.setWindowTitle("Limited subwindow")

    # Close event resets the variable which contains the ID
    def closeEvent(self, event):
        global state_limitedSubWindow
        # Reset the global variable
        state_limitedSubWindow = None
        event.accept()

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    # Creating an instance of the loading class
    frame = MyUI()
    frame.show()
    sys.exit(app.exec_())

所以总结QUiLoader有很多局限性所以最好使用uic.


如果你不想使用 uic 那么在 previous answer 我已经说明了如何将 uic 模块从 PyQt5 转换为 PySide2


可以找到完整的解决方案here