qml
Integrazione con C ++
Ricerca…
Creazione di una vista QtQuick da C ++
È possibile creare una vista QtQuick direttamente da C ++ ed esporre a proprietà definite QML C ++. Nel codice sottostante il programma C ++ crea una vista QtQuick ed espone a QML l'altezza e la larghezza della vista come proprietà.
main.cpp
#include <QApplication>
#include <QQmlContext>
#include <QQuickView>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
// Creating the view and manually setting the QML file it should display
QQuickView view;
view.setSource(QStringLiteral("main.qml"));
// Retrieving the QML context. This context allows us to expose data to the QML components
QQmlContext* rootContext = view.rootContext();
// Creating 2 new properties: the width and height of the view
rootContext->setContextProperty("WINDOW_WIDTH", 640);
rootContext->setContextProperty("WINDOW_HEIGHT", 360);
// Let's display the view
view.show();
return app.exec();
}
main.qml
import QtQuick 2.0
Rectangle {
// We can now access the properties we defined from C++ from the whole QML file
width: WINDOW_WIDTH
height: WINDOW_HEIGHT
Text {
text: qsTr("Hello World")
anchors.centerIn: parent
}
}
Creazione di una finestra QtQuick da C ++
A partire da Qt 5.1 e versioni successive è possibile utilizzare QQmlApplicationEngine anziché QQuickView per caricare e eseguire il rendering di uno script QML.
Con QQmlApplicationEngine è necessario utilizzare un tipo di finestra QML come elemento principale.
È possibile ottenere il contesto di root dal motore in cui è possibile quindi aggiungere proprietà globali al contesto che può essere accessibile dal motore durante l'elaborazione degli script QML.
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
QQmlContext* rootContext = engine.rootContext();
rootContext->setContextProperty("WINDOW_WIDTH", 640);
rootContext->setContextProperty("WINDOW_HEIGHT", 360);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
main.qml
import QtQuick 2.5
import QtQuick.Window 2.2
Window { // Must be this type to be loaded by QQmlApplicationEngine.
visible: true
width: WINDOW_WIDTH //Accessing global context declared in C++
height: WINDOW_HEIGHT //Accessing global context declared in C++
title: qsTr("Hello World")
Component.onCompleted: {
// We can access global context from within JavaScript too.
console.debug( "Width: " + WINDOW_WIDTH )
console.debug( "Height: " + WINDOW_HEIGHT )
}
MouseArea {
anchors.fill: parent
onClicked: {
Qt.quit();
}
}
Text {
text: qsTr("Hello World")
anchors.centerIn: parent
}
}
Creazione di un modello semplice per TreeView
Da Qt 5.5 abbiamo un nuovo TreeView meraviglioso, un controllo che tutti stavamo aspettando. Un TreeView implementa una rappresentazione ad albero di elementi di un modello. In generale assomiglia ad altre viste QML - ListView o TableView . Ma la struttura dei dati di TreeView è più complessa.
Un dato in ListView o TableView è rappresentato da una serie monodimensionale di nodi. In TreeView ogni nodo può contenere il proprio array di nodi. Pertanto, a differenza delle altre viste in TreeView per ottenere il nodo specificato, è necessario conoscere il nodo padre, non solo la riga o la colonna dell'elemento.
Un'altra importante differenza è che TreeView non supporta ListModel . Per fornire un dato dobbiamo sottoclasse QAbstractItemModel . In Qt sono disponibili classi di modelli pronte all'uso come QFileSystemModel che fornisce accesso al file system locale o QSqlTableModel che fornisce l'accesso a un database.
Nell'esempio seguente creeremo tale modello derivato da QAbstractItemModel . Ma per rendere l'esempio più realistico suggerisco di rendere il modello come ListModel ma specificato per gli alberi in modo che possiamo aggiungere nodi da QML. È necessario chiarire che il modello stesso non contiene dati ma fornisce solo l'accesso ad esso. Quindi fornire e organizzare i dati è interamente nostra responsabilità.
Poiché i dati del modello sono organizzati in un albero, la struttura del nodo più semplice è vista come segue, in pseudo codice:
Node {
var data;
Node parent;
list<Node> children;
}
In C++ la dichiarazione del nodo dovrebbe essere la seguente:
class MyTreeNode : public QObject
{
Q_OBJECT
public:
Q_PROPERTY(QQmlListProperty<MyTreeNode> nodes READ nodes)
Q_CLASSINFO("DefaultProperty", "nodes")
MyTreeNode(QObject *parent = Q_NULLPTR);
void setParentNode(MyTreeNode *parent);
Q_INVOKABLE MyTreeNode *parentNode() const;
bool insertNode(MyTreeNode *node, int pos = (-1));
QQmlListProperty<MyTreeNode> nodes();
MyTreeNode *childNode(int index) const;
void clear();
Q_INVOKABLE int pos() const;
Q_INVOKABLE int count() const;
private:
QList<MyTreeNode *> m_nodes;
MyTreeNode *m_parentNode;
};
Deriviamo la nostra classe da QObject per poter creare un nodo in QML . Tutti i nodi figli verranno aggiunti alla proprietà dei nodes , quindi le 2 parti successive del codice sono le stesse:
TreeNode {
nodes:[
TreeNode {}
TreeNode {}
]
}
TreeNode {
TreeNode {}
TreeNode {}
}
Vedi questo articolo per saperne di più sulla proprietà di default.
Implementazione della classe Node:
MyTreeNode::MyTreeNode(QObject *parent) :
QObject(parent),
m_parentNode(nullptr) {}
void MyTreeNode::setParentNode(MyTreeNode *parent)
{
m_parentNode = parent;
}
MyTreeNode *MyTreeNode::parentNode() const
{
return m_parentNode;
}
QQmlListProperty<MyTreeNode> MyTreeNode::nodes()
{
QQmlListProperty<MyTreeNode> list(this,
0,
&append_element,
&count_element,
&at_element,
&clear_element);
return list;
}
MyTreeNode *MyTreeNode::childNode(int index) const
{
if(index < 0 || index >= m_nodes.length())
return nullptr;
return m_nodes.at(index);
}
void MyTreeNode::clear()
{
qDeleteAll(m_nodes);
m_nodes.clear();
}
bool MyTreeNode::insertNode(MyTreeNode *node, int pos)
{
if(pos > m_nodes.count())
return false;
if(pos < 0)
pos = m_nodes.count();
m_nodes.insert(pos, node);
return true;
}
int MyTreeNode::pos() const
{
MyTreeNode *parent = parentNode();
if(parent)
return parent->m_nodes.indexOf(const_cast<MyTreeNode *>(this));
return 0;
}
int MyTreeNode::count() const
{
return m_nodes.size();
}
MyTreeNode *MyTreeModel::getNodeByIndex(const QModelIndex &index)
{
if(!index.isValid())
return nullptr;
return static_cast<MyTreeNode *>(index.internalPointer());
}
QModelIndex MyTreeModel::getIndexByNode(MyTreeNode *node)
{
QVector<int> positions;
QModelIndex result;
if(node) {
do
{
int pos = node->pos();
positions.append(pos);
node = node->parentNode();
} while(node != nullptr);
for (int i = positions.size() - 2; i >= 0 ; i--)
{
result = index(positions[i], 0, result);
}
}
return result;
}
bool MyTreeModel::insertNode(MyTreeNode *childNode, const QModelIndex &parent, int pos)
{
MyTreeNode *parentElement = getNode(parent);
if(pos >= parentElement->count())
return false;
if(pos < 0)
pos = parentElement->count();
childNode->setParentNode(parentElement);
beginInsertRows(parent, pos, pos);
bool retValue = parentElement->insertNode(childNode, pos);
endInsertRows();
return retValue;
}
MyTreeNode *MyTreeModel::getNode(const QModelIndex &index) const
{
if(index.isValid())
return static_cast<MyTreeNode *>(index.internalPointer());
return m_rootNode;
}
Per esporre la proprietà di tipo elenco a QML tramite QQmlListProperty abbiamo bisogno della prossima funzione 4:
void append_element(QQmlListProperty<MyTreeNode> *property, MyTreeNode *value)
{
MyTreeNode *parent = (qobject_cast<MyTreeNode *>(property->object));
value->setParentNode(parent);
parent->insertNode(value, -1);
}
int count_element(QQmlListProperty<MyTreeNode> *property)
{
MyTreeNode *parent = (qobject_cast<MyTreeNode *>(property->object));
return parent->count();
}
MyTreeNode *at_element(QQmlListProperty<MyTreeNode> *property, int index)
{
MyTreeNode *parent = (qobject_cast<MyTreeNode *>(property->object));
if(index < 0 || index >= parent->count())
return nullptr;
return parent->childNode(index);
}
void clear_element(QQmlListProperty<MyTreeNode> *property)
{
MyTreeNode *parent = (qobject_cast<MyTreeNode *>(property->object));
parent->clear();
}
Ora dichiariamo la classe del modello:
class MyTreeModel : public QAbstractItemModel
{
Q_OBJECT
public:
Q_PROPERTY(QQmlListProperty<MyTreeNode> nodes READ nodes)
Q_PROPERTY(QVariantList roles READ roles WRITE setRoles NOTIFY rolesChanged)
Q_CLASSINFO("DefaultProperty", "nodes")
MyTreeModel(QObject *parent = Q_NULLPTR);
~MyTreeModel();
QHash<int, QByteArray> roleNames() const Q_DECL_OVERRIDE;
QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE;
Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE;
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
QModelIndex parent(const QModelIndex &index) const Q_DECL_OVERRIDE;
int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
QQmlListProperty<MyTreeNode> nodes();
QVariantList roles() const;
void setRoles(const QVariantList &roles);
Q_INVOKABLE MyTreeNode * getNodeByIndex(const QModelIndex &index);
Q_INVOKABLE QModelIndex getIndexByNode(MyTreeNode *node);
Q_INVOKABLE bool insertNode(MyTreeNode *childNode, const QModelIndex &parent = QModelIndex(), int pos = (-1));
protected:
MyTreeNode *getNode(const QModelIndex &index) const;
private:
MyTreeNode *m_rootNode;
QHash<int, QByteArray> m_roles;
signals:
void rolesChanged();
};
Poiché abbiamo ricavato la classe del modello da QAbstractItemModel astratto, dobbiamo ridefinire la funzione successiva: data () , flags () , index () , parent () , columnCount () e rowCount () . Per poter utilizzare il nostro modello con QML , definiamo roleNames () . Inoltre, così come nella classe nodo, definiamo la proprietà predefinita per poter aggiungere nodi al modello in QML . roles proprietà dei roles contiene un elenco di nomi di ruolo.
L'implemento:
MyTreeModel::MyTreeModel(QObject *parent) :
QAbstractItemModel(parent)
{
m_rootNode = new MyTreeNode(nullptr);
}
MyTreeModel::~MyTreeModel()
{
delete m_rootNode;
}
QHash<int, QByteArray> MyTreeModel::roleNames() const
{
return m_roles;
}
QVariant MyTreeModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
MyTreeNode *item = static_cast<MyTreeNode*>(index.internalPointer());
QByteArray roleName = m_roles[role];
QVariant name = item->property(roleName.data());
return name;
}
Qt::ItemFlags MyTreeModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return 0;
return QAbstractItemModel::flags(index);
}
QModelIndex MyTreeModel::index(int row, int column, const QModelIndex &parent) const
{
if (!hasIndex(row, column, parent))
return QModelIndex();
MyTreeNode *parentItem = getNode(parent);
MyTreeNode *childItem = parentItem->childNode(row);
if (childItem)
return createIndex(row, column, childItem);
else
return QModelIndex();
}
QModelIndex MyTreeModel::parent(const QModelIndex &index) const
{
if (!index.isValid())
return QModelIndex();
MyTreeNode *childItem = static_cast<MyTreeNode*>(index.internalPointer());
MyTreeNode *parentItem = static_cast<MyTreeNode *>(childItem->parentNode());
if (parentItem == m_rootNode)
return QModelIndex();
return createIndex(parentItem->pos(), 0, parentItem);
}
int MyTreeModel::rowCount(const QModelIndex &parent) const
{
if (parent.column() > 0)
return 0;
MyTreeNode *parentItem = getNode(parent);
return parentItem->count();
}
int MyTreeModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return 1;
}
QQmlListProperty<MyTreeNode> MyTreeModel::nodes()
{
return m_rootNode->nodes();
}
QVariantList MyTreeModel::roles() const
{
QVariantList list;
QHashIterator<int, QByteArray> i(m_roles);
while (i.hasNext()) {
i.next();
list.append(i.value());
}
return list;
}
void MyTreeModel::setRoles(const QVariantList &roles)
{
static int nextRole = Qt::UserRole + 1;
foreach(auto role, roles) {
m_roles.insert(nextRole, role.toByteArray());
nextRole ++;
}
}
MyTreeNode *MyTreeModel::getNodeByIndex(const QModelIndex &index)
{
if(!index.isValid())
return nullptr;
return static_cast<MyTreeNode *>(index.internalPointer());
}
QModelIndex MyTreeModel::getIndexByNode(MyTreeNode *node)
{
QVector<int> positions;
QModelIndex result;
if(node) {
do
{
int pos = node->pos();
positions.append(pos);
node = node->parentNode();
} while(node != nullptr);
for (int i = positions.size() - 2; i >= 0 ; i--)
{
result = index(positions[i], 0, result);
}
}
return result;
}
bool MyTreeModel::insertNode(MyTreeNode *childNode, const QModelIndex &parent, int pos)
{
MyTreeNode *parentElement = getNode(parent);
if(pos >= parentElement->count())
return false;
if(pos < 0)
pos = parentElement->count();
childNode->setParentNode(parentElement);
beginInsertRows(parent, pos, pos);
bool retValue = parentElement->insertNode(childNode, pos);
endInsertRows();
return retValue;
}
MyTreeNode *MyTreeModel::getNode(const QModelIndex &index) const
{
if(index.isValid())
return static_cast<MyTreeNode *>(index.internalPointer());
return m_rootNode;
}
In generale, questo codice non è molto diverso dall'implementazione standard, ad esempio l' albero semplice
Invece di definire i ruoli in C++ forniamo un modo per farlo da QML . Gli eventi e i metodi TreeView funzionano fondamentalmente con QModelIndex . Personalmente non ho molto senso passare questo a qml in quanto l'unica cosa che puoi fare è ridistribuirla alla modella.
Ad ogni modo, la nostra classe fornisce un modo per convertire l'indice in nodo e viceversa. Per poter utilizzare le nostre classi in QML è necessario registrarlo:
qmlRegisterType<MyTreeModel>("qt.test", 1, 0, "TreeModel");
qmlRegisterType<MyTreeNode>("qt.test", 1, 0, "TreeElement");
Infine, un esempio di come possiamo usare il nostro modello con TreeView in QML :
import QtQuick 2.7
import QtQuick.Window 2.2
import QtQuick.Dialogs 1.2
import qt.test 1.0
Window {
visible: true
width: 800
height: 800
title: qsTr("Tree example")
Component {
id: fakePlace
TreeElement {
property string name: getFakePlaceName()
property string population: getFakePopulation()
property string type: "Fake place"
function getFakePlaceName() {
var rez = "";
for(var i = 0;i < Math.round(3 + Math.random() * 7);i ++) {
rez += String.fromCharCode(97 + Math.round(Math.random() * 25));
}
return rez.charAt(0).toUpperCase() + rez.slice(1);
}
function getFakePopulation() {
var num = Math.round(Math.random() * 100000000);
num = num.toString().split("").reverse().join("");
num = num.replace(/(\d{3})/g, '$1,');
num = num.split("").reverse().join("");
return num[0] === ',' ? num.slice(1) : num;
}
}
}
TreeModel {
id: treemodel
roles: ["name","population"]
TreeElement {
property string name: "Asia"
property string population: "4,164,252,000"
property string type: "Continent"
TreeElement {
property string name: "China";
property string population: "1,343,239,923"
property string type: "Country"
TreeElement { property string name: "Shanghai"; property string population: "20,217,700"; property string type: "City" }
TreeElement { property string name: "Beijing"; property string population: "16,446,900"; property string type: "City" }
TreeElement { property string name: "Chongqing"; property string population: "11,871,200"; property string type: "City" }
}
TreeElement {
property string name: "India";
property string population: "1,210,193,422"
property string type: "Country"
TreeElement { property string name: "Mumbai"; property string population: "12,478,447"; property string type: "City" }
TreeElement { property string name: "Delhi"; property string population: "11,007,835"; property string type: "City" }
TreeElement { property string name: "Bengaluru"; property string population: "8,425,970"; property string type: "City" }
}
TreeElement {
property string name: "Indonesia";
property string population: "248,645,008"
property string type: "Country"
TreeElement {property string name: "Jakarta"; property string population: "9,588,198"; property string type: "City" }
TreeElement {property string name: "Surabaya"; property string population: "2,765,487"; property string type: "City" }
TreeElement {property string name: "Bandung"; property string population: "2,394,873"; property string type: "City" }
}
}
TreeElement { property string name: "Africa"; property string population: "1,022,234,000"; property string type: "Continent" }
TreeElement { property string name: "North America"; property string population: "542,056,000"; property string type: "Continent" }
TreeElement { property string name: "South America"; property string population: "392,555,000"; property string type: "Continent" }
TreeElement { property string name: "Antarctica"; property string population: "4,490"; property string type: "Continent" }
TreeElement { property string name: "Europe"; property string population: "738,199,000"; property string type: "Continent" }
TreeElement { property string name: "Australia"; property string population: "29,127,000"; property string type: "Continent" }
}
TreeView {
anchors.fill: parent
model: treemodel
TableViewColumn {
title: "Name"
role: "name"
width: 200
}
TableViewColumn {
title: "Population"
role: "population"
width: 200
}
onDoubleClicked: {
var element = fakePlace.createObject(treemodel);
treemodel.insertNode(element, index, -1);
}
onPressAndHold: {
var element = treemodel.getNodeByIndex(index);
messageDialog.text = element.type + ": " + element.name + "\nPopulation: " + element.population;
messageDialog.open();
}
}
MessageDialog {
id: messageDialog
title: "Info"
}
}
Fare doppio clic per aggiungere un nodo, tenere premuto per informazioni sul nodo.