Binding list from C++ to QML
-
I am quite new to Qt (2 months). Basically I have items that will be displayed in a grid on a page, in a scroll-able view of pages. Most of the examples I found deal with models inside QML. I have items data from a file, thus the models must be from C++ side.
So far I chose ListView to be my pages scroll view and GridLayout to provide items placement. I chose GridLayout because it allows me to put item anywhere on the grid with extend properties row and column for the items. My models are derived from QAbstractListModel. So far my code is running but does not do anything (obviously). I put some code here, but I think instead try to get my probably "not so right" code to work. Please give me some clue/sample code to work with instead. I still need the UI component to represent item; it can be just a rectangle for now.Thanks,
Button_model.h
class Button_model : public QObject { Q_OBJECT Q_PROPERTY(QString DisplayText READ DisplayText WRITE setDisplayText NOTIFY DisplayTextChanged) QString m_DisplayText; Q_PROPERTY(int Page READ Page WRITE setPage NOTIFY PageChanged) int m_Page; Q_PROPERTY(int Row READ Row WRITE setRow NOTIFY RowChanged) int m_Row; Q_PROPERTY(int Col READ Col WRITE setCol NOTIFY ColChanged) int m_Col; public: explicit Button_model(QObject *parent = 0); QString DisplayText() const { return m_DisplayText; } int Page() const { return m_Page; } int Row() const { return m_Row; } int Col() const { return m_Col; } signals: void DisplayTextChanged(QString DisplayText); void PageChanged(int Page); void RowChanged(int Row); void ColChanged(int Col); public slots: void setDisplayText(QString DisplayText) { if (m_DisplayText == DisplayText) return; m_DisplayText = DisplayText; emit DisplayTextChanged(DisplayText); } void setPage(int Page) { if (m_Page == Page) return; m_Page = Page; emit PageChanged(Page); } void setRow(int Row) { if (m_Row == Row) return; m_Row = Row; emit RowChanged(Row); } void setCol(int Col) { if (m_Col == Col) return; m_Col = Col; emit ColChanged(Col); } }; typedef QList<Button_model *> ButtonsList;
Page_model.h
class Page_model : public QAbstractListModel { Q_OBJECT public: Page_model(const ButtonsList &buttonList, QObject *parent = NULL) : QAbstractListModel(parent), _buttons(buttonList) {} int rowCount(const QModelIndex &parent = QModelIndex()) const; QVariant data(const QModelIndex &index, int role) const; ~Page_model(); private: ButtonsList _buttons; }; typedef QList<Page_model *> PagesModelList;
Pages_model.h
class Pages_model : public QAbstractListModel { Q_OBJECT public: Pages_model(QObject *parent = NULL); Pages_model(const PagesModelList &pageList, QObject *parent = NULL); ~Pages_model(); int rowCount(const QModelIndex &parent = QModelIndex()) const; QVariant data(const QModelIndex &index, int role) const; void addAPage(Page_model *pAPageModel); private: PagesModelList _pagesModelList; };
Page.qml
import QtQuick 2.0 import QtQuick.Layouts 1.2 import QtQuick.Window 2.2 Rectangle { width: Screen.width-2 height: parent.height color: transparent border.color: "teal" border.width: 1 radius: 3 anchors.horizontalCenter: parent.horizontalCenter GridLayout { id: buttonsContent anchors.fill: parent margins: 3 } }
MainForm.ui.qml
import QtQuick 2.4 import QtQuick.Controls 1.3 import QtQuick.Layouts 1.2 Rectangle { id: rectangleMain property alias buttonView: flickableButtonPages width: 360 height: 500 ListView { id: flickableButtonPages anchors { bottom: parent.bottom; bottomMargin: 86; left: parent.left; leftMargin: 0; right: parent.right rightMargin: 0 top: parent.top topMargin: 0 } }
In main.cpp I have:
QQmlApplicationEngine engine; QQmlContext *root_context = engine.rootContext(); ..... pagesModel.addAPage(&aPageModel); root_context->setContextProperty("myPagesModel",&pagesModel);
-
Thank you for changing to triple back ticks. I though I saw somewhere in Forum guidelines ask to use @
I forgot to include main.qml. In there I assign the property alias of buttonView.
Basically my problem is that I have "lists" inside a "list" (, and individual list of "lists" will bind to GridLayout, not another ListView). That is 2 levels of binding, and my mind just draw a blank, have no idea what to do. I managed to do something like binding to comboBox, text, edit text,... but that straightforward and only at 1 level. So examples, documents, clues, hints,...are appreciated.import QtQuick 2.4 import QtQuick.Window 2.2 import QtQuick.Extras 1.4 Window { visible: true visibility: "FullScreen" MainForm { anchors.fill: parent homeButton.onClicked: Qt.quit() buttonView.model: myPagesModel } }
-
OK, I solved the first level of binding. The application shows pages now.
In main.cpp, it will have this// Simplified code, actual each Page_model has Button_model instances in it pagesModel.addPage(new Page_model(&pagesModel)); pagesModel.addPage(new Page_model(&pagesModel)); root_context->setContextProperty("myPagesModel",&pagesModel);
In main.qml, I just set the model of the list view
buttonView.model: myPagesModel
where buttonView is the property alias of the ListView in main.ui.qmlproperty alias buttonView: flickableButtonPages ListView { id: flickableButtonPages spacing: 2 orientation: ListView.Horizontal flickableDirection: Flickable.HorizontalFlick anchors { bottom: parent.bottom; bottomMargin: 86; left: parent.left; leftMargin: 0; right: parent.right; rightMargin: 0; top: parent.top; topMargin: 0 } delegate: Page { height: parent.height //width: parent.width } }
The one issue for display is if I set the width of the Page to parent width, as I want to flick 1 page at a time, seems just take down the application with it. But that is a separate question for later.
Now the main issue (second level of binding) is still remain. How do I bind the items in each page to the GridLayout inside my Page.qml? GridLayout does not even have delegate!
-
@TonyN Great news.
Now the main issue (second level of binding) is still remain. How do I bind the items in each page to the GridLayout inside my Page.qml? GridLayout does not even have delegate!
GridLayout
just manages the positions. I think in your case may be you can use aGridView
as it hasdelegate
andmodel
properties or use aGrid
with aRepeater
inside as it supportsmodel
and it can repeat your custom item. -
@p3c0 Does GridView support independent row and column for its items like GridLayout?
The items that I want to display on a grid-like can be at any row and column, depending on its row and column property. I checked out Grid, GridView, and GridLayout, but it seem only GridLayout allow the attached properties row and column that will allow us to set the item anywhere we want on the grid. -
@p3c0 Well, that's what I guessed.
I have an idea. I am not sure it's possible nor how to try to implement it as my knowledge in Qt is very limited. Let look at my Page.qml againimport QtQuick 2.0 import QtQuick.Layouts 1.2 import QtQuick.Window 2.2 Rectangle { id: page width: 100 height: 180 color: "transparent" border.color: "teal" border.width: 1 radius: 6 GridLayout { id: buttonsLayout columns: 2 rows:4 anchors { rightMargin: 6 leftMargin: 6 bottomMargin: 6 topMargin: 6 fill: parent } } }
In here, can we create its own delegate/model property? If we can, maybe a "onModelChanged", I can dynamically add new items to GridLayout (via children property), with their row and column set to Layout.row and Layout.column?
-
@TonyN I doubt that you can add a
delegate
andmodel
property forGridLayout
. These properties are all inbuilt and have been coded using Qt's C++ API's.
Sorry I'm too now unsure what could possibly fulfil your requirement.
Try assigning the model to aQtObject
like for eg:property QtObject myModel : page_model
This will definitely trigger the corresponding handler
onMyModelChanged
when thepage_model
will change. You can access the data usingmyModel
object. All the functions defined in theQAbstractItemModel
or whatever whichpage_model
inherits will be accessible throughmyModel
.Edited: Please ignore the earlier approach. I have deleted it from here
-
OK, sorry for not getting back to you guys quickly. I was yanked out to deal with issues of legacy products for past few days!!
Let me rephrase my questions again: Basically I want do something like the Android phone or iPhone. We have pages to host the app icons. We flick through the pages to find the app we want, and touch on the icon to run the app. So on my application, I will have pages that contain the items. Items can be different on its look, and when we select an item, we run the action that associated with it.@p3c0, I read through the comments of yours, and modify parts of my code to come up with new project for experiment. I think I have to give up my original idea of GridLayout, and go with GridView with fill in empty items between the items. I use QAbstractTableModel for the GridView. The application still run as the last time it did because I still cannot (don't know how) bind the items of a page to its GridView
@TuanLT: here is the whole projectItem_Model.h
#ifndef ITEM_H #define ITEM_H #include <QObject> class Item_model : public QObject { Q_OBJECT Q_PROPERTY(QString displayText READ displayText WRITE setDisplayText NOTIFY displayTextChanged) QString m_displayText; Q_PROPERTY(int row READ row WRITE setRow NOTIFY rowChanged) int m_row; Q_PROPERTY(int col READ col WRITE setCol NOTIFY colChanged) int m_col; public: explicit Item_model(QString text = "", int row = 0, int col = 0, QObject *parent = 0); QString displayText() const { return m_displayText; } int row() const { return m_row; } int col() const { return m_col; } signals: void displayTextChanged(QString displayText); void rowChanged(int row); void colChanged(int col); public slots: void setDisplayText(QString displayText) { if (m_displayText == displayText) return; m_displayText = displayText; emit displayTextChanged(displayText); } void setRow(int row) { if (m_row == row) return; m_row = row; emit rowChanged(row); } void setCol(int col) { if (m_col == col) return; m_col = col; emit colChanged(col); } }; typedef QList<Item_model *> ItemsList; #endif // ITEM_H
Item_model.cpp
#include "Item_model.h" Item_model::Item_model(QString text, int row, int col, QObject *parent) : QObject(parent) { m_row = row; m_col = col; m_displayText = text; }
Items_model.h
#ifndef ITEMS_MODEL_H #define ITEMS_MODEL_H #include <QtCore/QAbstractTableModel> #include "Item_model.h" class Items_model : public QAbstractTableModel { Q_OBJECT public: Items_model(int maxRow, int maxColumns, QObject *parent = NULL); int rowCount(const QModelIndex &parent = QModelIndex()) const; int columnCount(const QModelIndex & parent = QModelIndex()) const; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; void addItem(Item_model *pItem); private: ItemsList _itemsList; int _maxRows; int _maxCols; }; #endif // ITEMS_MODEL_H
Items_model.cpp
#include "Items_model.h"
Items_model::Items_model(int maxRow, int maxColumns, QObject *parent) :QAbstractTableModel(parent) { _maxCols = maxColumns; _maxRows = maxRow; } int Items_model::rowCount(const QModelIndex &parent) const { return _maxRows; } int Items_model::columnCount(const QModelIndex & parent) const { return _maxCols; } QVariant Items_model::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); int indexRow = index.row(); int indexCol = index.column(); if (indexRow >= _maxRows || indexCol >= _maxCols) return QVariant(); if (role == Qt::DisplayRole) { foreach(Item_model *pItem, _itemsList ) { if (pItem->row() == indexRow && pItem->col() == indexCol) { return pItem->displayText(); } } } return QVariant(); } void Items_model::addItem(Item_model *pItem) { // // Need check for row and column before append // _itemsList.append(pItem); }
Page_model.h
#ifndef PAGE_MODEL_H #define PAGE_MODEL_H #include <QtCore/QObject> #include "Items_model.h" class Page_model : public QObject { Q_OBJECT Q_PROPERTY(Items_model* Items READ Items WRITE setItems NOTIFY ItemsChanged) Items_model* m_Items; public: explicit Page_model(QObject *parent = 0); Items_model* Items() const { return m_Items; } signals: void ItemsChanged(Items_model* Items); public slots: void setItems(Items_model* Items) { if (m_Items == Items) return; m_Items = Items; emit ItemsChanged(Items); } }; typedef QList<Page_model *> PagesModelList; #endif // PAGE_MODEL_H
Page_model.cpp
#include "Page_model.h" Page_model::Page_model(QObject *parent) : QObject(parent) { m_Items = new Items_model(5,4,this); }
Pages_model.h
#ifndef PAGES_MODEL_H #define PAGES_MODEL_H #include "Page_model.h" class Pages_model : public QAbstractListModel { Q_OBJECT public: Pages_model(); int rowCount(const QModelIndex &parent = QModelIndex()) const; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; void addPage(Page_model *pPageModel); private: PagesModelList _pagesModelList; }; #endif // PAGES_MODEL_H
Pages_model.cpp
#include "Pages_model.h" Pages_model::Pages_model() { } int Pages_model::rowCount(const QModelIndex &parent) const { return _pagesModelList.count(); } QVariant Pages_model::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); int i = index.row(); if (i >= _pagesModelList.count()) return QVariant(); return QVariant::fromValue(_pagesModelList[i]); } void Pages_model::addPage(Page_model *pPageModel) { _pagesModelList.append(pPageModel); }
main.cpp
#include <QGuiApplication> #include <QQmlApplicationEngine> #include <QQmlContext> #include <QtQml> #include "Pages_model.h" int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QQmlApplicationEngine engine; QQmlContext *root_context = engine.rootContext(); // // prepare test data // Pages_model pagesModel; Page_model page1(&pagesModel); Page_model page2(&pagesModel); Page_model page3(&pagesModel); page1.Items()->addItem(new Item_model("Page 1, R2, C3", 2, 3, &page1)); page1.Items()->addItem(new Item_model("Page 1, R0, C0", 0, 0, &page1)); page1.Items()->addItem(new Item_model("Page 1, R1, C2", 0, 2, &page1)); page2.Items()->addItem(new Item_model("Page 2, R2, C3", 2, 3, &page2)); page2.Items()->addItem(new Item_model("Page 2, R0, C0", 0, 0, &page2)); page3.Items()->addItem(new Item_model("Page 3, R3, C3", 3, 3, &page1)); page3.Items()->addItem(new Item_model("Page 3, R0, C4", 0, 4, &page1)); page3.Items()->addItem(new Item_model("Page 3, R1, C2", 0, 2, &page1)); pagesModel.addPage(&page1); pagesModel.addPage(&page2); pagesModel.addPage(&page3); root_context->setContextProperty("myPagesModel",&pagesModel); // // load the main page // engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); return app.exec(); }
main.qml
import QtQuick 2.4 import QtQuick.Window 2.2 Window { visible: true MainForm { anchors.fill: parent quitButton.onClicked: { Qt.quit(); } pagesView.model: myPagesModel } }
MainForm.ui.qml
import QtQuick 2.4 import QtQuick.Controls 1.3 Rectangle { id: rectangleMain property alias quitButton: quitButton property alias pagesView: pagesView width: 360 height: 360 ListView { id: pagesView width: 500 orientation: ListView.Horizontal spacing: 2 anchors.bottom: quitButton.top anchors.bottomMargin: 8 anchors.right: parent.right anchors.rightMargin: 0 anchors.left: parent.left anchors.leftMargin: 0 anchors.top: parent.top anchors.topMargin: 0 delegate: PageView { height: parent.height viewModel: model.Items //displayItems:parent.model.Items } } Button { id: quitButton y: 325 text: qsTr("Quit") anchors.right: parent.right anchors.rightMargin: 50 anchors.left: parent.left anchors.leftMargin: 50 anchors.bottom: parent.bottom anchors.bottomMargin: 8 } }
PageView.qml
import QtQuick 2.0 import QtQuick.Layouts 1.2 import QtQuick.Window 2.2 Rectangle { property alias viewModel:gridViewItems.model id: pageView width: 100 height: 180 color: "transparent" border.color: "teal" border.width: 1 radius: 6 GridView { id: gridViewItems anchors { rightMargin: 6 leftMargin: 6 bottomMargin: 6 topMargin: 6 fill: parent } cellWidth: width / 2 cellHeight: 50 delegate: ItemView { height: 46 width: 46 color: "blue" //colorCode displayText:"hello"//name } // model: ListModel { // ListElement { // name: "Grey" // colorCode: "grey" // } // ListElement { // name: "Red" // colorCode: "red" // } // ListElement { // name: "Blue" // colorCode: "blue" // } // ListElement { // name: "Green" // colorCode: "green" // } // } } }
ItemView.qml
import QtQuick 2.0 Rectangle { property alias displayText: displayText.text width: 100 height: 62 Text { id: displayText anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter } }
-
I simplified the MainForm.ui.qml (as below) and find that the QML report "TypeError: Cannot read property 'items' of undefined" for each instance of the the page when I execute the app. According to this, I think I have to register Items_model. However I am unable to do so with qmlRegisterType()
(I rename the property Items in Page_model to have lowercamel: Q_PROPERTY(Items_model* items READ Items WRITE setItems NOTIFY itemsChanged) )
import QtQuick 2.4 import QtQuick.Controls 1.3 Rectangle { id: rectangleMain property alias quitButton: quitButton property alias pagesView: pagesView width: 360 height: 360 ListView { id: pagesView width: 500 orientation: ListView.Horizontal spacing: 2 anchors { bottom: quitButton.top bottomMargin: 8 right: parent.right rightMargin: 0 left: parent.left leftMargin: 0 top: parent.top topMargin: 0 } delegate: Rectangle { id:paView height: parent.height width:100 color: "transparent" border.color: "teal" border.width: 1 radius: 6 GridView { anchors { rightMargin: 6 leftMargin: 6 bottomMargin: 6 topMargin: 6 fill: parent } cellWidth: width / 2 cellHeight: 50 model:model.items delegate: Rectangle { height: 46 width: 46 color: "blue" //colorCode Text { anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter text:model.displayText } } } } } Button { id: quitButton y: 325 text: qsTr("Quit") anchors { right: parent.right rightMargin: 50 left: parent.left leftMargin: 50 bottom: parent.bottom bottomMargin: 8 } } }
-
Thanks you very much!
But, now i have new problem, when i using signal and slot to communicate between cpp file and qml file. I'm using code below:
Cpp file (main.cpp)
QQmlApplicationEngine engine;
QQmlContext* context = engine.rootContext();
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
QObject *topLevel = engine.rootObjects().value(0);
QQuickWindow *window = qobject_cast<QQuickWindow *>(topLevel);
KeywordBusiness business;
QObject::connect(window,SIGNAL(savekeywords(KeywordsModel)), &business, SLOT(SaveChangedData(KeywordsModel)));on class KeywordBusiness .h I define a slot SaveChangedData with parameter is KeywordsModel extends QObject
On qml file. I define a signal savekeywords with also parameter is KeywordsModel.
But when i click button to call signal savekeywords , in cpp file the QObject::connect funtion not working.
Please help me, I was missing something?
Thanks!
-
I simplified the MainForm.ui.qml (as below) and find that the QML report "TypeError: Cannot read property 'items' of undefined" for each instance of the the page when I execute the app.
Since that property is in parent model try accessing it with its parent object.
model: pagesView.model.items
-
@p3c0 : Thanks, you are correct about that, but that is not enough to solve the problem. I find out what was missing. Instead, create the Q_PROPERTY, I have to create custom "roles" like these (example code below) in the model. Again, I don't know this is the right way or not, but it solved my problem of binding models further down on the chain (second level). But then now, I find out that the QAbstractTableModel may not the right choice :~) Despite I implemented columnCount(), it only ask for data in rows of column 0!!
QHash<int, QByteArray> Pages_model::roleNames() const { QHash<int, QByteArray> roles; roles[DisplayNameRole] = "displayName"; roles[DisplayItemsRole] = "displayItems"; return roles; } QVariant Pages_model::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); int i = index.row(); if (i < 0 || i >= _pagesModelList.count()) return QVariant(); const Page_model *pPage = _pagesModelList[i]; if (role == DisplayNameRole) { return pPage->displayName(); } else if (role == DisplayItemsRole) { return QVariant::fromValue(pPage->Items()); } return QVariant(); }
-
But then now, I find out that the QAbstractTableModel may not the right choice :~) Despite I implemented columnCount(), it only ask for data in rows of column 0!!
That's right
GridView
orListView
only deals with rows. In that case can't you replace it withQAbstractItemModel
? Keep everything other than columns.