python - checkbox selection in QListView

╄→尐↘猪︶ㄣ 提交于 2020-07-09 05:11:32

问题


So, 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 appearence 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?


回答1:


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])



回答2:


Here is the PyQt5 implementation:

from PyQt5 import QtWidgets, QtGui, QtCore

class ChecklistDialog(QtWidgets.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 = QtWidgets.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 = QtWidgets.QPushButton('OK')
    self.cancelButton = QtWidgets.QPushButton('Cancel')
    self.selectButton = QtWidgets.QPushButton('Select All')
    self.unselectButton = QtWidgets.QPushButton('Unselect All')

    hbox = QtWidgets.QHBoxLayout()
    hbox.addStretch(1)
    hbox.addWidget(self.okButton)
    hbox.addWidget(self.cancelButton)
    hbox.addWidget(self.selectButton)
    hbox.addWidget(self.unselectButton)

    vbox = QtWidgets.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':

import sys

fruits = [
    'Banana',
    'Apple',
    'Elderberry',
    'Clementine',
    'Fig',
    'Guava',
    'Mango',
    'Honeydew Melon',
    'Date',
    'Watermelon',
    'Tangerine',
    'Ugli Fruit',
    'Juniperberry',
    'Kiwi',
    'Lemon',
    'Nectarine',
    'Plum',
    'Raspberry',
    'Strawberry',
    'Orange',
]
app = QtWidgets.QApplication(sys.argv)
form = ChecklistDialog('Fruit', fruits, checked=True)
if form.exec_() == QtWidgets.QDialog.Accepted:
    print('\n'.join([str(s) for s in form.choices]))`


来源:https://stackoverflow.com/questions/50666680/python-checkbox-selection-in-qlistview

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!