Walletfox.com

Checkable list in Qt with QListWidget or QListView

This article shows how to create a checkable list in Qt. The application simultaneously highlights the checked items and saves the checked items into a file.


Two variants of the solution are presented:

  • Variant 1 - a simpler solution that employs QListWidget.
  • Variant 2 - a more complex solution that employs QListView and a subclassed QStringListModel. This solution is more suitable for problems that require additional capabilities (other than checking and unchecking). This approach requires that we override methods flags(), data() and setData().

Variant 1 - QListWidget

This variant of the implementation uses QListWidget to obtain the desired functionality. The header customdialog.h can be seen below. The checkable list is represented by QListWidget. To make the items checkable, we have to add a flag Qt::ItemIsUserCheckable. Anytime we check an item, we also trigger a slot highlightChecked() which highlights the checked items in yellow.

class CustomDialog : public QDialog
{
    Q_OBJECT
public:
    CustomDialog(QWidget *parent = 0);
public slots:
    void highlightChecked(QListWidgetItem* item);
    void save();
private:
    QListWidget* widget;
    QDialogButtonBox* buttonBox;
    QGroupBox* viewBox;
    QPushButton* saveButton;
    QPushButton* closeButton;

    void createListWidget();
    void createOtherWidgets();
    void createLayout();
    void createConnections();
};

In the constructor we initialize all the widgets, populate the list widget with data, create the necessary layouts and connections.

CustomDialog::CustomDialog(QWidget *parent): QDialog(parent)
{
    setWindowTitle("Checkable list in Qt");

    createListWidget();
    createOtherWidgets();
    createLayout();
    createConnections();
}

The method createListWidget() can be seen below. We populate the list widget with the method addItems(QStringList). Once the items have been created, we enable checking/unchecking of the items with the flag Qt::ItemIsUserCheckable. This flag is all that is necessary to make items checkable. We also set the default state of the items to Qt::Unchecked.

void CustomDialog::createListWidget(){
    widget = new QListWidget;
    QStringList strList;
    strList << "monitor" << "mouse" << "keyboard" << "hard disk drive"
            << "graphic card" << "sound card" << "memory" << "motherboard";

    widget->addItems(strList);

    QListWidgetItem* item = 0;
    for(int i = 0; i < widget->count(); ++i){
        item = widget->item(i);
        item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
        item->setCheckState(Qt::Unchecked);
    }
}

In the method createConnections() we establish all the necessary connections. In particular, to enable highlighting of the items upon checking, we connect the signal itemChanged(QListWidgetItem*) of the QListWidget to the slot highlightChecked(QListWidgetItem*).

void CustomDialog::createConnections(){
    QObject::connect(widget, SIGNAL(itemChanged(QListWidgetItem*)),
                     this, SLOT(highlightChecked(QListWidgetItem*)));
    QObject::connect(saveButton, SIGNAL(clicked()), this, SLOT(save()));
    QObject::connect(closeButton, SIGNAL(clicked()), this, SLOT(close()));
}

The slot highlightChecked(QListWidgetItem*) can be seen below. The method renders the background of checked items in light yellow, the background of unchecked items remains white.

void CustomDialog::highlightChecked(QListWidgetItem *item){
    if(item->checkState() == Qt::Checked)
        item->setBackgroundColor(QColor("#ffffb2"));
    else
        item->setBackgroundColor(QColor("#ffffff"));
}

This is all that is necessary to implement checking and highlighting of items with QListWidget. The entire implementation can be found in the source files above (Variant 1).

Variant 2 - QListView and subclassed QStringListModel

Unlike the previous implementation, this implementation employs QListView and subclasses QStringListModel. Below is the header customdialog.h, which stores a pointer to an instance of QListView and a custom model instance CustonListModel.

class CustomDialog : public QDialog
{
    Q_OBJECT
public:
    CustomDialog(QWidget *parent = 0);
public slots:
    void save();
private:
    CustomListModel* model;
    QListView* view;
    QDialogButtonBox* buttonBox;
    QGroupBox* viewBox;
    QPushButton* saveButton;
    QPushButton* closeButton;

