Accessing QAbstractListModel item in Qml without using a ListView - qt

I have subclassed QAbstractListModel in order to have a model on the qml side. I can easily use this model in ListViews and other similar components that deal with models, however, I can't access it directly. This is what I am trying without success:
myModel[0].name // TypeError: Cannot read property 'name' of undefined
Is this possible? Am I using the wrong syntax?

You can access generic model (based on QAbstractListModel) easily when you use DelegateModel model as a mediator.
import QtQuick 2.2
import QtQml.Models 2.2
DelegateModel {
id: delegateModel
}
MyModel {
id: myModel
onDataLoaded: {
delegateModel.model = myModel;
for (var row = 0; row < myModel.rowCount(); row++) {
var item = delegateModel.items.get(row).model;
console.log(" name " + row + ":" + item.name);
}
}
}

(1) You're mixing up roles and properties.
Your model implements roles which are used to feed a delegate in a view. In your code, you try to access a property.
(2) A model is not an array, where you can access rows by the [] operator.
But you can write a getter function to achieve exactly that:
class ConversationListModel : public QAbstractListModel
{
Q_OBJECT
public:
enum ConversationRoles {
IdRole = Qt::UserRole, // 256
NameRole,
};
explicit ConversationListModel(QObject *parent = 0);
QHash<int, QByteArray> roleNames() const;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
Q_INVOKABLE ConversationModel* get(quint32 conversationId) const;
signals:
// ...
Than you need a data type that represents a row, e.g. ConversationModel. Now you can access a row using
myModel.get(0).name
As stated in (1), you now need to give ConversationModel a property called name
public:
explicit ConversationModel(QObject *parent = 0);
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
QString name() const;

To make the (correct) solution of Simon Warta work, you have to register ConversationModel as QML type. For example in your main you have to state
qmlRegisterType<ConversationModel>("ConversationModel", 1, 0, "ConversationModel");
Then in your qml file you have to import this type with
import ConversationModel 1.0
Afterwards everything works as expected and you can use
myModel.get(0).name
in your qml code, given that myModel is of type ConversationListModel and was registered using QDeclarativeContext::setContextProperty.

Related

Is it possible to create binding proxy in qt qml?

i have a cpp struct which have so much fields
struct CloudMusicSongList{ ... };
and i want use it in qml in order input some infomation to it's instance, but i don't want to create a qobject derived class,and create a lot of qproperty ...
i seache in google and get this code snippt from this blog
class ListItem : public QObject
{
Q_OBJECT
public :
ListItem(QObject *parent = 0) : QObject(parent) {}
virtual ~ListItem() {}
virtual int id() const = 0;
virtual QVariant data(int role) const = 0;
virtual QHash<int, QByteArray> roleNames() const = 0;
virtual void triggerItemUpdate() {emit dataChanged();}
signals:
void dataChanged();
};
i just try following code but even not print called data method
defined.h
template <typename T>
class bindingProxy : public bindingProxyBase
{
public:
bindingProxy(QObject* parent = 0)
: bindingProxyBase(parent)
{
}
T tm;
virtual ~bindingProxy()
{
}
virtual QVariant data(int role)
{
qDebug() << "called data method";
QVariant qv;
auto fucs = Prism::Reflection::get_md<T>();
if (role == Qt::DisplayRole)
fucs[0].getFunc(tm, 0, qv);
else
fucs[role].getFunc(tm, role, qv);
return qv;
}
QHash<int, QByteArray> roleNames() const
{
static QHash<int, QByteArray> roles{};
if (roles.count() == 0)
{
for (Prism::Reflection::PrismMetaData<T>& item : Prism::Reflection::get_md<T>())
{
roles[item.offset] = item.name;
}
}
return roles;
}
bool setData(QVariant& value, int role = Qt::EditRole)
{
Prism::Reflection::get_md<T>()[role].setFunc(tm, role, value);
triggerItemUpdate();
return true;
}
};
bodyViewModel.cpp
#include ....
...
..
Q_DECLARE_METATYPE(bindingProxy<CloudMusicSongList>*);
class BodyViewModel : public QObject
{
Q_PROPERTY(bindingProxy<CloudMusicSongList>* editSongListModel READ editSongListModel WRITE setEditSongListModel NOTIFY editSongListModelChanged)
...
...
...
uing.qml
Button {
id:btn_tst
text: BodyViewModelCpp.editSongListModel.name + "hbb"
onClicked: BodyViewModelCpp.editSongListModel.name = "3232"
}
button look like this image after run app rung
i don't know whether it is the X Y problem.
somebody can help? thanks
i want create a bindingProxy template class which twoway bindable in qml
i think it should equivalence dynamicObject in wpf
Based on CloudMusicSongList I'm assuming the thing that you have is a list/array. There are quite a number of ways of exposing this to QML.
QQmlListProperty - https://doc.qt.io/qt-6/qqmllistproperty.html
QAbstractListModel - https://doc-snapshots.qt.io/qt6-dev/qabstractlistmodel.html
QVariant, QVariantList, and/or QVariantMap - https://doc-snapshots.qt.io/qt6-dev/qvariant.html
QObject / Q_OBJECT - create a new QML component and register it
Q_GADGET - register your C++ structure (good for access the class in a signal, but, QML/JS cannot copying it into a Javascript object, only good for the duration of the signal)
QQmlListProperty is a list of QObjects. It requires the underlying QObject be registered, but, allows you to create/manage and walk through a list of the results. The list is owned, usually by another QObject. e.g. we could have a QmlFile object, then implement a QmlFileSystem object that can return a list of QmlFiles.
QAbstactListModel can be used in places where a model is required, e.g. ListView.
QVariant, QVariantList and QVariantMap is the simplest way of creating a Javascript object in QML. The advantage is you do not have to register anything, but, the disadvantage is you do not get intellisense.
QVariant getBooks() {
QVariantList books;
QVariantMap book1;
book1["title"] = QString("Lord of the Rings");
QVariantMap book2;
book2["title"] = QString("The Hobbit");
books.append(book2);
return books;
// results in QML/JS:
// [ { "title": "Lord of the Rings" },
// { "title" : "The Hobbit" } ]
}

How to properly link C++ model to QML front end

here is my setup:
First is the model class, this stores a pointer to the data of type CompetitionsList. We can see that it implements the necessary basic functions when deriving from QAbstractListModel, and it uses the QML_ELEMENT macro to expose the model to the QML type system:
class CompetitionsListModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(CompetitionsList* list READ list WRITE setList)
QML_ELEMENT
public:
explicit CompetitionsListModel(QObject *parent = nullptr);
enum {
NameRole = Qt::ItemDataRole(),
IdRole
};
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
CompetitionsList *list() const;
void setList(CompetitionsList *list);
virtual QHash<int, QByteArray> roleNames() const;
private:
CompetitionsList* m_list; \\ <--- ptr to data
};
Then we have the data class. This class is also a QML_ELEMENT, and it has a userid property. This is used to populate the QVector<listItem> by requesting data from a RESTful http server.
The two signals preItemAppended() and postItemAppended() are emitted when data is added to the QVector<listItem>. These signals are then connected to the Model as a way of notifying the model it must create a new row in the list. The important function here is populateCompetitions, which I explain below.
struct listItem {
QString competitionName;
QString competitionId;
};
class CompetitionsList : public QObject
{
Q_OBJECT
Q_PROPERTY(QString userId READ getUserId WRITE setUserId)
QML_ELEMENT
public:
explicit CompetitionsList(QObject *parent = nullptr);
void populateCompetitions(const QString& userId);
QVector<listItem> items();
QString getUserId() const;
void setUserId(const QString &value);
signals:
void preItemAppended();
void postItemAppended();
void errorPopulatingData();
private:
QVector<listItem> m_competitions;
QString userId;
};
The populateCompetitions function looks as so:
void CompetitionsList::populateCompetitions(const QString &userId)
{
qDebug() << "initialising data";
HttpClient* client = new HttpClient();
client->getUserCompetitions(userId);
connect(client, &HttpClient::getUserCompetitionsResult, [=](const QByteArray& reply){
QJsonDocument _reply = QJsonDocument::fromJson(reply);
if(_reply["data"].isNull()) {
emit errorPopulatingData();
}
else {
const QJsonArray competitions = _reply["data"]["competitions"].toArray();
for(const QJsonValue& competition : competitions) {
emit preItemAppended();
listItem item{competition["competition-name"].toString(), competition["competition-id"].toString()};
m_competitions.append(std::move(item));
emit postItemAppended();
}
}
});
}
It requests data from the database, and then stores it locally.
Then finally, the QML model which ties it all together:
ListView {
id: view
implicitHeight: 1920
implicitWidth: 1080
clip: true
model: CompetitionsListModel {
list: CompetitionsList {
id: compList
}
Component.onCompleted: { compList.userId = "6033f377257e8630ed13299e" } //<-- calls the populateCompetitions function
}
delegate: RowLayout {
Text {
text: qsTr(model.name + ":::" + model.id)
}
}
}
We can see that it has a ListView element, with a model of type CompetitionsListModel and a list of type CompetitionsList. Once the model component is created, we set the userId of the list, this in turn calls the populateCompetitions function which sets up the data for the model to use as seen above.
Unfortunately this doesnt anything when I run the code. Blank screen. Nada. I was wondering if anyone has an insight as to what might be causing this based on the code provided. Ive been at it for so long and it just inst being nice.
I think you need to call qmlRegisterType or UncreatableType on your CompetitionsListModel :
https://doc.qt.io/qt-5/qqmlengine.html#qmlRegisterType

Populate ComboBox from QList<QObject*>

I was following an example on web to populate combobox, but it did not wok for me and I do not know why!. I have two classes stock and DbCon, stock has three private fields along with public accessor and mutators. DbCon has a Q_Property and two public function, one returns a database connection and the other creates and returns stock list as a QList<QObject*>. In main.cpp I have created a contextual property named "data" to access DbCon from QML.
in main.qml I have
....
ComboBox{
model: data.stockModel
textRole: "code"
}
....
in main.cpp
DbCon db;
engine.rootContext()->setContextProperty("data", &db);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
in dbcon.h
class DbCon:public QObject
{
Q_OBJECT
Q_PROPERTY(QList<QObject*> stockModel READ stockModel)
public:
explicit DbCon(QObject *parent = 0);
QSqlDatabase db();
QList<QObject*> stockModel();
};
in implementation of QList<QObject*> stockModel() of dbcon.h
QList<QObject*> data;
....
while (query.next()) {
stock *s = new stock();
....
data.append(s);
}
return data;
and in stock.h
class stock : public QObject
{
Q_OBJECT
private:
QString m_name;
QString m_code;
int m_id;
public:
explicit stock(QObject *parent = 0);
QString name();
void setname(QString &name);
QString code();
void setcode(QString &code);
int id();
void setid(int &id);
};
When I run the application I get the following message in application output
QQmlExpression: Expression qrc:/main.qml:16:20 depends on non-NOTIFYable properties:
QQuickComboBox::data
and I do not get anything in combobox!
If I create another contextual property in main.cpp in this way
engine.rootContext()->setContextProperty("myModel", QVariant::fromValue(data));
and set myModel as model for combobox, it works fine. But I want to do it in this way because onCurrentIndexChanged I will call another function that returns another QList<QObject*> for a TableView of another qml file.
EDIT: Entrie qml
import QtQuick 2.5
import QtQuick.Window 2.2
import QtQuick.Controls 2.0
Window {
visible: true
width:600
height:600
property string contentSource
Column{
spacing:10
ComboBox{
model: data.stockModel
textRole: "code"
}
Loader {
id: content
width: parent.width
height:400
}
Row{
spacing:10
Button{
text: "Page1"
onClicked: content.source = "Page1.qml"
}
Button{
text: "Page2"
onClicked: content.source = "Page2.qml"
}
}
}
}
By changing data to dataStore in main.cpp and data.stockModel to dataStore.stockModel in main.qml I get following error
file:///C:/Qt/Qt5.7.0/5.7/mingw53_32/qml/QtQuick/Controls.2/ComboBox.qml:62:15: Unable to assign [undefined] to QString
You have two issues:
Your stockModel property should be NOTIFYable, which means that you should define the property with e.g. Q_PROPERTY(QList<QObject*> stockModel READ stockModel NOTIFY stockModelChanged) and provide a void stockModelChanged(const QList<QObject *> &) signal in the DbCon class.
stock::name() must be a property too, so you need to declare that with Q_PROPERTY(QString name READ name NOTIFY nameChanged) and provide a void nameChanged(const QString &) signal in the stock class as well.

QTableView - set the first column as "read only"

I have a QTableView based on a QStandardItemModel.
I want to set the first column as "read only" and all the others columns editable.
I'm not an expert of QT and OOP, i searched around the web and in the QT-documentation and I've understand that I need to reimplement the flags(const QModelIndex &index) function of my model, but I don't know how and where do the re-implementation.
Thanks in advance!
You should create a new class inherited from QStandardItemModel, reimplement method flags and use your new class instead of the standard one.
class MyModel : public QStandardItemModel
{
public:
virtual Qt::ItemFlags flags(const QModelIndex& index) const override
{
Qt::ItemFlags result = QStandardItemModel::flags(index);
if (index.column() == 0) //0 is the first column!
{
result &= ~Qt::ItemIsEditable;
}
return result;
}
}
Another way to do the same:
- create a new class inherited from QStandardItem,
- reimplement flags in the same way
- call QStandardItemModel::setItemPrototype with an instance of the new class
This way is a little bit more complicated because you will need to reimplement method QStandardItem::clone as well.

Sort a QAbstractListModel derived model by role in QML ListView

I've created a QAbstractListModel derived model based on an underlying QHash. Since I need to use the model in QML, I cannot make use of the sorting functionality Qt widgets and views have integrated.
I tried using a QSortFilterProxyModel but it doesn't seem to work with my model. Getting the model to properly work in QML wasn't tedious enough, and now I am stuck on sorting.
Any suggestions are appreciated.
Here is the model source:
typedef QHash<QString, uint> Data;
class NewModel : public QAbstractListModel {
Q_OBJECT
Q_PROPERTY(int count READ count NOTIFY countChanged)
public:
NewModel(QObject * parent = 0) : QAbstractListModel(parent) {}
enum Roles {WordRole = Qt::UserRole, CountRole};
QHash<int, QByteArray> roleNames() const {
QHash<int, QByteArray> roles;
roles[WordRole] = "word";
roles[CountRole] = "count";
return roles;
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const {
if (index.row() < 0 || index.row() >= m_data.size()) return QVariant();
Data::const_iterator iter = m_data.constBegin() + index.row();
switch (role) {
case WordRole:
return iter.key();
case CountRole:
return iter.value();
} return QVariant();
}
int rowCount(const QModelIndex &parent) const {
Q_UNUSED(parent)
return m_data.size();
}
int count() const { return m_data.size(); }
public slots:
void append(const QString &word) {
bool alreadyThere = m_data.contains(word);
if (alreadyThere) m_data[word]++;
else m_data.insert(word, 1);
Data::const_iterator iter = m_data.find(word);
uint position = delta(iter);
if (alreadyThere) {
QModelIndex index = createIndex(position, 0);
emit dataChanged(index, index);
} else {
beginInsertRows(QModelIndex(), position, position);
endInsertRows();
emit countChanged();
}
}
void prepend(const QString &word) {
if (m_data.contains(word)) m_data[word]++;
else m_data.insert(word, 1);
}
signals:
void countChanged();
private:
uint delta(Data::const_iterator i) {
uint d = 0;
while (i != m_data.constBegin()) { ++d; --i; }
return d;
}
Data m_data;
};
Here is "trying" to sort it:
NewModel model;
QAbstractItemModel * pm = qobject_cast<QAbstractItemModel *>(&model);
QSortFilterProxyModel proxy;
proxy.setSourceModel(pm);
proxy.setSortRole(NewModel::WordRole);
proxy.setDynamicSortFilter(true);
Alas, the proxy works as a model, but it doesn't sort the entries.
If you enable QSortFilterProxyModel::setDynamicSortFilter(true), you need to call QSortFilterProxyModel::sort(...) function once to let the proxy know which way to sort.
With that, any time the model is updated the proxy will sort everything again just automatically.
proxy.setDynamicSortFilter(true);
proxy.sort(0);
First of all, There's no need for qobject_cast<QAbstractItemModel *> downcasting -- the NewModel is a derived class of the QAbstractItemModel and the polymorphism principle says that you can use a subclass everywhere where a parent class is applicable.
Second, your prepend method does not use beginInsertRows and endInsertRows. That's a violation of the MVC API. You'll get data corruption in the attached views and proxy models if you use it this way.
Third, you haven't mentioned whether you're actually using your proxy model as the model for the attached view :).
Finally, you are using QHash as a backing store of your data with QHash::iterator for insertion. That's an itneresting solution, but something which just cannot work -- an insertion or removal can cause the hash table to grow/shrink, which means changing all data you publish via your model indexes. This is just not going to work. Don't use QHash when you need a stable order. The O(n) complexity of your delta method should be interpreted as a warning; this is a wrong approach.
Have a Look at https://github.com/oKcerG/SortFilterProxyModel. This project exposes the functionality of QSortFilterProxyModel nicely to QML. I used it in different projects and it junst worked. However if you want to implement your own solution it's something to get your ideas.

Resources