问题
I have a QWizard that reads the column headers of a CSV file and makes the user choose what they want with each column. In the 2nd page of this wizard, I add the combo-boxes in a for loop that loops over the column names of the CSV. All combo-boxes are mandatory fields. However, another constraint is that at least one of them must be selected to choice 3 or above (c1 or c2 in my MWE), before the user can press "Next".
In addition to self.NextButton.setEnabled(False), I also tried using isComplete and completeChanged according to this example and this question, but being a beginner to PyQt and not so good at C++, I haven't been able to understand that much of the existing documentation. For now isComplete seems to be fine but the wizard isn't registering it.
Can anyone teach me how I would be able to achieve what I wanted (the text in bold)?
from PyQt5 import QtGui, QtWidgets, QtCore
import csv
class ChooseFile(QtWidgets.QWizardPage):
def __init__(self, parent=None):
super(ChooseFile, self).__init__(parent)
body = QtWidgets.QVBoxLayout()
self.filePathShow = QtWidgets.QLineEdit(self)
self.filePathShow.setReadOnly(True) # not editable
self.registerField("filePathShow*", self.filePathShow) # mandatory
body.addWidget(self.filePathShow)
browseButton = QtWidgets.QPushButton('Browse...', self)
browseButton.clicked.connect(self.browseDialog)
browseBox = QtWidgets.QHBoxLayout()
browseBox.addWidget(browseButton)
body.addLayout(browseBox)
self.setLayout(body)
def browseDialog(self):
filePath, _ = QtWidgets.QFileDialog.getOpenFileName(self, 'Open CSV', '/home', '(*.csv)')
if filePath: # only changes if valid file, stays the same if user presses cancel
self.setField("filePathShow", filePath)
class ChooseColumns(QtWidgets.QWizardPage):
def __init__(self, parent=None):
super(ChooseColumns, self).__init__(parent)
self.box = QtWidgets.QGroupBox()
body = QtWidgets.QVBoxLayout()
body.addWidget(self.box) # these are where the choices (comboboxes) go
self.setLayout(body)
def initializePage(self):
filePath2 = self.field("filePathShow")
with open(str(filePath2), 'r') as f:
reader = csv.reader(f)
self.columns = next(reader)
# make a combobox for each column
grid = QtWidgets.QGridLayout()
self.comboBoxes = [None] * len(self.columns)
for i, col in enumerate(self.columns):
grid.addWidget(QtWidgets.QLabel(col), i, 0) # printscolumn name
self.comboBoxes[i] = QtWidgets.QComboBox()
self.comboBoxes[i].addItem("") # default value since is mandatory field
self.comboBoxes[i].addItem("a")
self.comboBoxes[i].addItem("b")
self.comboBoxes[i].addItem("c1")
self.comboBoxes[i].addItem("c2")
grid.addWidget(self.comboBoxes[i], i, 1)
self.registerField("column" + str(i) + "*", self.comboBoxes[i]) # all mandatory
self.comboBoxes[i].currentIndexChanged.connect(self.isComplete)
#self.connect(self.comboBoxes[i], QtCore.SIGNAL(currentIndexChanged()),
# self, QtCore.SIGNAL(completeChanged()))
self.comboBoxes[i].currentIndexChanged.connect(self.completeChanged) # DOESN'T WORK
self.box.setLayout(grid)
def isComplete(self, other): # WORKS
self.selections = [None] * len(self.columns)
for i in range(len(self.selections)): # first fill the list
self.selections[i] = self.comboBoxes[i].currentIndex()
#print(self.selections)
for item in self.selections: # then evaluate the values
if i >= 3:
return True
return False
class Manager(QtWidgets.QWizard):
def __init__(self, parent=None):
super(Manager, self).__init__(parent)
self.resize(500, 300)
self.addPage(ChooseFile(self))
self.addPage(ChooseColumns(self))
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = Manager()
w.show()
sys.exit(app.exec_())
回答1:
The isComplete signal must return a Boolean that indicates whether it can advance to the next page or terminate the process. This method should not be invoked directly but through the completeChanged signal.
This case is special since the amount of QComboBox is variable, so if the user goes back to the previous page and selects another .csv, the amount of QComboBox should be changed and registered would be a problem so in this case I will not do it but It is directly handled using isComplete.
Finally, as the objective (I suppose) is to obtain the selected values then I will use a property of the QWizard to store that information and be able to obtain it on the last page that I added for my test.
import csv
from PyQt5 import QtGui, QtWidgets, QtCore
class ChooseFile(QtWidgets.QWizardPage):
def __init__(self, parent=None):
super(ChooseFile, self).__init__(parent)
body = QtWidgets.QVBoxLayout(self)
self.filePathShow = QtWidgets.QLineEdit(self)
self.filePathShow.setReadOnly(True) # not editable
self.registerField("filePathShow*", self.filePathShow) # mandatory
body.addWidget(self.filePathShow)
browseButton = QtWidgets.QPushButton("Browse...", self)
browseButton.clicked.connect(self.browseDialog)
browseBox = QtWidgets.QHBoxLayout()
browseBox.addWidget(browseButton)
body.addLayout(browseBox)
def browseDialog(self):
filePath, _ = QtWidgets.QFileDialog.getOpenFileName(
self, "Open CSV", "/home", "(*.csv)"
)
if filePath:
self.setField("filePathShow", filePath)
class ChooseColumns(QtWidgets.QWizardPage):
def __init__(self, parent=None):
super(ChooseColumns, self).__init__(parent)
self.comboboxes = []
box = QtWidgets.QGroupBox()
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(box)
self.flay = QtWidgets.QFormLayout()
box.setLayout(self.flay)
def initializePage(self):
for combo in self.comboboxes:
self.flay.removeRow(combo)
self.comboboxes = []
self.wizard().setProperty("indexes_selected", [])
self.wizard().setProperty("options_selected", [])
filePath2 = self.field("filePathShow")
options = ("", "a", "b", "c1", "c2")
with open(filePath2, "r") as f:
reader = csv.reader(f)
header = next(reader)
for i, text in enumerate(header):
combo = QtWidgets.QComboBox()
combo.addItems(options)
combo.currentIndexChanged.connect(self.completeChanged)
self.flay.addRow(text, combo)
self.comboboxes.append(combo)
def isComplete(self):
indexes = [combo.currentIndex() for combo in self.comboboxes]
is_completed = all(index >= 1 for index in indexes) and any(
index >= 3 for index in indexes
)
self.wizard().setProperty("indexes_selected", indexes)
self.wizard().setProperty(
"options_selected", [combo.currentText() for combo in self.comboboxes]
)
return is_completed
class FinalPage(QtWidgets.QWizardPage):
def initializePage(self):
print(self.wizard().property("indexes_selected"))
print(self.wizard().property("options_selected"))
class Manager(QtWidgets.QWizard):
def __init__(self, parent=None):
super(Manager, self).__init__(parent)
self.resize(500, 300)
self.addPage(ChooseFile())
self.addPage(ChooseColumns())
self.addPage(FinalPage())
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = Manager()
w.show()
sys.exit(app.exec_())
来源:https://stackoverflow.com/questions/60607172/how-do-i-enforce-the-minimum-index-of-any-one-of-multiple-qcomboboxes-within-a-f