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

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

我创建了一个应用程序,该应用程序的主窗口包括MDI区域和MDI区域的子窗口。这两个窗口都是通过QT Designer创建的,并保存为ui文件。我的python脚本加载了主窗口,并提供了打开子窗口的功能。到目前为止,一切正常!

现在我有子窗口中的按钮,它应该触发影响主窗口中元素的功能(例如,在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')

  • 通过...的默认方式
    self.pushButton.clicked.connect(self.function)
    ...不起作用,因为pushButton在另一个类中定义为主窗口。 (子窗口类)
    而且我也不能在子窗口类中添加此代码,因为使用被调用的函数(self.function),我无法访问主窗口中的元素。

  • 主窗口类中用于捕获信号的插槽的声明(到目前为止我发现)也不起作用:

  • 1
    2
    3
    4
    5
    @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个子窗口。主ui文件中包含的第一个ist(始终通过执行激活)。第二个子窗口是独立的,可以通过主菜单按钮显示。

    py文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    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文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    <?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>
       
       </widget>
       
      </widget>
      <widget class="QStatusBar" name="statusbar"/>
     
       <property name="text">
        <string>Caesar Cipher</string>
       </property>
      </action>
     
       <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文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    <?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类,但是需要.ui的插槽必须属于self.ui,但不幸的是,在PySide2中,使用QUiLoader不能从.ui创建类。

    因此解决方案是使用pyside2-uic将.ui转换为.py,因为它将生成一个类。

    该文件夹包含以下文件:

    1
    2
    3
    a"?a"€a"€ main.py
    a"
    ?a"€a"€ test_sub.ui
    a""a"€a"test.ui

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

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

    因此您必须获得以下结构:

    1
    2
    3
    4
    5
    6
    .
    a"?a"€a"€ main.py
    a"
    ?a"€a"€ test_sub.ui
    a"?a"€a"€ test_sub_ui.py
    a"
    ?a"€a"test.ui
    a""a"€a"€ test_ui.py

    此后,您必须修改main.py(有关更多信息,请阅读前面的答案):

    main.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    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,那么在上一个答案中,我已经说明了如何将uic模块从PyQt5转换为PySide2

    完整的解决方案可以在这里找到