qml
Интеграция с C ++
Поиск…
Создание представления QtQuick из C ++
Можно создать представление QtQuick непосредственно из C ++ и показать его свойствам QML C ++. В приведенном ниже коде программа C ++ создает представление QtQuick и предоставляет QML высоту и ширину представления в качестве свойств.
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
}
}
Создание окна QtQuick из C ++
Начиная с Qt 5.1 и более поздних версий вы можете использовать QQmlApplicationEngine вместо QQuickView для загрузки и визуализации QML-скрипта.
С QQmlApplicationEngine вам нужно использовать тип окна QML в качестве корневого элемента.
Вы можете получить корневой контекст из механизма, в котором затем вы можете добавить глобальные свойства в контекст, который может получить доступ к движку при обработке 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
}
}
Создание простой модели для TreeView
Начиная с Qt 5.5 у нас есть новый замечательный TreeView , который мы все ждали. TreeView реализует древовидное представление элементов из модели. В общем, это похоже на другие представления QML - ListView или TableView . Но структура данных TreeView более сложна.
Данные в ListView или TableView представлены одномерным массивом узлов. В TreeView каждый узел может содержать собственный массив узлов. Поэтому, в отличие от других представлений TreeView для получения указанного узла, мы должны знать родительский узел, а не только строку или столбец элемента.
Другим важным отличием является то, что TreeView не поддерживает ListModel . Чтобы предоставить данные, мы должны подклассифицировать QAbstractItemModel . В Qt есть готовые к использованию классы моделей, такие как QFileSystemModel, который обеспечивает доступ к локальной файловой системе или QSqlTableModel, который обеспечивает доступ к базе данных.
В следующем примере мы создадим такую модель, полученную из QAbstractItemModel . Но чтобы сделать пример более реалистичным, я предлагаю сделать модель, подобную ListModel, но указанную для деревьев, чтобы мы могли добавлять узлы из QML. Необходимо уточнить, что сама модель не содержит каких-либо данных, а только обеспечивает доступ к ней. Поэтому предоставление и организация данных - это полностью наша ответственность.
Поскольку данные модели организованы в дереве, простейшая структура узла рассматривается следующим образом, в псевдокоде:
Node {
var data;
Node parent;
list<Node> children;
}
В C++ объявление узла должно быть следующим:
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;
};
Мы выводим наш класс из QObject, чтобы иметь возможность создать узел в QML . Все дочерние узлы будут добавлены в свойство nodes так что следующая 2 части кода будут одинаковыми:
TreeNode {
nodes:[
TreeNode {}
TreeNode {}
]
}
TreeNode {
TreeNode {}
TreeNode {}
}
См. Эту статью, чтобы узнать больше о свойствах по умолчанию.
Реализация класса узла:
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;
}
Чтобы вывести свойство списка в QML через QQmlListProperty, нам нужны следующие 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();
}
Теперь давайте объявим класс модели:
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();
};
Поскольку мы вывели класс модели из абстрактного QAbstractItemModel, мы должны переопределить следующую функцию: data () , flags () , index () , parent () , columnCount () и rowCount () . Чтобы наша модель могла работать с QML мы определяем roleNames () . Кроме того, как и в классе узлов, мы определяем свойство default, чтобы иметь возможность добавлять узлы в модель в QML . roles собственности проведет список имен ролей.
Реализация:
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;
}
В общем, этот код не сильно отличается от стандартной реализации, например пример Simple tree
Вместо определения ролей на C++ мы предлагаем способ сделать это из QML . События и методы TreeView в основном работают с QModelIndex . Я лично не вижу большого смысла передать это qml, поскольку единственное, что вы можете сделать с ним - это передать его обратно в модель.
В любом случае, наш класс предоставляет способ конвертировать индекс в узел и наоборот. Чтобы иметь возможность использовать наши классы в QML, нам необходимо зарегистрировать его:
qmlRegisterType<MyTreeModel>("qt.test", 1, 0, "TreeModel");
qmlRegisterType<MyTreeNode>("qt.test", 1, 0, "TreeElement");
И, конечно, пример того, как мы можем использовать нашу модель с TreeView в 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"
}
}
Дважды щелкните для добавления узла, нажмите и удерживайте для информации о узле.