QMLからC++のコンテナクラスを参照する
##要点
- QListに変換するとQML内で配列として扱える
- std::copyがとても便利
詳細は以下のURLから
Qt Project : Data Type Conversion Between QML and C++
###実行環境
Qt Creator 3.2.1
Qt 5.3.2
対象とするコンテナクラス
- std::array
- std::list
- std::vector
- QVector
まず, QML側にC++の拡張クラスを登録する。
#include <QtQml>
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QObject>
#include <containertranslate.h> //登録するC++クラスのインポート
int main(int argc, char *argv[]){
QGuiApplication app(argc,argv);
//QMLに登録するC++クラスの宣言
//(engine.loadの後に宣言すると怒られる)
qmlRegisterType<ContainerTranslate>("containertranslate",1,0,"ContainerTranslate");
QQmlApplicationEngine engine;
//読み込むQMLファイルの指定
engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
return app.exec();
}
main.cppで重要なのは
- 登録するクラスのインポート
- qmlRegisterType を使ったクラスの登録
の2つ. qmlResisterType は
qmlRegisterType<C++クラス名>("インポートファイル名",メジャーバージョン,マイナーバージョン,"QML内のエレメント宣言名")
といった具合に書く。
これをengine.load(...)の後に書くと, QML起動時に怒られるので必ず前に書こう。
次に, C++の拡張クラスを書く。
プロジェクトにファイルを追加するとき、"C++クラスを追加"を選択する。
このときに, "型情報" -> "QQuickItem" を選ぶことをお忘れなく。
これをしないとコンパイルエラーになって辛い思いをする。
これのクラスでは,
- メンバ変数の宣言
- QMLからのアクセッサ
を定義する。
// /////////////// ヘッダファイル //////////////////////
#ifndef CONTAINERTRANSLATE_H
#define CONTAINERTRANSLATE_H
// -------------------------------------
// include をお忘れなく
// -------------------------------------
#include <QQuickItem>
#include <QVector>
#include <vector>
#include <list>
#include <array>
// -------------------------------------
class ContainerTranslate : public QQuickItem
{
Q_OBJECT
public:
//コンストラクタ
explicit ContainerTranslate(QQuickItem *parent = 0);
// C++コンテナ -> QList変換し, QList をQMLに渡すメソッド
//Q_INVOKABLEをつけることでQMLから呼び出し可能にする
Q_INVOKABLE QList<qreal> getContainer();
//-----------メンバ変数定義------------------
private:
//検証に使うコンテナ達
std::vector<double> vec;
std::array<int,10> ary;
std::list<double> list;
QVector<double> qvec;
//みんな最終的にはこれに変換
QList<qreal> qlist;
};
#endif // CONTAINERTRANSLATE_H
続いてソースファイル
// /////////////// ソースファイル //////////////////////
#include "containertranslate.h"
ContainerTranslate::ContainerTranslate(QQuickItem *parent) :
QQuickItem(parent)
{
//コンテナを初期化
for(int i=0; i<10; i++){
vec.push_back(i);
ary[i] = i;
list.push_back(i);
qvec.push_back(i);
}
}
// C++標準コンテナをQListに変換
QList<qreal> ContainerTranslate::getContainer(){
qlist.clear(); //メモリ解放
//ここで std::** -> QListに変換している
std::copy(vec.begin(),vec.end(),std::back_inserter(qlist));
// std::copy(ary.begin(),ary.end(),std::back_inserter(qlist));
// std::copy(list.begin(),list.end(),std::back_inserter(qlist));
// std::copy(qvec.begin(),qvec.end(),std::back_inserter(qlist));
return qlist;
}
ここで登場する std::copy
が非常にcoolで, std::vector, list, array, QVector のすべてに対して同じ文法でコピーが行える.
x.begin()
, x.end()
でコンテナの先頭/末尾のイテレータを参照する.
std::back_inserter(Container &y)
は y の末尾に, 指定した x の要素を挿入する。
このコピーは参照渡しで動いているらしく, 複数回コピーを行っても非常に高速に動作する。
これで, QMLにコンテナを渡す機能は揃ったので, 次はQMLを書こう。
//----------------------main.qml------------------------
import QtQuick 2.0
import QtQuick.Window 2.0
import containertranslate 1.0 //ここでC++拡張クラスをインポート
Window{
visible: true
width: 100
height: 62
ContainerTranslate{id: container} //C++拡張クラスをエレメントとして宣言
//QMLが無事起動したときに送られるシグナル
Component.onCompleted: {
var ctnr = container.getContainer() //メソッド呼び出し
for(var i=0; i<10; i++)
console.debug(ctnr[i]) //コンテナの参照
}
}
QMLからC++拡張クラスのメソッドを呼ぶときに必要なことは2つ。
-
qmlRegisterType
で宣言したインポート名でクラスをインポート -
qmlRegisterType
で宣言したエレメント名でエレメントを作る。またidも必ず振る。
これさえやれば, QMLの他のエレメントメソッドを呼ぶようにC++拡張クラスのメソッドを呼び出すことができる。
上記QMLの実行結果は, ContainerTranlate::getContainer()
のどの変換式を使っても
qml:0
qml:1
qml:2
qml:3
qml:4
qml:5
qml:6
qml:7
qml:8
qml:9
と表示される。
QListをQMLへメソッドで渡す場合参照渡しで渡されるため, 高速に動作する。
上記の方法を用いれば, コピーから参照までを高速に, かつ, とても簡単に実装することができる。
QMLが認識できるQListの型は int, qreal(double), bool, QUrl, QString
と, 基本型を扱うならば, ほとんどがこの方法で間にあう。
もう生の配列には戻れないね .... orz