Recherche…


Créer une vue QtQuick à partir de C ++

Il est possible de créer une vue QtQuick directement à partir de C ++ et d'exposer aux propriétés définies par QML C ++. Dans le code ci-dessous, le programme C ++ crée une vue QtQuick et expose à QML la hauteur et la largeur de la vue en tant que propriétés.

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
    }
}

Créer une fenêtre QtQuick à partir de C ++

A partir de Qt 5.1 et versions ultérieures, vous pouvez utiliser QQmlApplicationEngine au lieu de QQuickView pour charger et afficher un script QML.

Avec QQmlApplicationEngine, vous devez utiliser un type de fenêtre QML comme élément racine.

Vous pouvez obtenir le contexte racine du moteur où vous pouvez ensuite ajouter des propriétés globales au contexte auquel le moteur peut accéder lors du traitement des scripts 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
    }
}

Créer un modèle simple pour TreeView

Depuis Qt 5.5, nous avons un nouveau TreeView merveilleux, un contrôle que nous attendions tous. Un TreeView implémente une représentation arborescente des éléments d'un modèle. En général, cela ressemble à d'autres vues QML - ListView ou TableView . Mais la structure de données de TreeView est plus complexe.

Une donnée dans ListView ou TableView est représentée par un tableau de nœuds à une dimension. Dans TreeView, chaque nœud peut contenir son propre tableau de nœuds. Par conséquent, contrairement aux autres vues de TreeView pour obtenir le nœud spécifié, nous devons connaître le nœud parent, pas seulement la ligne ou la colonne de l'élément.

Une autre différence majeure est que TreeView ne supporte pas ListModel . Pour fournir une donnée, nous devons sous- classer QAbstractItemModel . Dans Qt, il existe des classes de modèle prêtes à l'emploi comme QFileSystemModel qui donne accès au système de fichiers local ou QSqlTableModel qui donne accès à une base de données.

Dans l'exemple suivant, nous allons créer un tel modèle dérivé de QAbstractItemModel . Mais pour rendre l'exemple plus réaliste, je suggère de rendre le modèle comme ListModel mais spécifié pour les arbres afin que nous puissions ajouter des nœuds à partir de QML. Il est nécessaire de préciser que le modèle lui-même ne contient aucune donnée, mais seulement d'y accéder. La fourniture et l'organisation de données relèvent donc entièrement de notre responsabilité.

Comme les données de modèle sont organisées dans un arbre, la structure de noeud la plus simple est vue comme suit, en pseudo-code:

Node {
    var data;
    Node parent;
    list<Node> children;
}

En C++ la déclaration de noeud doit être la suivante:

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;
};

Nous dérivons notre classe de QObject pour pouvoir créer un nœud dans QML . Tous les nœuds enfants seront ajoutés à la propriété nodes afin que la partie suivante du code soit identique:

TreeNode {
    nodes:[
        TreeNode {}
        TreeNode {}
    ]
}

TreeNode {
    TreeNode {}
    TreeNode {}
}

Voir cet article pour en savoir plus sur la propriété par défaut.

Implémentation de classe de noeud:

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;
}

Pour exposer une propriété de type liste à QML via QQmlListProperty, nous avons besoin des 4 prochaines fonctions:

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();
}

Maintenant, déclarons la classe de modèle:

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();
};

Puisque nous avons dérivé la classe de modèle de l'abstraction QAbstractItemModel, nous devons redéfinir la fonction suivante: data () , flags () , index () , parent () , columnCount () et rowCount () . Pour que notre modèle puisse fonctionner avec QML nous définissons roleNames () . En outre, ainsi que dans la classe de nœud, nous définissons la propriété default pour pouvoir ajouter des nœuds au modèle dans QML . roles propriété roles contiendra une liste de noms de rôles.

La mise en oeuvre:

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;
}

En général, ce code n'est pas très différent de l'implémentation standard, par exemple Exemple d' arborescence simple

Au lieu de définir des rôles en C++ nous fournissons un moyen de le faire à partir de QML . Les événements et méthodes TreeView fonctionnent essentiellement avec QModelIndex . Personnellement, je ne vois pas beaucoup de sens à transmettre cela à qml, car la seule chose que vous pouvez faire est de le renvoyer au modèle.

Quoi qu’il en soit, notre classe offre un moyen de convertir l’index en nœud et vice versa. Pour pouvoir utiliser nos classes dans QML, nous devons l’enregistrer:

qmlRegisterType<MyTreeModel>("qt.test", 1, 0, "TreeModel");
qmlRegisterType<MyTreeNode>("qt.test", 1, 0, "TreeElement");

Et enfin, en exemple, comment utiliser notre modèle avec TreeView dans 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"
      }
}

Double-cliquez pour ajouter un nœud, maintenez la touche enfoncée pour obtenir des informations sur le nœud.



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow