Search code examples
c++qtqt5qlistviewqstringlistmodel

QListView flushes slowly when there are three QListViews in a QDialog


I customized my data model AnnotationListModel inherited from QStringListModel.

The purpose of AnnotationListModel class, is to add checkboxes for data items and achieve exclusion between data items.

Now, I create three QListView and AnnotationListModel objects in a QDialog. The data item in the model is added as a checkbox, and is exclusive.

I can check the checkbox in QListView, but the checked status can't display quickly.

How can I enable QListView to flush quickly?

class AnnotationListModel :public QStringListModel
{
    Q_OBJECT
public:
    AnnotationListModel(AnnotationType annType, const QStringList& stringList, QObject* parent = nullptr);
    ~AnnotationListModel() {};

protected:
    Qt::ItemFlags flags(const QModelIndex& index) const override;
    QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
    bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override;

signals:
    void sendAnnotation(const AnnotationType& annoType, const QString& annotation);

private:
    AnnotationType m_annoType;
    QStringList m_stringList;
    QMap<QString, bool> m_stringList_map; //<annotationName,checked>
};

AnnotationListModel::AnnotationListModel(AnnotationType annType, const QStringList& stringList, QObject* parent)
    :QStringListModel(parent), m_stringList(stringList), m_annoType(annType)
{
    setStringList(m_stringList);
    for (auto& value : m_stringList)
    {
        m_stringList_map[value] = false;
    }
}

Qt::ItemFlags AnnotationListModel::flags(const QModelIndex& index) const
{
    if (!index.isValid())
    {
        return Qt::ItemIsEnabled;
    }
    return Qt::ItemIsUserCheckable | QStringListModel::flags(index);
}

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

    if (role == Qt::CheckStateRole)
    {
        if (m_stringList_map[m_stringList.at(index.row())] == true)
        {
            return Qt::Checked;
        }
        else
        {
            return Qt::Unchecked;
        }
    }
    else if (role == Qt::DisplayRole)
    {
        return m_stringList.at(index.row());
    }

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

bool AnnotationListModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
    if (!index.isValid())
    {
        return false;
    }

    if (role == Qt::CheckStateRole)
    {
        if (value == Qt::Checked)
        {
            for (int i{ 0 }; i < m_stringList.size(); ++i)
            {
                if (i != index.row())
                {
                    m_stringList_map[m_stringList.at(i)] = false;
                }
                else
                {
                    m_stringList_map[m_stringList.at(i)] = true;
                    sendAnnotation(m_annoType, m_stringList.at(i));
                }
            }
        }
        else if (value == Qt::Unchecked)
        {
            m_stringList_map[m_stringList.at(index.row())] = false;
        }
    }

    return QStringListModel::setData(index, value, role);
}

Create three AnnotationListModel objects:

switch (itr_kind.key())
{
case AnnotationType::CounCode:
    m_counCodeModel = new AnnotationListModel(AnnotationType::CounCode, l_annoStrList);
    m_uiDialog->listView_country->setModel(m_counCodeModel);
    connect(m_counCodeModel, &AnnotationListModel::sendAnnotation, this, &AnnotationDialog::sendAnnotation);
    break;

case AnnotationType::DriSide:
    m_driSideModel = new AnnotationListModel(AnnotationType::DriSide, l_annoStrList);
    m_uiDialog->listView_driving->setModel(m_driSideModel);
    connect(m_driSideModel, &AnnotationListModel::sendAnnotation, this, &AnnotationDialog::sendAnnotation);
    break;

case AnnotationType::RoadType:
    m_roadTypeModel = new AnnotationListModel(AnnotationType::RoadType, l_annoStrList);
    m_uiDialog->listView_road->setModel(m_roadTypeModel);
    connect(m_roadTypeModel, &AnnotationListModel::sendAnnotation, this, &AnnotationDialog::sendAnnotation);
}

The Dialog is opened through mainwindow.

enter image description here


Solution

  • IMO, you have made 4 mistakes but the important ones are the first 2:

    • The correct approach to add a functionality to an existing model (QStringListModel in your case) is with a proxy model (a subclass of QAbstractProxyModel). It works just as well but is easier to implement and way more flexible since it can be used with any model type.
    • As you want your checkboxes to be exclusive, you only need to remember the one that is checked, there was no need to have a whole QMap<...> container for that.
      I believe this is where most of the computation time was being wasted in your code, especially when testing the dialog in debug mode (containers have many, many checks that are done only in debug, making them way slower to use than in release).
      At least, it was right not to save the checked index as a QModelIndex.
    • You forgot to emit dataChanged signals from your setData method.
    • You forgot to return false/true from your setData method. To be precise, a boolean is indeed returned but only by QStringListModel::setData(...), which is not what you want to do.

    The below AnnotationProxyModel implements exclusive checkboxes for any model you want to implement. It derives QIdentityProxyModel and remember the checked index thanks to a QPersistentModelIndex (which unlike QModelIndex is OK to save).
    I let you check if you really need to keep the Q_OBJECT macro and your signal(s) in the below definition and if yes, add them back yourself.

    #include <QtCore/QIdentityProxyModel>
    class AnnotationProxyModel : public QIdentityProxyModel {
    public:
        AnnotationProxyModel(QObject* parent = nullptr);
        QVariant data(const QModelIndex& index, int role) const override;
        Qt::ItemFlags flags(const QModelIndex& index) const override;
        bool setData(const QModelIndex& index, const QVariant& value, int role) override;
    private:
        QPersistentModelIndex checkedIndex;
    };
    
    AnnotationProxyModel::AnnotationProxyModel(QObject* parent) : 
        QIdentityProxyModel(parent),
        checkedIndex()
    {
    }
    
    QVariant AnnotationProxyModel::data(const QModelIndex& index, int role) const
    {
        switch (role) {
        case Qt::CheckStateRole: return QVariant((checkedIndex == index) ? Qt::Checked : Qt::Unchecked);
        default: return QIdentityProxyModel::data(index, role);
        }
    }
    
    Qt::ItemFlags AnnotationProxyModel::flags(const QModelIndex& index) const
    {
        return QIdentityProxyModel::flags(index) | Qt::ItemIsUserCheckable;
    }
    
    bool AnnotationProxyModel::setData(const QModelIndex& index, const QVariant& value, int role)
    {
        switch (role) {
        case Qt::CheckStateRole: {
            if (checkedIndex == index) {
                if (value.value<Qt::CheckState>() != Qt::Unchecked) // Trying to check the already checked index -> return false
                    return false;
                else {
                    checkedIndex = QPersistentModelIndex(); // Unchecking the already checked index -> return true;
                    emit dataChanged(index, index, { Qt::CheckStateRole });
                    return true;
                }
            }
            else {
                if (value.value<Qt::CheckState>() == Qt::Unchecked) // Trying to uncheck an index that is already unchecked -> return false
                    return false;
                else {
                    QModelIndex uncheckedIndex = checkedIndex;
                    checkedIndex = QPersistentModelIndex(index);
                    emit dataChanged(uncheckedIndex, uncheckedIndex, { Qt::CheckStateRole });
                    emit dataChanged(index, index, { Qt::CheckStateRole });
                    return true;
                }
            }
        }
        default: return QIdentityProxyModel::setData(index, value, role);
        }
    }
    

    Like any other proxy model, it is used by calling setSourceModel(...) with your QStringListModel, i.e. in that fashion:

    AnnotationProxyModel* checkModel = new AnnotationProxyModel(view);
    checkModel->setSourceModel(countryModel);
    view->setModel(checkModel);