我有下面的代码。

在PyQt5下,所有按钮单击均按预期方式工作。
在PySide2下,它们没有。

任何解释,这是PySide2错误吗?

import os
import sys
import xlrd
from PySide2 import QtCore, QtGui, QtWidgets
# from PyQt5 import QtCore, QtGui, QtWidgets


class ExcelDialog(QtWidgets.QDialog):

    def __init__(self, excel_file=None, items=()):
        """
        Constructor
        :param excel_file: excel file to list
        :param items: items to show if the file is none
        """

        QtWidgets.QDialog.__init__(self)

        self.setObjectName("ExcelSelectionDialog")
        self.resize(272, 229)
        self.setMaximumSize(QtCore.QSize(272, 229))
        self.setModal(True)
        self.verticalLayout = QtWidgets.QVBoxLayout(self)
        self.verticalLayout.setContentsMargins(1, 1, 1, 1)
        self.verticalLayout.setObjectName("verticalLayout")
        self.sheets_list = QtWidgets.QListWidget(self)
        self.sheets_list.setFrameShape(QtWidgets.QFrame.StyledPanel)
        self.sheets_list.setObjectName("sheets_list")
        self.verticalLayout.addWidget(self.sheets_list)
        self.frame = QtWidgets.QFrame(self)
        self.frame.setFrameShape(QtWidgets.QFrame.NoFrame)
        self.frame.setFrameShadow(QtWidgets.QFrame.Raised)
        self.frame.setObjectName("frame")
        self.horizontalLayout = QtWidgets.QHBoxLayout(self.frame)
        self.horizontalLayout.setContentsMargins(1, 1, 1, 1)
        self.horizontalLayout.setObjectName("horizontalLayout")
        spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.horizontalLayout.addItem(spacerItem)
        self.cancelButton = QtWidgets.QPushButton(self.frame)
        self.cancelButton.setObjectName("cancelButton")
        self.horizontalLayout.addWidget(self.cancelButton)
        self.acceptButton = QtWidgets.QPushButton(self.frame)
        self.acceptButton.setObjectName("acceptButton")
        self.horizontalLayout.addWidget(self.acceptButton)
        self.verticalLayout.addWidget(self.frame)

        self.retranslateUi(self)
        QtCore.QMetaObject.connectSlotsByName(self)

        # click
        self.acceptButton.clicked.connect(self.accepted)
        self.cancelButton.clicked.connect(self.rejected)
        self.sheets_list.doubleClicked.connect(self.accepted)

        self.excel_sheet = None

        self.sheet_names = list()
        if excel_file is not None:
            if os.path.exists(excel_file):
                self.fill_from_file(excel_file=excel_file)
            else:
                self.sheets_list.addItems(items)
        else:
            self.sheets_list.addItems(items)

    def fill_from_file(self, excel_file):
        """

        :param excel_file:
        :return:
        """
        if excel_file is not None:
            xls = xlrd.open_workbook(excel_file, on_demand=True)
            self.sheet_names = xls.sheet_names()
            self.sheets_list.addItems(self.sheet_names)

            if len(self.sheet_names) > 0:
                self.excel_sheet = 0

    def accepted(self):
        """

        :return:
        """
        if len(self.sheets_list.selectedIndexes()):
            self.excel_sheet = self.sheets_list.selectedIndexes()[0].row()
        print('Accepted: self.excel_sheet: ', self.excel_sheet)

        self.close()

    def rejected(self):
        """

        :return:
        """
        print('Rejected: self.excel_sheet: ', self.excel_sheet)
        self.close()

    def retranslateUi(self, ExcelSelectionDialog):
        """

        :param ExcelSelectionDialog:
        :return:
        """
        ExcelSelectionDialog.setWindowTitle(QtWidgets.QApplication.translate("ExcelSelectionDialog", "Excel sheet selection", None, -1))
        self.cancelButton.setText(QtWidgets.QApplication.translate("ExcelSelectionDialog", "Cancel", None, -1))
        self.acceptButton.setText(QtWidgets.QApplication.translate("ExcelSelectionDialog", "Accept", None, -1))


if __name__ == "__main__":
    excel_file = None
    app = QtWidgets.QApplication(sys.argv)
    window = ExcelDialog(excel_file, items=['A', 'B', 'C'])
    window.show()
    sys.exit(app.exec_())


顺便说一句,我在PySide 5.12.3上

最佳答案

看来是个错误,在我的回答中,我将尝试分析正在发生的事情。

首先,我将MCVE简化为以下内容:

from PySide2 import QtCore, QtGui, QtWidgets
# from PyQt5 import QtCore, QtGui, QtWidgets


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

        list_widget = QtWidgets.QListWidget()
        list_widget.addItems(list("ABC"))

        accept_button = QtWidgets.QPushButton("Accept")
        cancel_button = QtWidgets.QPushButton("Cancel")

        lay = QtWidgets.QVBoxLayout(self)
        hlay = QtWidgets.QHBoxLayout()
        hlay.addStretch()
        hlay.addWidget(accept_button)
        hlay.addWidget(cancel_button)
        lay.addWidget(list_widget)
        lay.addLayout(hlay)

        accept_button.clicked.connect(self.accepted)
        cancel_button.clicked.connect(self.rejected)

    def accepted(self):
        print("accepted")

    def rejected(self):
        print("rejected")


要记住的另一件事是QDialog有一个称为accepted()的信号,它导致这种奇怪的行为。还请记住,可以在带有可调用信号,带有插槽的信号与另一个信号之间建立连接。

我的假设是,PySide2首先使用python的正常功能进行连接,然后才与插槽和信号进行连接,并且使用以下代码对此进行了验证:

    # ...
    # create connections
    QtCore.QObject.connect(self, QtCore.SIGNAL("accepted()"), self, QtCore.SLOT("accepted_test()"))
    QtCore.QObject.connect(self, QtCore.SIGNAL("rejected()"), self, QtCore.SLOT("rejected_test()"))

def accepted_test(self):
    print("accepted_test")

def rejected_test(self):
    print("rejected_test")

def accepted(self):
    print("accepted")

def rejected(self):
    print("rejected")
# ...


如果按“接受”和“取消”按钮,则会得到:

accepted_test
rejected_test




另一方面,PyQt5似乎没有相同的层次结构,因此它更喜欢与python函数的连接。

指出一个错误是主观的,因为在文档中未明确声明该错误,并且取决于每个人的期望行为,这可能是由PySide2计划的,因为对他们而言,它是正确的,而对PyQt5则是相同的。

对于这种情况,有一种解决方法:使用装饰器@QtCore.Slot()将QMetaObject的一部分接受和拒绝:

    # ...
    accept_button.clicked.connect(self.accepted)
    cancel_button.clicked.connect(self.rejected)

@QtCore.Slot()
def accepted(self):
    print("accepted")

@QtCore.Slot()
def rejected(self):
    print("rejected")
# ...


但是,我个人的建议(可以视为一种好的做法)是不要创建已经使用基类的方法。

10-06 13:17