Reference material:
- Exposing Attributes of C++ Types to QML
- Overview - QML and C++ Integration
- Embedding C++ Objects into QML with Context Properties
Referring to View-Model model, QML is used as View, and object in C++ is used as Model to realize the separation of business logic and interface.
Exposing the properties of a single C++ class
In this way, the C++ class instance registered in the context can be accessed directly in QML, and it is registered to the whole QML (specifically to a QQuickView or engine). Take the example of customizing a Name class that includes a data attribute.
Class definition
Classes that need to be exposed to QML access need special definitions:
/*name.h*/ #include <QObject> class Name : public QObject //Inheritance from QObject { Q_OBJECT//QObject macro Q_PROPERTY(QString data READ data WRITE setData NOTIFY dataChanged) public: Name(QObject *parent = nullptr);//Default constructor Name(QString _name);//Constructor QString data() const;//READ interface void setData(const QString& _data);//WRITE interface signals: QString dataChanged();//NOTIFY signal (not required) private: QString m_data;//Private attributes };
New classes can be added by right-clicking the project - > New File - > C++ Class, inheriting from QObject and automatically adding files to the project.
Warning: Don't define classes directly in the cpp file, because Q_OBJECT macros need to be processed by moc, non.h files won't be processed by moc, and "unrecognized symbols" error occurs during compilation.
Q_PROPERTY(QString data READ data WRITE setData NOTIFY dataChanged)
This line of code defines the interface exposed to QML access. The object we provide here is a QString, the READ interface is a function named data, the WRITE interface is a function named setData, and the NOTIFY interface is used to bind notifications. Only when the NOTIFY interface is set, QML can automatically bind to the properties in C++. Synchronization. The naming method here is best unified with the default.
Class implementation
/*name.cpp*/ #include "name.h" Name::Name(QObject *parent) : QObject(parent) {//Default constructor } Name::Name(QString _data) : m_data(_data) {//Custom constructor to initialize private object m_data } QString Name::data() const { return m_data;//READ interface implementation, returning private objects } void Name::setData(const QString& _data) { if(_data != m_data){//WRITE interface implementation, update m_data and send out signals m_data = _data; emit dataChanged(); } }
In setData, it is necessary to determine whether the data is updated or not, and only when the data is really changed will the signal be sent, otherwise there is an infinite recursive risk.
Register to context
Then it instantiates a Name class and registers it in the context. Registration needs to be completed before reading the. qml file.
/*main.cpp*/ Name a_name("test"); QQmlApplicationEngine engine; QQmlContext* rootContex = engine.rootContext();//Get the root context of engine rootContex->setContextProperty("name", QVariant::fromValue(a_name));//register engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
Read in QML
QML can read the attributes returned by the interface, assign the attributes directly, and monitor the changes of attributes.
/*main.qml*/ Rectangle { Text { text:name.data //Equivalent to calling the name.data() interface Connections {//Establish a connection to NOTIFY interface target: name onDataChanged:{//Corresponding to NOTIFY interface console.log("data has beed changed!"); } } } Button { text:"changeData" onClicked: { name.data = "hasChanged!"; } } }
When the button is clicked, the text of Text will change, and the console output "data has beed changed!" closed-loop achieved.
Connections are used to listen for events, and the target for listening is the object that defines NOTIFY (note the hump naming of onDataChanged event handler and the naming of the original NOTIFY definition).
Use QList as a Model
A more common requirement is to expose the attributes of a set of objects and render them automatically through View in QML. For example, we put the Name class into a QList list and pass it as a Model to the ListView in QML.
/*main.cpp*/ QList<QObject*> NameList; NameList.append(new Name("name1")); NameList.append(new Name("name2"));//Add two Name objects to the array QQmlApplicationEngine engine; QQmlContext* rootContex = engine.rootContext();//Get the root context of engine rootContex->setContextProperty("nameList", QVariant::fromValue(NameList));//register engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
/*main.qml*/ ListView{ width: parent.width height: 300 model: nameList delegate: Rectangle { height: 30 width: parent.width color: "#000000" Text { text: model.data //Call the data() method for each object color: "#FFFFFF" Connections{ target: model //Listen on this object onDataChanged:{ console.log("changed!"); } } } } }
This gives you a list rendering. When the rendered list is registered in the context, the dynamic modification of the list item Name will be reflected in the interface, but the dynamic addition and deletion of array elements will not change (because QList itself does not have the NOTIFY interface).
Register type to QML context
The current Name class has fully implemented the interface registered in the context of QML, so it can be invoked in C++.
qmlRegisterType<Name>("com.myapp.name", 1, 0, "Name"); //Package name, large version number, small version number, type name
After registering, you can use Name class and its attributes directly after adding references to the QML file.
import com.myapp.name 1.0 Name { data: "Foo" }
Encapsulating a set of data exposed to QML
Unlike placing QList directly, dynamic binding list rendering can be achieved through more advanced encapsulation. Avoid using the QQmlListProperty method. Documentation is problematic and there is little relevant information.
Reference material
Linear tables are encapsulated as abstract list types
Naive C++ linear table types (arrays or Vector template classes, etc.) can be encapsulated as Models directly accessed by QML. Encapsulated classes can be inherited from QAbstractListModel or more complex QAbstractTable Model. The key is to implement several virtual functions as QML call interface after inheritance (complete virtual function table reference document):
/*Virtual functions that must be implemented*/ int rowCount(const QModelIndex &parent) const;//Number of rows returned QVariant data(const QModelIndex &index, int role) const;//Returns data requested by index and role QHash<int, QByteArray> roleNames() const;//Return data alias
More complex QML-oriented functions can be achieved by implementing more virtual functions.
Definition of abstract type
Suppose we have a QList in which each element is of type name, note that name has completed the encapsulation necessary for QML access. So a class encapsulated in a Name-type linear table that can be rendered in QML should look like this:
/*namelist.h*/ #include <QAbstractListModel> #include <QVariant> #include <QDebug> #include "name.h" class NameList : public QAbstractListModel { Q_OBJECT public: enum datatype { type1 = 0 }; NameList(QObject *parent); NameList(){ addName("test1"); addName("test2"); } /*The virtual functions that must be implemented are called by the QML engine*/ int rowCount(const QModelIndex &parent) const;//Number of rows returned QVariant data(const QModelIndex &index, int role) const;//Returns the requested data QHash<int, QByteArray> roleNames() const;//Return data alias /*Other interfaces*/ Q_INVOKABLE bool pushData(QString a_name); private: QList<Name*> _NameList;//Encapsulated arrays };
First, an enumeration type is named in the class, and each type corresponds to an attribute accessed in the data item. The Name class has only one data attribute, so only one type is defined.
Then it is rowCount (const QModel Index & parent), which is used by the QML engine to get the number of items in the list when querying the list.
QVariant data (const QModel Index & Index, int role) is the interface used by the QML engine to access each list item. When accessing, the index is indicated by index, and the role is indicated by the attributes (corresponding to the enumeration type data type).
QHash < int, QByteArray > roleNames () returns the alias of role (not particularly important for the time being).
Implementing abstract types
/*namelist.cpp*/ #include <QQmlListProperty> #include <QList> #include "namelist.h" NameList::NameList(QObject *parent) { } int NameList::rowCount(const QModelIndex &parent) const { return _NameList.count();//Return Private List Data Volume } QVariant NameList::data(const QModelIndex &index, int role) const { qDebug() << role; int row = index.row();//index contains attributes such as. row() and. count(). return QVariant::fromValue(_NameList.at(row));//Data items are wrapped as QVariant returns } QHash<int, QByteArray> NameList::roleNames() const { QHash<int, QByteArray> d; d[datatype::type1] = "Foo";//Alias tpye1 return d; } bool NameList::pushData(QString a_name) { Name* cache = new Name(a_name); beginInsertRows(QModelIndex(), _NameList.count(), _NameList.count()); _NameList.append(cache); endInsertRows(); return true; }
After the list is encapsulated, beginInsert Rows (QModel Index, int, int) is called when adding or deleting the list. The first parameter corresponds to the Model data, and the virtual rootItem of the Model is obtained by QModel Index (); the last two parameters represent the changed range of rows: for example, when three data are added to the second line, two parameters are used. They are ((2,4)). After the modification is completed, the name of endInsertRows() will be changed.
Register to context
/*main.cpp*/ NameList theList;//Instantiate a class QQmlApplicationEngine engine; QQmlContext* rootContex = engine.rootContext(); rootContex->setContextProperty("namelist", &theList);//Register to context engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
Read in QML
/*main.qml*/ ListView{ width: parent.width height: 300 model: namelist //Using abstract classes as model s delegate: Rectangle { height: 30 width: parent.width color: "#999999" Text { text: model.modelData.name //name is an attribute of each item color: "#FFFFFF" } Button { anchors.right: parent.right width: 50 height: 30 font.family: "FontAwesome" font.pixelSize: 24 text: "\uf019" onClicked: { model.modelData.name = textField.text;//You can modify attributes directly to item } } } }