我有下面的代码。
在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")
# ...
但是,我个人的建议(可以视为一种好的做法)是不要创建已经使用基类的方法。