I developed a simple dialog with a checkbox, which allows the user to select one or several items from a list. Besides the standard OK and Cancel buttons, it adds Select All and Unselect All buttons, allowing the user to check/uncheck all items at once (this comes handy for large lists).
import os, sys
from PyQt4 import Qt, QtCore, QtGui
class ChecklistDialog(QtGui.QDialog):
def __init__(self, name, stringlist=None, checked=False, icon=None, parent=None):
super(ChecklistDialog, self).__init__(parent)
self.name = name
self.icon = icon
self.model = QtGui.QStandardItemModel()
self.listView = QtGui.QListView()
if stringlist is not None:
for i in range(len(stringlist)):
item = QtGui.QStandardItem(stringlist[i])
item.setCheckable(True)
check = QtCore.Qt.Checked if checked else QtCore.Qt.Unchecked
item.setCheckState(check)
self.model.appendRow(item)
self.listView.setModel(self.model)
self.okButton = QtGui.QPushButton("OK")
self.cancelButton = QtGui.QPushButton("Cancel")
self.selectButton = QtGui.QPushButton("Select All")
self.unselectButton = QtGui.QPushButton("Unselect All")
hbox = QtGui.QHBoxLayout()
hbox.addStretch(1)
hbox.addWidget(self.okButton)
hbox.addWidget(self.cancelButton)
hbox.addWidget(self.selectButton)
hbox.addWidget(self.unselectButton)
vbox = QtGui.QVBoxLayout()
vbox.addWidget(self.listView)
vbox.addStretch(1)
vbox.addLayout(hbox)
self.setLayout(vbox)
#self.setLayout(layout)
self.setWindowTitle(self.name)
if self.icon is not None: self.setWindowIcon(self.icon)
self.okButton.clicked.connect(self.accept)
self.cancelButton.clicked.connect(self.reject)
self.selectButton.clicked.connect(self.select)
self.unselectButton.clicked.connect(self.unselect)
def reject(self):
QtGui.QDialog.reject(self)
def accept(self):
self.choices = []
i = 0
while self.model.item(i):
if self.model.item(i).checkState():
self.choices.append(self.model.item(i).text())
i += 1
QtGui.QDialog.accept(self)
def select(self):
i = 0
while self.model.item(i):
item = self.model.item(i)
if not item.checkState():
item.setCheckState(True)
i += 1
def unselect(self):
i = 0
while self.model.item(i):
item = self.model.item(i)
item.setCheckState(False)
i += 1
if __name__ == "__main__":
fruits = ["Banana", "Apple", "Elderberry", "Clementine", "Fig",
"Guava", "Mango", "Honeydew Melon", "Date", "Watermelon",
"Tangerine", "Ugli Fruit", "Juniperberry", "Kiwi", "Lemon",
"Nectarine", "Plum", "Raspberry", "Strawberry", "Orange"]
app = QtGui.QApplication(sys.argv)
form = ChecklistDialog("Fruit", fruits, checked=True)
if form.exec_():
print("\n".join([str(s) for s in form.choices]))
The above code works, but I am bothered by the strange behaviour of QListView: when the dialog is display with the "check" parameter turned to True, all checkbozes appear selected with an "X" mark (as expected). However, when the Select All button is clicked, the checkboxes are instead grayed (although the items are correctly selected). I would prefer that they be displayed with an "X" mark, in order to present a consistent appearance to the user.
See the figures below.
In general terms, my question is: how to control the way the checkboxes are displayed in a QListView?
The problem is caused because the states of a QCheckBox
are 3:
Qt::Unchecked 0 The item is unchecked.
Qt::PartiallyChecked 1 The item is partially checked. Items in hierarchical models may be partially checked if some, but not all, of their children are checked.
Qt::Checked 2 The item is checked.
And as you see they are integer values, and when you pass the value of True this is converted to 1 which is equivalent to Qt::PartiallyChecked
by those you get the rectangle instead of the cross.
The solution is to pass it the correct value: QtCore.Qt.Checked
as I show below:
import sys
from PyQt4 import Qt, QtCore, QtGui
class ChecklistDialog(QtGui.QDialog):
def __init__(
self,
name,
stringlist=None,
checked=False,
icon=None,
parent=None,
):
super(ChecklistDialog, self).__init__(parent)
self.name = name
self.icon = icon
self.model = QtGui.QStandardItemModel()
self.listView = QtGui.QListView()
for string in stringlist:
item = QtGui.QStandardItem(string)
item.setCheckable(True)
check = \
(QtCore.Qt.Checked if checked else QtCore.Qt.Unchecked)
item.setCheckState(check)
self.model.appendRow(item)
self.listView.setModel(self.model)
self.okButton = QtGui.QPushButton('OK')
self.cancelButton = QtGui.QPushButton('Cancel')
self.selectButton = QtGui.QPushButton('Select All')
self.unselectButton = QtGui.QPushButton('Unselect All')
hbox = QtGui.QHBoxLayout()
hbox.addStretch(1)
hbox.addWidget(self.okButton)
hbox.addWidget(self.cancelButton)
hbox.addWidget(self.selectButton)
hbox.addWidget(self.unselectButton)
vbox = QtGui.QVBoxLayout(self)
vbox.addWidget(self.listView)
vbox.addStretch(1)
vbox.addLayout(hbox)
self.setWindowTitle(self.name)
if self.icon:
self.setWindowIcon(self.icon)
self.okButton.clicked.connect(self.onAccepted)
self.cancelButton.clicked.connect(self.reject)
self.selectButton.clicked.connect(self.select)
self.unselectButton.clicked.connect(self.unselect)
def onAccepted(self):
self.choices = [self.model.item(i).text() for i in
range(self.model.rowCount())
if self.model.item(i).checkState()
== QtCore.Qt.Checked]
self.accept()
def select(self):
for i in range(self.model.rowCount()):
item = self.model.item(i)
item.setCheckState(QtCore.Qt.Checked)
def unselect(self):
for i in range(self.model.rowCount()):
item = self.model.item(i)
item.setCheckState(QtCore.Qt.Unchecked)
if __name__ == '__main__':
fruits = [
'Banana',
'Apple',
'Elderberry',
'Clementine',
'Fig',
'Guava',
'Mango',
'Honeydew Melon',
'Date',
'Watermelon',
'Tangerine',
'Ugli Fruit',
'Juniperberry',
'Kiwi',
'Lemon',
'Nectarine',
'Plum',
'Raspberry',
'Strawberry',
'Orange',
]
app = QtGui.QApplication(sys.argv)
form = ChecklistDialog('Fruit', fruits, checked=True)
if form.exec_() == QtGui.QDialog.Accepted:
print '\n'.join([str(s) for s in form.choices])