QAbstractListModel checkbox list exclusive
-
Hi,
I'm trying to create a custom list of checkable item. There can be only one item check at time. If we click on an item the (select/unselect) operation must be done on CPP side and QML side must react to signal "selected". The list must be own by the CPP side.
But I'm having problem reacting to unselect event.
Here is what I have :
PopupInputListModel.h
#ifndef POPUPINPUTLISTMODELLIST_H #define POPUPINPUTLISTMODELLIST_H #include <QAbstractListModel> class PopupInputListModelList; class PopupInputListModel : public QObject { Q_OBJECT Q_PROPERTY(QString value READ value WRITE value NOTIFY valueChanged) Q_PROPERTY(QString displayText READ displayText WRITE displayText NOTIFY displayTextChanged) Q_PROPERTY(bool selected READ selected WRITE selected NOTIFY selectedChanged) public: PopupInputListModel(QObject* parent = 0); PopupInputListModel(const QString& value, const QString& displayText, bool selected = false, QObject* parent = 0); public: QString value() const; void value(const QString& newValue); QString displayText() const; void displayText(const QString& newDisplayText); bool selected() const; void selected(bool bselected); signals: void valueChanged(); void displayTextChanged(); void selectedChanged(); private: QString m_value; QString m_displayText; bool m_selected; friend class PopupInputListModelList; }; Q_DECLARE_METATYPE(PopupInputListModel *) class PopupInputListModelList : public QAbstractListModel { Q_OBJECT Q_PROPERTY(int count READ rowCount NOTIFY countChanged) public: enum MainMenuListModelDataRole { DisplayTextRole = Qt::UserRole + 1, ValueRole, SelectedRole }; public: PopupInputListModelList(QObject *parent = 0); int rowCount(const QModelIndex &parent = QModelIndex()) const; QVariant data(const QModelIndex &index, int role = DisplayTextRole) const; Qt::ItemFlags flags(const QModelIndex& index) const; bool setData(const QModelIndex & index, const QVariant & value, int role); void addItem(PopupInputListModel* pMenu); protected: QHash<int, QByteArray> roleNames() const; signals: void countChanged(int iNewCount); private: QList<PopupInputListModel *> m_ItemsList; friend class PopupInputListModel; }; Q_DECLARE_METATYPE(PopupInputListModelList *) #endif // POPUPINPUTLISTMODELLIST_H
PopupInputListModel.cpp
#include <QQmlEngine> #include "PopupInputListModelList.h" #include <QDebug> PopupInputListModel::PopupInputListModel(QObject* parent /*= 0*/) : QObject(parent) , m_value("") , m_displayText("") , m_selected(false) {} PopupInputListModel::PopupInputListModel(const QString& value, const QString& displayText, bool selected /*= false*/, QObject* parent /*= 0*/) : QObject(parent) , m_value(value) , m_displayText(displayText) , m_selected(selected) {} QString PopupInputListModel::value() const { return m_value; } void PopupInputListModel::value(const QString& newValue) { if (m_value != newValue) { m_value = newValue; emit valueChanged(); } } QString PopupInputListModel::displayText() const { return m_displayText; } void PopupInputListModel::displayText(const QString& newDisplayText) { if (m_displayText != newDisplayText) { m_displayText = newDisplayText; emit displayTextChanged(); } } bool PopupInputListModel::selected() const { return m_selected; } void PopupInputListModel::selected(bool newSelected) { if (m_selected != newSelected) { m_selected = newSelected; emit selectedChanged(); } } PopupInputListModelList::PopupInputListModelList(QObject *parent) : QAbstractListModel(parent) { } int PopupInputListModelList::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent) return m_ItemsList.count(); } QVariant PopupInputListModelList::data(const QModelIndex &index, int role) const { if (index.isValid() == false) { return QVariant(); } if (index.row() < 0 || index.row() >= m_ItemsList.count()) { return QVariant(); } QVariant result; switch (role) { case DisplayTextRole: case Qt::DisplayRole: { result = m_ItemsList.at(index.row())->displayText(); } break; case ValueRole: result = m_ItemsList.at(index.row())->value(); break; case SelectedRole: result = m_ItemsList.at(index.row())->selected(); break; default: result = QVariant(); break; } return result; } Qt::ItemFlags PopupInputListModelList::flags(const QModelIndex& index) const { Qt::ItemFlags returnFlags = QAbstractListModel::flags(index); return returnFlags; } bool PopupInputListModelList::setData(const QModelIndex & index, const QVariant & value, int role) { int elemIndex = index.row(); bool bNewValue = value.toBool(); if (bNewValue == true) { for (QList<PopupInputListModel *>::iterator it = m_ItemsList.begin(); it != m_ItemsList.end(); ++it) { int indexToChanged = (it - m_ItemsList.begin()); if ((*it)->selected() == true && indexToChanged != elemIndex) { (*it)->selected(false); //emit dataChanged(createIndex(indexToChanged, 0), createIndex(indexToChanged, 0)); qDebug() << (*it)->m_displayText << " setData force to [UNSELECTED]"; } } } m_ItemsList[elemIndex]->selected(bNewValue); //emit dataChanged(createIndex(elemIndex, 0), createIndex(elemIndex, 0)); for (QList<PopupInputListModel *>::iterator itDebug = m_ItemsList.begin(); itDebug != m_ItemsList.end(); ++itDebug) { qDebug() << (*itDebug)->m_displayText << " setData is " << ((*itDebug)->selected() ? "[SELECTED]" : "[UNSELECTED]"); } emit dataChanged(createIndex(0, 0), createIndex(m_ItemsList.size() - 1, 0)); return true; } void PopupInputListModelList::addItem(PopupInputListModel* pItem) { beginInsertRows(QModelIndex(), rowCount(), rowCount()); QQmlEngine::setObjectOwnership(pItem, QQmlEngine::CppOwnership); pItem->setParent(this); m_ItemsList << pItem; emit countChanged(m_ItemsList.size()); endInsertRows(); } QHash<int, QByteArray> PopupInputListModelList::roleNames() const { QHash<int, QByteArray> roles; roles[DisplayTextRole] = "displayText"; roles[ValueRole] = "value"; roles[SelectedRole] = "selected"; return roles; }
Now here is how I inject the list to the QML
main.cpp
QGuiApplication app(argc, argv); QQmlApplicationEngine engine; PopupInputListModelList* pNewList = new PopupInputListModelList(); pNewList->addItem(new PopupInputListModel("0", "Sunday", true) ); pNewList->addItem(new PopupInputListModel("1", "Monday") ); pNewList->addItem(new PopupInputListModel("2", "Tuesday") ); pNewList->addItem(new PopupInputListModel("3", "Wednesday") ); pNewList->addItem(new PopupInputListModel("4", "Thursday") ); pNewList->addItem(new PopupInputListModel("5", "Friday") ); pNewList->addItem(new PopupInputListModel("6", "Saturday") ); QQmlEngine::setObjectOwnership(pNewList, QQmlEngine::CppOwnership); // Create the UIManager engine.rootContext()->setContextProperty("myList", pNewList);
And the QML side :
ListView { width : 200 spacing: 2 model: myList delegate: CheckBox { id : c width : parent.width height : 15 text: model.displayText + " ---> " + (model.selected ? "CHECK" : "UNCHECK") checked : { console.log("checked value changed for [" + displayText + "] = [" + selected + "]") return (selected === true) } onClicked: { selected = !selected } } }
First time the list is displayed the item "Sunday" is selected. If we click on the item "Monday" we can see this :
"Sunday" setData force to [UNSELECTED] "Sunday" setData is [UNSELECTED] "Monday" setData is [SELECTED] "Tuesday" setData is [UNSELECTED] "Wednesday" setData is [UNSELECTED] "Thursday" setData is [UNSELECTED] "Friday" setData is [UNSELECTED] "Saturday" setData is [UNSELECTED] qml: checked value changed for [Sunday] = [false] qml: checked value changed for [Sunday] = [false] qml: checked value changed for [Tuesday] = [false] qml: checked value changed for [Tuesday] = [false] qml: checked value changed for [Wednesday] = [false] qml: checked value changed for [Wednesday] = [false] qml: checked value changed for [Thursday] = [false] qml: checked value changed for [Thursday] = [false] qml: checked value changed for [Friday] = [false] qml: checked value changed for [Friday] = [false] qml: checked value changed for [Saturday] = [false] qml: checked value changed for [Saturday] = [false]
Now we can see that "monday" is now selected on CPP side. But I did never get any feedbacko on the QML side.
Also, I don't understand WHY the checked property is read twice for each other elements ??? And why there is not read for item "monday" on QML side ???What is strange is that the text of the checkbox label is valid and properly react to the property "selected", but not property "checked" !
Another thing I've tried whas to make react checkbox checked property to the onSelectedChange event of the list model item.
delegate: CheckBox { ... Connections { target : model onSelectedChanged : { console.log("model onSelectedChanged"); // <--- checked = model.selected } } }
Once I put a breakpoint on the the line having "// <---" this solution work perfectly. But If I do not set a breakpoint I'm getting an error about saying that this signal does not exists !
QML Connections: Cannot assign to non-existent property "onSelectedChanged"
I have tried connecting it using javascript but it is simply not working moore :
delegate: CheckBox { ... onComponentCompleted : { model.selectedChanged.connect( function() { console.log("Test !"); } ); } }
It complains about model.selectedChanged is undefined.
I'm stuck on this problem since 3 days and I'm really out of idea. I hope that a good Samaritan can help me :)
Best regards,
-
Hi
try to use Binding to your CheckBox:
Binding { target: checkbox property: 'checked' value: checkboxenable }
where
checkboxenable
is role in your model