qt-designer - PySide6在使用 `QMetaObject.connectSlotsByName(MainWindow)`时如何使用 `QUiLoader().load(...)`

标签 qt-designer pyside6

我有以下测试代码:

from os import path

from PySide6.QtCore import QObject, QMetaObject
from PySide6.QtUiTools import QUiLoader
from PySide6.QtWidgets import QApplication


class MyWin(QObject):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.ui = QUiLoader().load(path.join(path.dirname(__file__), "MainWindow.ui"))
        self.ui.pushButton.clicked.connect(self.on_pushButton_clicked)

    def show(self):
        self.ui.show()

    def on_pushButton_clicked(self):
        print("button pushed!")


app = QApplication([])
win = MyWin()
win.show()
app.exec()

及其关联的MainWindow.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="QVBoxLayout" name="verticalLayout">
    <item>
     <widget class="QPushButton" name="pushButton">
      <property name="text">
       <string>PushButton</string>
      </property>
     </widget>
    </item>
    <item>
     <widget class="QTableView" name="tableView"/>
    </item>
   </layout>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>800</width>
     <height>19</height>
    </rect>
   </property>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
 </widget>
 <resources/>
 <connections/>
</ui>

...按预期工作。

问题是:如何更换线路:

        self.ui.pushButton.clicked.connect(self.on_pushButton_clicked)

使用 QMetaObject.connectSlotsByName(???) 进行等效操作?

这里的问题是 PySide6 QUiLoader 无法将小部件添加为 self 的子级(因为 PyQt6 uic.loadUi(filename, self) 可以do),因此我被迫将 UI 放在一个单独的变量(self.ui)中,而插槽在“父”MyWin 中定义。

如何规避限制?

我问的原因是我的真实程序有无数的信号/槽,并且手动connect()它们是一个真正的PITA(并且容易出错)

更新: 根据建议,我修改了 MyWin 以继承 QWidget,但启用 self.ui.setParent(self) 足以阻止 UI 显示。

from os import path

from PySide6.QtCore import QMetaObject
from PySide6.QtUiTools import QUiLoader
from PySide6.QtWidgets import QApplication, QWidget


class MyWin(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.ui = QUiLoader().load(path.join(path.dirname(__file__), "MainWindow.ui"))
        self.ui.pushButton.clicked.connect(self.on_pushButton_clicked)
        self.ui.setParent(self)
        # QMetaObject.connectSlotsByName(self)

    def myshow(self):
        self.ui.show()

    def on_pushButton_clicked(self):
        print("button pushed!")


app = QApplication([])
win = MyWin()
win.myshow()
app.exec()

我还看到一些奇怪的错误:

mcon@ikea:~/projects/pyside6-test$ venv/bin/python t.py
qt.pysideplugin: Environment variable PYSIDE_DESIGNER_PLUGINS is not set, bailing out.
qt.pysideplugin: No instance of QPyDesignerCustomWidgetCollection was found.
Qt WebEngine seems to be initialized from a plugin. Please set Qt::AA_ShareOpenGLContexts using QCoreApplication::setAttribute and QSGRendererInterface::OpenGLRhi using QQuickWindow::setGraphicsApi before constructing QGuiApplication.
^C^C^C^C
Terminated

我需要从另一个终端终止进程,正常的 Ctrl-C 会被忽略。

更新2: 我根据@ekhumoro的建议进一步更新了代码:

from os import path

from PySide6.QtCore import QMetaObject
from PySide6.QtUiTools import QUiLoader
from PySide6.QtWidgets import QApplication, QWidget, QMainWindow


class UiLoader(QUiLoader):
    _baseinstance = None

    def createWidget(self, classname, parent=None, name=''):
        if parent is None and self._baseinstance is not None:
            widget = self._baseinstance
        else:
            widget = super(UiLoader, self).createWidget(classname, parent, name)
            if self._baseinstance is not None:
                setattr(self._baseinstance, name, widget)
        return widget

    def loadUi(self, uifile, baseinstance=None):
        self._baseinstance = baseinstance
        widget = self.load(uifile)
        QMetaObject.connectSlotsByName(widget)
        return widget


class MyWin(QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        UiLoader().loadUi(path.join(path.dirname(__file__), "MainWindow.ui"), self)
        # self.pushButton.clicked.connect(self.on_pushButton_clicked)
        QMetaObject.connectSlotsByName(self)

    def on_pushButton_clicked(self):
        print("button pushed!")


app = QApplication([])
win = MyWin()
win.show()
app.exec()

这也不起作用:它显示 GUI,但按钮单击未连接(除非我明确取消注释该行)。

我做错了什么?

最佳答案

回答标题中所述的问题:

可以通过将容器小部件设置为 ui-widget 的父级来修复原始示例。但是,还需要一些额外的步骤。首先,ui-widget 的标志必须包含 Qt.Window ,否则它将成为不可见窗口的子窗口。其次,必须重新实现 ui-widget 的 close 事件,以便应用程序正确关闭。最后,自动连接的插槽必须用 QtCore.Slot 装饰。

这是一个完整的示例:

from os import path
from PySide6.QtCore import Qt, QEvent, Slot, QMetaObject
from PySide6.QtUiTools import QUiLoader
from PySide6.QtWidgets import QApplication, QWidget


class MyWin(QWidget):
    def __init__(self):
        super().__init__()
        self.ui = QUiLoader().load(
            path.join(path.dirname(__file__), "MainWindow.ui"))
        self.ui.setParent(self, self.ui.windowFlags() | Qt.WindowType.Window)
        self.ui.installEventFilter(self)
        QMetaObject.connectSlotsByName(self)

    def eventFilter(self, source, event):
        if event.type() == QEvent.Type.Close and source is self.ui:
            QApplication.instance().quit()
        return super().eventFilter(source, event)

    def myshow(self):
        self.ui.show()

    @Slot()
    def on_pushButton_clicked(self):
        print("button pushed!")


app = QApplication(['Test'])
win = MyWin()
win.myshow()
app.exec()

PS:另见我完全修改后的alternative solution 使用 loadUi 风格的方法,现在可以在 PySide2 和 PySide6 上正常工作。

关于qt-designer - PySide6在使用 `QMetaObject.connectSlotsByName(MainWindow)`时如何使用 `QUiLoader().load(...)`,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/74254074/

相关文章:

python - PyQt设计器: How to make a button's edges rounder?

qt - 如何从 .ui 文件中读取 qt5 中的小部件?

python - QtDesigner 更改将在重新设计用户界面后丢失

python - 为什么我不能在graphicsview上使用鼠标事件(mouseMoveEvent, mouseReleaseEvent)?

python - Qt Designer 直接从设计器自动调整 tableWidget

python - 如何从功能 QSystemTrayIcon 向上下文菜单添加操作

python - PySide6:加载共享库时出错:libQt6DesignerComponents.so.6:无法打开共享对象文件

python - 无法从 pyside 应用程序中的 qtquick2plugin.dll 中提取插件元数据

python - 自定义QQuickItem未绘制