    void createListModelView();
    void createOtherWIdgets();
    void createLayout();
    void createConnections();
};

In the constructor of CustomDialog we instantiate QListView and CustomListModel (details on the implementation follow) and we populate the list model with data.

CustomDialog::CustomDialog(QWidget *parent): QDialog(parent)
{
    setWindowTitle("Checkable list in Qt");

    createListModelView();
    createOtherWIdgets();
    createLayout();
    createConnections();
}
void CustomDialog::createListModelView(){
    view = new QListView;
    model = new CustomListModel;
    view->setModel(model);

    QStringList strList;
    strList << "monitor" << "mouse" << "keyboard" << "hard disk drive"
            << "graphic card" << "sound card" << "memory" << "motherboard";
    model->setStringList(strList);
}

Subclassing QStringListModel

To make items checkable, we subclass the QStringListModel. We introduce a container that will store the checked items, namely QSet<QPersistentModelIndex> checkedItems and override the methods data(), setData() and flags().

class CustomListModel : public QStringListModel
{
public:
    CustomListModel(QObject* parent = 0);
    CustomListModel(const QStringList & strings, QObject* parent = 0);
    Qt::ItemFlags flags (const QModelIndex& index) const;
    QVariant data(const QModelIndex &index, int role) const;
    bool setData(const QModelIndex &index, const QVariant &value,
                 int role);
    void save();
private:
    QSet<QPersistentModelIndex> checkedItems;
};
Note: To store the checked items we use QPersistentModelIndex, rather than QModelIndex. Unlike QModelIndex, QPersistentModelIndex can be stored safely, since the references to items will continue to be valid as long as they can be accessed by the model.

To make items checkable, we have to override the method flags() and equip the items with an extra flag Qt::ItemIsUserCheckable.

Qt::ItemFlags CustomListModel::flags (const QModelIndex & index) const {
    Qt::ItemFlags defaultFlags = QStringListModel::flags(index);
    if (index.isValid()){
        return defaultFlags | Qt::ItemIsUserCheckable;
    }
    return defaultFlags;
}

The actual functionality is implemented in the methods setData() and data(). In setData() we firstly check the validity of the index. Since we decided to handle Qt::CheckStateRole only, we return false for all other roles. If the user checks an item, i.e. if the value passed to the setData() method equals to Qt::Checked, we insert the index into the set of checked items. If the user unchecks an item, we remove it from the set. After that, we emit the signal dataChanged() and return true.

bool CustomListModel::setData(const QModelIndex &index,
                                const QVariant &value, int role){

    if(!index.isValid() || role != Qt::CheckStateRole)
        return false;

    if(value == Qt::Checked)
        checkedItems.insert(index);
    else
        checkedItems.remove(index);

    emit dataChanged(index, index);
    return true;
}

In data() we handle ourselves the Qt::CheckStateRole and Qt::BackgroundColorRole, for all other roles we return the default implementation. We render the data based on whether they are contained in the set of the checkedItems or not. If the item is contained in the checkedItems set, we return Qt::Checked, otherwise we return Qt::Unchecked. We also specify the background of items, checked items have their background rendered in a light yellow, i.e. #ffffb2, unchecked items have a white background.

QVariant CustomListModel::data(const QModelIndex &index,
                                 int role) const {
    if (!index.isValid())
        return QVariant();

    if(role == Qt::CheckStateRole)
        return checkedItems.contains(index) ?
                    Qt::Checked : Qt::Unchecked;
    
    else if(role == Qt::BackgroundColorRole)
        return checkedItems.contains(index) ?
                    QColor("#ffffb2") : QColor("#ffffff");

    return QStringListModel::data(index, role);
}

Note: The conditional statement

    if(role == Qt::CheckStateRole)
        return checkedItems.contains(index) ?
                    Qt::Checked : Qt::Unchecked;

is nothing else than:

    if(role == Qt::CheckStateRole){
        if(checkedItems.contains(index))
            return Qt::Checked;
        else
            return Qt::Unchecked;
    }

That's it! Further details of the implementation can be found in the source files at the beginning of this article (Variant 2).

Tagged: Qt