Reputation: 1186
I have the code below.
Under PyQt5 all the button clicks work as expected. Under PySide2 they do not.
Any explanation, is this a PySide2 bug?
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_())
BTW, I'm on PySide 5.12.3
Upvotes: 1
Views: 753
Reputation: 243965
It seems a bug, in my answer I will try to analyze what is happening.
First of all I have simplified the MCVE to the following:
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")
Another thing to keep in mind is that QDialog has a signal called accepted()
that is causing this strange behavior. Also keep in mind that the connection can be made between a signal with a callable, with a slot and another signal.
My hypothesis is that PySide2 first makes the connection with the slots and signals before making the connections with the normal functions of python, and that is verified using the following code:
# ...
# 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")
# ...
If you press the Accept and Cancel buttons you get:
accepted_test
rejected_test
On the other hand, PyQt5 does not seem to have the same hierarchy, so it prefers the connection with the python function.
Indicate that a bug is subjective because in the docs is not explicitly stated and depends on the behavior that each one expects, maybe this is planned by PySide2 since for them it is correct, and the same for PyQt5.
There is a workaround for this case: make accepted and rejected part of the QMetaObject using the decorator @QtCore.Slot()
:
# ...
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")
# ...
But my personal recommendation that can be considered as good practice is not to create methods that already use the base class.
Upvotes: 1