デフォルトプロジェクトで生成されるQMLはWindowsコンポーネントを使ったデスクトップスタイルのプログラムとなっています。しかしながら、スマートフォンやタブレットのアプリでは基本的にウィンドウの概念がなく、単一のビューで成り立っています。

Qt QuickにはQQuickWindowから派生したQQuickViewが提供されており、このクラスを使うことで、それらの環境に適したアプリを開発することができます。

QQuickViewを使ったひな形の例です。QQuickViewの派生クラス・MainViewを作成し、main()でMainViewの作成と表示を行います。MainViewのコンストラクタでは、リサイズモードに「QQuickView::SizeRootObjectToView(ビューのサイズに応じて最上位QMLコンポーネントサイズを自動で変更)」を指定してから、QMLファイルを呼び出しています。

スマートフォンアプリでは原則としてウィンドウのサイズはOSが決めるので、アプリ側で初期サイズを指定する必要はありませんが、デスクトップOSとのクロスプラットフォーム開発をするのであれば、ビューを表示する前にwidthとheightの値を指定しておきます。
#include <QGuiApplication>
#include <QQmlApplicationEngine>

#include "mainview.h"

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);

    // メインビューの作成と表示
    MainView mv;
    mv.show();

    return app.exec();
}
import QtQuick 2.9

// Windowの配置や、Itemのサイズ指定は不要
Item {
}
#include <QQuickView>

class MainView : public QQuickView
{
    Q_OBJECT
public:
    MainView();
};

MainView::MainView()
{   
    // デスクトップOSならウィンドウサイズを指定
#if defined(Q_OS_LINUX) || (defined(Q_OS_WIN) && !defined(Q_OS_WINRT))
    setWidth(640);
    setHeight(480);
#endif

    // 最上位コンポーネントのサイズをビューに合わせる
    this->setResizeMode(QQuickView::SizeRootObjectToView);
    // 最初に適用するQMLをリソースから読み込む
    this->setSource(QUrl("qrc:/main.qml"));
}
せっかくなので、ビュースタイルアプリ特有のイベントを処理してみましょう。ウィンドウのないOSでは、アプリを切り替えるなどでバックグラウンドに移動したときにサスペンドイベントが発生します。Qt 5.2以降では「QGuiApplication::applicationState」にアプリが休止状態であるかどうかが記録されており、applicationStateChangedシグナルを受け取ることで、アプリの休止や復旧時の処理を行うことができます。
class MainView : public QQuickView
{
    Q_OBJECT
public slots:
    void onAppStateChanged(Qt::ApplicationState s);
};

MainView::MainView()
{   
    // グローバルなQGuiApplicationオブジェクトはqAppとして呼び出せる
    connect(qApp, SIGNAL(applicationStateChanged(Qt::ApplicationState)),
        this, SLOT(onAppStateChanged(Qt::ApplicationState)));

    // アプリが終了されるときに呼び出される
    connect(qApp, SIGNAL(aboutToQuit()), SLOT(onQuit()));
}

void MainView::onAppStateChanged(Qt::ApplicationState s)
{
    if (s == Qt::ApplicationSuspended){
        // サスペンド状態に入るので、設定データの保存などを行う
    } else if (s == Qt::ApplicationActive){
        // サスペンドから復旧したので、接続の再開などを行う
    }
}

void MainView::onQuit()
{
    // OSによってはアプリ終了時にサスペンドに移行しないため、直接呼び出している
    onAppStateChanged(Qt::ApplicationSuspended);
}
 | 2018年11月6日

QQuickItemを継承したC++クラスを作ることで、UI関連の制御も行えるQMLコンポーネントを実装することができます。また、このQQuickItemを継承したQQuickPaintedItemを継承すればQPainterによるオーナードローも実装できます。

QQuickPaintedItemを使ったプログラム例を紹介します。前回のQObject継承クラスと同様、Q_OBJECTを用意するまでは同じですが、コンストラクタでは親アイテムの情報がQMLから届いているので、それを親クラスにちゃんと転送するようにしておきましょう。
#include <QQuickPaintedItem>

class CustomItem : public QQuickPaintedItem
{
    Q_OBJECT
public:
    CustomItem(QQuickItem *parent = nullptr);
}

CustomItem::CustomItem(QQuickItem *parent) : QQuickPaintedItem(parent)
{
}
QQuickItemのイベントをC++で受け取るには2つのパターンがあります。まずはQQuickItemで呼び出される関数(QtドキュメントのProtected Functionsに記載されている関数)をオーバーライドする方法です。これらの関数はオーバーライドするだけでイベントを処理することができます。
class CustomItem : public QQuickPaintedItem
{
    Q_OBJECT
protected:
    virtual void mousePressEvent(QMouseEvent *ev);
    virtual void paint(QPainter *p);
}

void CustomItem::mousePressEvent(QMouseEvent *ev)
{
    Q_UNUSED(ev);
}

void CustomItem::paint(QPainter *p)
{
    p->drawRect(QRectF(0,0,10,10));
}
もうひとつはQMLのシグナル(QtドキュメントのSignalsに記載されている関数)同士を紐付ける(接続する)方法で、プロパティの変更などはこれによって受け取ることになります。

シグナルは「public slots:」で宣言した関数をQObject::connectで対象のQObject派生クラスと紐付けます。引数は「送信元のオブジェクト」「送信元のシグナル関数」「送信先の(シグナルを受け取る)オブジェクト」「送信先のスロット関数」の順で指定します。SIGNALとSLOTマクロを使用するとより簡潔に記述できます。逆に紐付けを解除したい場合はQObject::disconnectを使用します。
class CustomItem : public QQuickPaintedItem
{
    Q_OBJECT
public slots:
    void onSizeChanged();
};

CustomItem::CustomItem(QQuickItem *parent) : QQuickPaintedItem(parent)
{
    // widthChangedシグナルを自クラスのonSizeChangedに紐付ける
    connect(this, &QQuickItem::widthChanged, this, &CustomItem::onSizeChanged);
    // マクロを使ったheightChangedの接続方法
    connect(this, SIGNAL(heightChanged()), SLOT(onSizeChanged()));
}

void CustomItem::onSizeChanged()
{
    // 親アイテムの中央に移動
    setX((parentItem()->width() - width()) / 2.0);
    setY((parentItem()->height() - height()) / 2.0);
}
タイマーなどの派生クラスを作らないオブジェクトでもシグナルとスロットが重要になります。こちらはタイマー制御を行うためのクラス・QTimerを使った例です。

class CustomItem : public QQuickPaintedItem
{
    Q_OBJECT
private:
    QTimer *timer;
}

CustomItem::CustomItem(QQuickItem *parent) : QQuickPaintedItem(parent)
{
    // 親に関連付けると、親が破棄されるとこのオブジェクトも破棄されるのでdelete処理は不要
    timer = new QTimer(this);
    // タイマーイベントごとにQQuickPaintedItem::updateを実行してアイテムを再描画させる
    connect(timer, SIGNAL(timeout()), this, SLOT(update()));
    // 15ms間隔でタイマーを開始
    timer->start(15);
    // 手動でタイマーを破棄する例
    // timer->deleteLater();
}
QtではC++と同様、新しいオブジェクトを作成するのに「new」を使いますが、これを「delete」で直接削除するとQMLから急にアクセスできなくなることによる不具合の原因となります。

Qtの場合、親オブジェクトを指定して作成したオブジェクトは、親オブジェクトが破棄されたときにQtが適切に処理してくれます。Qtによって破棄されると「destroyed」シグナルが発生するので、QMLが関与する最終処理はデストラクタではなく、こちらに記述するのが望ましいでしょう。なお、手動でQtオブジェクトを削除したいときは「deleteLater()」関数を呼び出して、破棄処理をQtに任せます。

以上を踏まえた最終的なプログラム例がこちらとなります。プロジェクトを実行すると、4つの四角形が順番に明滅し、また、この領域をクリックすると四角形が赤色に切り替わります。
#ifndef CUSTOMITEM_H
#define CUSTOMITEM_H

#include <QQuickPaintedItem>

class CustomItem : public QQuickPaintedItem
{
    Q_OBJECT
    Q_PROPERTY(QColor color MEMBER color)

private:
    QTimer *timer;
    QColor color;
    int frame = 0, cframe = 0;

public:
    CustomItem(QQuickItem *parent = nullptr);

protected:
    virtual void mousePressEvent(QMouseEvent *ev);
    virtual void paint(QPainter *p);

public slots:
    void onSizeChanged();
};

#endif
#include "customitem.h"
#include <QTimer>
#include <QPainter>

CustomItem::CustomItem(QQuickItem *parent) : QQuickPaintedItem(parent)
{
    connect(this, &QQuickItem::widthChanged, this, &CustomItem::onSizeChanged);
    connect(this, SIGNAL(heightChanged()), SLOT(onSizeChanged()));

    // 左ボタンの入力を受け付け、mousePressイベントを発生させる
    setAcceptedMouseButtons(Qt::LeftButton);

    timer = new QTimer(this);
    connect(timer, SIGNAL(timeout()), this, SLOT(update()));
    timer->start(15);
}

void CustomItem::mousePressEvent(QMouseEvent *ev)
{
    Q_UNUSED(ev);
    color = Qt::GlobalColor::red;
    // rgb形式の例
    //color = QColor(255, 0, 0);
}

void CustomItem::paint(QPainter *p)
{
    frame += 2;
    qreal w = width(), h = height();

    qreal r = qMin(w - 1, h - 1) / 2.0;
    qreal s = r / 20.0;
    const qreal ps = 1.0;
    qreal sz = r - 2 * ps - s;

    // アンチエイリアスを有効にして描画
    p->setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);

    // colorの色を持つペンを作成
    QPen pen(color);
    pen.setWidthF(ps);
    p->setPen(pen);

    QPointF cp = QPointF(w / 2, h / 2);

    const int all = 120;
    const qreal start = 30, end = 90;
    QRectF drc;
    for(int i = 0; i < 4; i++){
        qreal pos = (frame - i * 30) % all;
        qreal opq = (pos - end) / (start - end);
        if(opq < 0 || opq > 1) opq = 0;
        switch(i){
        case 3: drc = QRectF(cp.x() - s - sz, cp.y() + s, sz, sz); break;
        case 2: drc = QRectF(cp.x() + s, cp.y() + s, sz, sz); break;
        case 1: drc = QRectF(cp.x() + s, cp.y() - s - sz, sz, sz); break;
        case 0: drc = QRectF(cp.x() -s - sz, cp.y() - s - sz, sz, sz); break;
        }

        color.setAlpha(static_cast<int>(opq * 255.0));
        p->setBrush(QBrush(color));

        p->drawRect(drc);
    }

    color.setAlpha(255);
}

void CustomItem::onSizeChanged()
{
    setX((parentItem()->width() - width()) / 2.0);
    setY((parentItem()->height() - height()) / 2.0);
}
Qtプロジェクトファイルのダウンロード
 | 2018年11月6日

C++ではQMLはQObjectをベースとした拡張クラスとみなされます。QObjectを継承した独自のクラスをQMLエンジンに登録することで、QMLコードからそのC++クラスを呼び出すことができます。

通常はmain()関数内のqmlファイルを読み込む前に「qmlRegisterType<C++クラス>("URL", "メジャーバージョン", "マイナーバージョン, "QML内で使用する名称");」という形で登録します。URLとバージョンはもっぱらimport文で用いられ、URLの決まりは特にないものの、javaパッケージと同じ方式で宣言するのが定石となっています。

ためしに、HelloクラスをQMLコンポーネントとして扱えるようにしてみましょう。まずはmain.cppにHelloクラスを宣言しているヘッダファイルをインクルードし、qmlRegisterTypeでQMLエンジンに登録するようにプログラムします。
#include <QGuiApplication>
#include <QQmlApplicationEngine>

#include "hello.h"

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    qmlRegisterType<Hello>("com.tnksoft", 1, 0, "Hello");

    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    if (engine.rootObjects().isEmpty())
        return -1;

    return app.exec();
}
QObjectを継承したHelloクラスの作成です。QObjectをインクルードし、クラス宣言の初めにQML関連の宣言をマクロ化した「Q_OBJECT」をセミコロンなしで記述します。
#ifndef HELLO_H
#define HELLO_H

#include <QObject>

class Hello : public QObject
{
    Q_OBJECT

public:
    Hello();
    virtual ~Hello();
}

#endif
このクラスにプロパティを実装してみます。プロパティは「Q_PROPERTY」マクロを使用し、「Q_PROPERTY([プロパティの型] [プロパティ名] READ [getter関数] WRITE [setter関数] NOTIFY [signal関数])」のような形式で、カンマでは区切らずに宣言します。READの項目は必須ですが、読み出し専用プロパティであればWRITEやNOTIFYは省略しても構いません。

型の宣言例
QML型プロパティ型
整数qint
浮動小数(real)qreal
浮動小数(double)qdouble
Booleanbool
文字列QString
日付QDate

クラスの宣言部にgetter関数とsetter関数の宣言を追加します。Qtでは関数の宣言にCamel記法(先頭は小文字)を使うのが通例です。NOTIFYで記述した関数名は「signal:」として宣言し、関数の実体は作成しない点に気を付けましょう。C++で記述したシグナルは、qmakeツールによって、コンパイル先のOSに応じたイベント関数として自動で生成されます。
class Hello : public QObject
{
    Q_OBJECT
    // textという名称のプロパティを宣言
    Q_PROPERTY(QString text READ getText WRITE setText NOTIFY textChanged)

private:
    // メンバ変数
    //Qt 5.1以降ではこのようにMEMBERを用意するとgetter/setter関数が自動で作られる
    //Q_PROPERTY(QString text MEMBER _text NOTIFY textChanged)
    QString _text;

signals:
  // シグナルは宣言のみで実装はしない
    void textChanged();

public:
    Hello();
    virtual ~Hello();

    // READで宣言した関数を実装
    QString getText();
    // WRITEで宣言した関数を実装
    // 数値型であればconstや&は必要ない
    void setText(const QString &text);
};
続いて関数の実装です。コード例のように、シグナルを発生させるには、呼び出したい箇所で「emit [シグナル関数];」を呼び出します。
#include "hello.h"
#include <QDebug>

Hello::Hello() : QObject()
{
}

Hello::~Hello()
{
}

void Hello::setText(const QString &text)
{
    // もしtextを使用しないのなら「Q_UNUSED(text)」をコードの先頭に記述する
    _text = text;
    // QMLへtext変更のシグナルを送信する
    emit textChanged();
}

QString Hello::getText()
{
    return _text;
}
QMLからアクセス可能な関数を実装するには、対象の関数の前に「Q_INVOKABLE」を付加します。
class Hello : public QObject
{
public:
    Q_INVOKABLE int execCalc(int a, int b);
};

int Hello::execCalc(int a, int b)
{
    return a + b;
}
QMLでの実装例です。「qmlRegisterType」で登録したURLとバージョンの組み合わせをimportして、Helloをアイテムの一部として追加します。
import QtQuick 2.9
import QtQuick.Window 2.2
import com.tnksoft 1.0

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello Cpp")

    Hello{
        id:h
        onTextChanged: {
            // emit textChanged();によって実行される
            console.log("Text has updated.")
        }
    }

    Component.onCompleted: {
        // Q_INVOKABLEの関数を実行
        t.text = h.execCalc(10, 5);
    }

    Text{
        id:t
    }

    TextInput{
        id:i
        y:10; width:100
        onTextChanged: {
            // プロパティへの設置により、Hello::setText()が呼び出される
            h.text = this.text;
        }
    }
}
Qtプロジェクトファイルのダウンロード
 | 2018年11月5日

ポップアップメニューを実装するにはQtQuick.Controlsの「Menu」コンポーネントを使用します。コンテキストメニュー、いわゆる右クリックメニューはMouseAreaで調べますが、タッチデバイスでは右クリックによる画面の長押しとして扱うことになるので、この場合はクリックに使用したデバイスがマウス以外であるかを調べる必要があります。

メニューを表示するには「popup()」を実行します。第1引数にxの値(real値)を指定した場合、第2引数はy値とみなされます。第1引数に表示対象のアイテムを指定すると、そのアイテムに対する相対位置(第2引数,第3引数)=(x,y)に表示されます。引数を省略することもできますが、UWPなどの環境によっては正しい位置に表示されないので、座標値は指定したほうがよいでしょう。

メニューの分割線は「MenuSeparator」を使用します。サブメニューを用意したいのであれば、Menuを入れ子にして、titleプロパティーにサブメニューの親として表示したいテキストを指定します。

メニューが選択されたかどうかは「triggered」シグナルで検知します。
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 2.4

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Context Menu")

    Label{
        id:lbl
        x:0; y:0
    }

    MouseArea {
        anchors.fill: parent
        acceptedButtons: Qt.LeftButton | Qt.RightButton

        // 右クリックでポップアップメニューを表示
        onClicked:{
            if (mouse.button === Qt.RightButton){
                // UWPなど一部の環境では、引数を省略すると正しい場所に表示されない
                menu.popup(mouse.x, mouse.y);
            }
        }
        // マウス以外のデバイスによる長押しでも同様に表示
        onPressAndHold: if (mouse.source === Qt.MouseEventNotSynthesized) menu.popup(mouse.x, mouse.y)

        Menu {
            id: menu

            MenuItem {
                text: "Menu1"
                onTriggered: {
                    lbl.text = "Menu1 clicked.";
                    m2.checked = !m2.checked;
                }
            }
            MenuItem {
                // チェック付きメニュー
                id: m2
                text: "Menu2"
                checkable: true
            }

            MenuSeparator {
            }

            // サブメニュー
            Menu {
                title: "SubMenu1"
                MenuItem { text: "Menu2-1" }
                MenuItem { text: "Menu2-2" }
            }
        }
    }
}
 | 2018年11月5日

QMLへDropAreaを組み込むことでドラッグ&ドロップを実装することができます。

[テキストやファイルのドロップ]
テキストやファイルなどのOSからのドロップであれば、DropAreaのenteredとdroppedシグナルでデータの受け入れを制御できます。

onEnteredではdragプロパティ(DragEventタイプ)が使えます。対象がファイルなら「hasUrl」を、テキストなら「hasText」の値を調べ、条件に一致しないようであれば「drag.accepted = false;」とすることでデータのドロップを禁止します。

onDroppedではdropプロパティが使用できます。こちらも実体はDragEventなので「drop.text」などからデータを改めて取得します。
Rectangle {
    x: 100
    y: 210
    width: 100
    height: 100
    color: "blue"

    DropArea {
        id: dta
        anchors.fill: parent

        onEntered:{
            // drag.accepted = false;
        }

        onDropped:{
            if (drop.hasUrls){
                lbl.text = drop.urls.toString();
            }else if (drop.hasText) {
                lbl.text = drop.text;
            }
        }
    }
}

[QMLアイテムのドラッグ&ドロップ]
QMLアイテムをドラッグできるようにするには、アイテムの「Drag.active」プロパティをtrueにします。Dragを有効にしてもマウス操作が使えるようになるわけではないので、MouseAreaを追加してドラッグさせたいアイテムと連動させる必要があります。

DropAreaで受け入れることのできるQMLアイテムを限定したいのであれば「keys」プロパティを使用します。DropAreaの「keys」配列内の文字列のいずれかと、ドラッグしたアイテムに含まれる「Drag.keys」配列内のいずれかが一致すると、ドロップ操作が受け入れられるようになります。


これらを踏まえたコード例がこちらになります。緑と青色の矩形は両方ともDropAreaですが、緑色にはkeysが指定されているので、ウィンドウにある赤色の矩形しかドラッグ&ドロップを受け付けません。
import QtQuick 2.9
import QtQuick.Window 2.2

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Drag and Drop")

    Text{
         id: lbl
         wrapMode: Text.Wrap
         width: parent.width
         text: "test"
    }

    DropArea {
        id: da
        x: 100
        y: 100
        width: 100
        height: 100

        Rectangle {
            id: rc
            anchors.fill: parent
            color: "green"
        }

        // ドラッグキーに「test」を持つものだけを受け入れる
        keys:["test"]

        states: [
            // ドラッグ&ドロップできるアイテムがあれば見た目を変えるようにする例
            State{
                when: da.containsDrag
                PropertyChanges {
                    target: rc
                    color: "yellow"
                }
            }
        ]
    }

    Rectangle {
        x: 100
        y: 210
        width: 100
        height: 100
        color: "blue"

        DropArea {
            id: dta
            anchors.fill: parent

            onEntered:{
                // drag.accepted = false;
            }

            onDropped:{
                if (drop.hasUrls){
                    lbl.text = drop.urls.toString();
                }else if (drop.hasText) {
                    lbl.text = drop.text;
                }
            }
        }
    }

    Rectangle {
        x: 210
        y: 100
        width: 20
        height: 20
        color: "red"

        Drag.active: true
        Drag.keys: ["test"]

        MouseArea {
            anchors.fill: parent
            // ドラッグを連動させたいアイテム
            drag.target: parent
            // ドラッグできるx方向の最小値と最大値
            drag.minimumX: 100
            drag.maximumX: 500
            // QMLアイテムのドロップはMouseArea側で実装する必要がある
            onReleased: {
                if(parent.Drag.target === da){
                    lbl.text = "Dropped";
                }
            }
        }
    }
}
 | 2018年11月2日

QMLの基本コンポーネント(QtQuick 2.0)にはTimerが含まれています。これ自体が描画されることはありませんが、QMLコンポーネントに含めることで一定時間間隔での関数の実行が行えます。
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 1.0

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Timer")

    Timer{
        id:tm
        // triggeredを呼び出す間隔(ミリ秒)
        interval: 200
        // イベントを繰り返し発生させるか
        repeat: true
        // タイマーの呼び出し
        onTriggered:{
            rc.x++; // 200msごとに矩形を1ピクセル右へ移動
        }
    }

    Rectangle{
        id:rc
        color: "blue"
        x:0; y:0; width:100; height: 100;
    }

    Button{
        text: "Start"
        onClicked: {
            // タイマーが実行中でなければstart()で開始
            // 実行中ならstop()で停止
            if(tm.running == false){
                tm.start();
                this.text = "Stop";
            }else {
                tm.stop();
                this.text = "Start";
            }
        }
    }
}
 | 2018年11月1日

既存のQMLに、新しいQMLを動的に追加し、常に使用しないコンポーネントを後から追加できるようにさせることで、初期動作やメモリーリソースの効率化を図ることができます。

「Qt.createComponent」でコンポーネントを作成し、「Component.createObject」でQMLの実体を対象のアイテム内に追加するのが基本的な流れになります。

プロジェクトに任意のQt Quickファイルを追加し、後から追加したいQMLのデータを記入します。
Qt.createComponentの第1引数には読み込みたいQMLファイル、第2引数には読み込みモードを指定します。

読み込みモードには、同期読み込み「Component.PreferSynchronous」と、非同期読み込み「Component.Asynchronous」のいずれかを選択でき、引数を省略した際は同期モードでの読み込みとなります。

createComponentでは作成されたComponentオブジェクトが返されます。コンポーネントの作成が完了すると、このComponentのstatusプロパティが「Component.Ready」になるので、それを確認したら、「Component.createObject([作成する親アイテム],[生成時に追加するプロパティ])」で実体を作成します。

まずは同期モードでの作成例です。ボタンを押すたびにウィンドウ内にRect.qmlで定義したアイテムが次々と追加されていきます。
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 1.0

Window {
    id: mainwin
    visible: true
    width: 640
    height: 480
    title: qsTr("Dynamical Object")

    Button{
        text: "Generate"
        property Component c: null

        function intRandom(mn, mx){
            // mnからmxの範囲でランダムな整数を生成
            return Math.floor((Math.random() * ((mx + 1) - mn)) + mn);
        }

        onClicked: {
            // qmlファイルよりコンポーネントの作成
            if(!c) c = Qt.createComponent("Rect.qml");

            // コンポーネントが正しく取得できたら、mainwinにオブジェクトを生成
            if(c.status == Component.Ready){
                var x = intRandom(0, mainwin.width - 100);
                var y = intRandom(0, mainwin.height - 100);
                var i = c.createObject(mainwin, {x:x, y:y});
            }
        }
    }
}
続いて非同期モードでの作成例です。Windowコンポーネントの生成が完了すると「Component.onCompleted」が呼び出されるので、ここでRect.qmlの非同期読み込みを実行しています。イベント関数(スロット)への登録は「connect」メソッドを使用します。
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 1.0

Window {
    id: mainwin
    visible: true
    width: 640
    height: 480
    title: qsTr("Dynamical Object")

    property Component c: null

    function intRandom(mn, mx){
        return Math.floor((Math.random() * ((mx + 1) - mn)) + mn);
    }

    function onComponentStateChanged(){
        if(c.status == Component.Ready){
            btn.enabled = true;
        }
    }

    Component.onCompleted:{
        c = Qt.createComponent("Rect.qml", Component.Asynchronous);
        c.statusChanged.connect(onComponentStateChanged);
        // これは間違い
        // c.onStateChanged = onComponentStateChanged;
    }

    Button{
        text: "Generate"
        id: btn
        // 非同期読み込みが成功するまでボタンを無効に
        enabled: false

        onClicked: {
            var x = intRandom(0, mainwin.width - 100);
            var y = intRandom(0, mainwin.height - 100);
            var i = c.createObject(mainwin, {x:x, y:y});
        }
    }
}
参照先のファイルにはURLも指定できますが、ほとんどのアプリストアではアプリに初めから含まれていないスクリプトを実行することを禁止しています。不要なトラブルを避けるためにも、アプリ外からのQMLの読み込みは避けたほうがよいでしょう。

動的に作成したオブジェクトは「destroy()」で破棄することができます。

「Qt.createQmlObject()」を使うと、文字列からQMLオブジェクトを作成できます。第1引数にはQML構文を、第2引数には追加する親アイテムを指定します。
try{
    item = Qt.createQmlObject(
        'import QtQuick 2.0; Rectangle ' +
        '{color: "red"; width: 20; height: 20}',
        mainwin);
}catch(e){
    // 作成に失敗したときは例外が発生し、qmlErros配列に情報が格納される
    console.log(e.qmlErros[0].message);
}
 | 2018年11月1日

QMLではComboBoxやListViewなどのリスト形式のアイテムは「ListModel」と呼ばれるデータをもとにして作成されます。

ListModeへはListElementアイテムにKey-Value形式のデータを追加していきます。通常は「text」のキーを持つデータが反映されますが、独自のテンプレートを用意することで、キーをもとに任意のデータを表示させることが可能です。

独自のキーを定義した場合は、表示のためのQMLを自前で作ります。コンボボックスでは、ComboBoxStyleというアイテム(import QtQuick.Controls.Styles 1.0)のlabelプロパティにQMLレイアウトを割り当てると反映されます。

このサンプルでは、コンボボックスのラベルに内部のアイテムを横に並べることのできる「Row」へ「Image」と「Text」を追加し、モデルに格納されている対象のデータを描画するようにしています。
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 1.0
import QtQuick.Controls.Styles 1.0

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Image ComboBox")

    ComboBox{
        id: cb
        width: 100
        style: ComboBoxStyle{
            id: cbstyle
            label: Row{
                // はみ出た部分は描画させない
                clip:true
                Image{
                    id:color
                    // ソースとして選択中インデックスのimgデータを使用
                    source:cbdata.get(cb.currentIndex).img
                    height:parent.height
                    width:this.height
                    // アイテム枠内に画像を縮小させる
                    fillMode: Image.PreserveAspectFit
                }
                Text{
                    id:text
                    text:cbdata.get(cb.currentIndex).text
                    anchors.verticalCenter: parent.verticalCenter
                }
            }
        }
        model: ListModel {
            id: cbdata
            ListElement { text: "Glape"; img:"imgs/icon1.png" }
            ListElement { text: "Banana"; img:"imgs/icon2.png" }
            ListElement { text: "Apple"; img:"imgs/icon3.png" }
        }
    }
}
なお、画像ファイルなどをリソースとしてプロジェクトに登録するには、プロジェクトのメニューより「既存のファイル/ディレクトリを追加」より、ファイルやフォルダを選択することで行えます。リソース内部で階層化した場合は、リソースの参照時も階層を明記します。
リストビューで独自のレイアウトを用いるにはdelegateプロパティにComponentアイテムを割り当てます。コンボボックスとは異なり、クリックして選択したり、アイテムのハイライト表示などの選択状態の処理はQMLで都度作成する必要があります。
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 1.0

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("ListView")

    ListView{
        id: lv
        model: lm
        width: 100
        height: 100
        clip: true

        // リスト描画の定義
        delegate: user

        // ハイライトの描画定義(水色の矩形を左右いっぱいに広げて配置)
        highlight: Rectangle {
            color: "#cde7ff"
            anchors.left:parent.left
            anchors.right:parent.right
        }
    }

    Component{
        // リストアイテムの定義
        id:user
        Item{
            width: lv.width
            height: 25

            // アイテムをクリックできるようにする
            MouseArea{
                anchors.fill: parent
                onClicked: {
                    // アイテムが保有するインデックスをリストに適用させることで、選択状態にさせる
                    lv.currentIndex = index;
                }
            }

            Row{
                anchors.fill: parent
                Text{
                    id:f_name
                    text: name
                }

                Image{
                    id:f_icon
                    source: src
                    height:parent.height
                    fillMode: Image.PreserveAspectFit
                }

                CheckBox{
                    id:f_check
                    checked: chk
                }
            }
        }
    }

    // リストアイテムの初期定義
    ListModel{
        id:lm
        ListElement{
            name: "User1"
            src: "icon1.png"
            chk: false
        }
        ListElement{
            name: "User2"
            src: "icon2.png"
            chk: true
        }
        ListElement{
            name: "User3"
            src: "icon3.png"
            chk: false
        }
    }

    // アイテムを動的に追加する例
    Button{
        anchors.top: lv.bottom
        text: "Add"
        onClicked: {
            lv.model.append({ name:"new", src:"icon1.png", chk:true });
        }
    }
}
 | 2018年10月31日

QMLアイテムにアニメーション効果を適用したい場合は、transitionsプロパティを使用します。

Transitionを定義する前に、アニメーション後の状態を示すコードを用意します。この状態はstatesプロパティーで記述し、State.nameで指定した名称をアイテムのstateプロパティに代入すると、State内の定義がアイテムに反映されるようになります。アイテムのステータスの変更を検知したいのであれば「onStateChanged」関数を組み込みます。

Stateに定義できものとして、アンカーを変更する「AnchorChanges」、アイテムの親子関係を変更する「ParentChange」、アイテムのプロパティを変更する「PropertyChanges」が挙げられます。

Stateによるプロパティ値の変更例を紹介します。このQMLコードでは、ボタンを押すと「lblColor.state」が「red」に設定されます。これによりラベルのstatesにある「red」を名称に持つデータが適用されるため、lblColorをターゲットにcolorプロパティが"red"になります。なお、「target:this」と記述してしまうとStateアイテムを参照することになるため、この方法は使えません。
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 1.0
import QtQuick.Layouts 1.0

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("State test")

    ColumnLayout{
        Label{
            id: lblColor
            text: "Hello!"
            state: "black"
            states:[
                State{
                    name:"black"
                    PropertyChanges{
                        target:lblColor
                        color:"black"
                    }
                },
                State{
                    name:"red"
                    PropertyChanges{
                        target:lblColor
                        color:"red"
                    }
                }
            ]
        }

        Button{
            text: "Change"
            onClicked: {
                lblColor.state = "red";
            }
        }
    }
}
Stateにある「when」プロパティがtrueであるときは、そのステータスが自動で適用されます。つまり、whenに条件式を割り当てれば、シグナルを調べる必要がなくなるというわけです。
MouseArea{
    id:ma
    states:[
        State{
            // このマウスエリアが押されると、rcというアイテムが表示される
            when: ma.pressed
            PropertyChanges {
                target: rc
                visible: true
            }
        }
    ]
}
アイテムのtransitionsプロパティ内に記述することで、stateをキーにしたアニメーションを実行できます。

この例では、ラベルのステートが"black"から"red"に切り替わった時に、ColorAnimationが実行されるので、1秒かけて文字の色が黒から赤に変わっていきます。
Label{
    id: lblColor
    text: "Hello!"
    state: "black"
    states:[
        State{
            name:"black"
            PropertyChanges{
                target:lblColor
                color:"black"
            }
        },
        State{
            name:"red"
            PropertyChanges{
                target:lblColor
                color:"red"
            }
        }
    ]
    transitions: [
        Transition {
            from: "black"
            to: "red"
            ColorAnimation {
                target:lblColor
                duration: 1000
            }
        }
    ]
}
transitionsは配列なので、状態値の変化の数だけTransitionを追加できます。また、fromを明示しない場合は、変化前のstateはすべての値が対象になります。toも同様です。

次にアイテムのプロパティ値を徐々に変化させることでアニメーションさせる「PropertyAnimation」について解説します。このデータの「property」に指定したプロパティ文字列(クォーテーションで囲まないとそのプロパティの数値が参照されてしまうため動作しない)を「from(未指定の場合は現時点のアイテムが持つ値)」から「to」へとdurationミリ秒をかけて変化していきます。easingプロパティによるイージングも実装できます。
ColumnLayout{
    Label{
        id: lblColor
        /* 省略 */
        transitions: [
            Transition {
                from: "black"
                to: "red"
                ColorAnimation {
                    target:lblColor
                    duration: 1000
                }
            },
            Transition{
                to: "move"
                PropertyAnimation {
                    target: lblColor
                    // 複数のターゲットを指定したい場合の例
                    // targets:[lblColor, lblExtra]
                    easing.type: Easing.InOutQuad
                    to: 200
                    duration: 2000
                    property: "x"
                    // 複数のプロパティを同時に変更したい場合の例
                    // properties: "x,y"
                    to: 200
                }
            }
        ]
    }

    Button{
        text: "Color"
        onClicked: {
            lblColor.state = "red";
        }
    }

    Button{
        text: "Move"
        onClicked: {
            lblColor.state = "move";
        }
    }
}
このコードでは「Move」ボタンを押すとラベルのステートが「move」になり、かつfromステートが未指定なので、ラベルの状態(stateがblackかredであるか)に関わらず、ラベルが右方向に200ピクセル移動します。

対象のプロパティが数値型であれば、PropertyAnimationの代わりにNumberAnimationを使うことができます。基本的な動作は変わりありませんが、数値に特化した分、処理は高速です。
Transition{
    to: "move"
    NumberAnimation {
        target: lblColor
        property: "x"
        easing.type: Easing.InOutQuad
        to: 200
        duration: 2000
    }
}
 | 2018年10月30日

QMLではプロパティバインドを使わなくても、LayoutコンポーネントやAnchorプロパティを使うことで、ウィンドウのサイズに応じた位置調整をおこなえます。

まずは独自のコンポーネントを作ってみましょう。プロジェクト一覧の右クリックメニューより「Add new...」を選択し、ファイル種別から「QMLファイル(Qt Quick 2)」を指定し、QMLファイルをプロジェクトに追加します。プロジェクトに埋め込むのであれば、ファイル名がそのままオリジナルのコンポーネント名になります。今回のサンプルでは「SelectSample.qml」にしています。
「QtQuick.Layout」では、ColumnLayoutやGridLayoutといった、コンポーネントを並べて表示するのに便利なパネルが用意されていますので、これをインポートします。
import QtQuick 2.0
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.1

ColumnLayout{
    ComboBox {
        id: cmbSelect
    }

    RowLayout{
        TextField {
            id: txtAdd
        }

        Button {
            id: btnAdd
        }
    }
}
このコードではColumnLayoutにアイテムを入れ子にすることで、上半分がcmbSelectの領域に、下半分がRowLayoutの領域になります。さらにRowLayoutにアイテムを挿入すると、左下がtxtAdd、右下がbtnAddの領域に割り当てられます。

各種レイアウトコンポーネントの中にあるアイテムにはLayoutプロパティーが追加され、このLayoutプロパティを定義することで、アイテムのサイズを決めることができます。逆にプロパティバインディングによるサイズ指定は機能しませんので気を付けましょう。

[Layoutプロパティの設定例]
Layout.fillWidth:trueアイテムが幅いっぱいに表示される
Layout.preferredWidth:parent.widthアイテムの幅が親の幅と同じになる
Layout.alignment:Qt.AlignHCenterアイテムがパネルの左右中央に配置される
Layout.maximumHeight:10アイテムの最大幅を10pxにする
Layout.rightMargin:5右側のマージンを5pxにする

アプリのプログラムで特に指定しなければ、DPIスケーリングは自動で行われるので、通常は高解像度でのピクセル値を気にすることはないでしょう。なお、「Screen.pixelDensity」には1mmあたりのピクセル数が格納されているので、これを使うことで解像度に依存しないサイズの指定ができます。
import QtQuick 2.0
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.1

ColumnLayout{
    ComboBox {
        id: cmbSelect
        Layout.fillWidth:true
    }

    RowLayout{
        TextField {
            id: txtAdd
            Layout.fillWidth:true
        }

        Button {
            id: btnAdd
            text: qsTr("Add")
        }
    }
}
コンボボックスのリストアイテムの初期値は「model」プロパティに格納します。単純なテキスト選択方式であれば「model:["item1","item2","item3"]」のように、配列を入れるだけで構いません。動的に処理したい場合は、画像+テキストのように凝ったレイアウトを表現したいのであれば、modelプロパティにListModelを指定します。ListModelの詳細な使い方は別の章で紹介します。
ComboBox {
    id: cmbSelect
    model: ListModel {
        ListElement { text:"item1"}
        ListElement { text:"item2"}
        ListElement { text:"item3"}
    }
}
ボタンを押したら、コンボボックスに入力済みのテキストを追加するようにしてみましょう。ボタンに「onClicked」関数を用意し、その関数内で「model.append」を呼び、JSON形式でデータを追加します。また、「console.log」ではJavaScriptと同様に、コンソールデバッガに文字列を出力できます。
Button {
    id: btnAdd
    text: qsTr("Add")
    onClicked: {
        var text = txtAdd.text;
        console.log(text);
        if(text) cmbSelect.model.append({text:text});
    }
}
QMLファイルに記述したアイテムへは、そのファイル内にあるアイテムからしかアクセスできません。つまり、このままでは、ほかのQMLファイルからはコンボボックスの値を取得することはできないということになります。

これを解決するのがプロパティエイリアスです。一番上位にあるアイテムに「property alias [外部に公開する名称]:[転送先の内部プロパティ]」を追加することで、代わりとなるプロパティ名を外部に公開することができます。propertyの前に「readonly」を付け加えると、そのプロパティは読み込み専用になります。

このコードでは「SelectSample.selectedText」によって、コンボボックスにある現在のテキストを取得できるようになっています。
import QtQuick 2.0
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.1

ColumnLayout{
    property alias selectedText : cmbSelect.currentText

    ComboBox {
        id: cmbSelect
        Layout.fillWidth:true
        model: ListModel {
            ListElement { text:"item1"}
            ListElement { text:"item2"}
            ListElement { text:"item3"}
        }
    }

    RowLayout{
        TextField {
            id: txtAdd
            Layout.fillWidth:true
        }

        Button {
            id: btnAdd
            text: qsTr("Add")
            onClicked: {
                var text = txtAdd.text;
                console.log(text);
                if(text) cmbSelect.model.append({text:text});
            }
        }
    }
}
signal構文を使うと、コンポーネントに独自のシグナルを用意することができます。例えば、ItemEx.qmlから生成されたコンポーネントに「onClosing」を用意しておくと、「closing()」を実行したのを機に、「onClosing」が呼び出されます。一般的なメソッドとは異なり、closing()に何も関連付けられていない状態でclosing()を実行しても、何も起きないだけで、エラーも発生しません。
//ItemEx.qml
Item{
    id: i1
    signal closing()
    
    // このメソッドを呼び出すとclosingシグナルが発生する
    function close(){
        i1.closing();
    }
}

// main.qml
ItemEx{
    onClosing:{
    }
}
オリジナルコンポーネントをメインウィンドウに追加します。Windowコンポーネントにはレイアウトの概念がないので、位置の調整は「anchors」プロパティで行います。anchorsと連動するのはプロパティ自体であり、その数値ではない点に注意しましょう。
import QtQuick 2.9
import QtQuick.Window 2.2

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    SelectSample {
        id:ss
        // 親の上下中央。「anchors.fill:parent」なら親の枠いっぱいに表示される
        anchors.centerIn: parent
    }

    Text{
        // SelectSampleのselectedText(=cmbSelect.currentText)とテキストを紐づけ
        text:ss.selectedText

        // 親の底辺と連動。マージンは5px
        anchors.bottom:parent.bottom
        anchors.bottomMargin:5

        // 親の左右中央に配置
        anchors.horizontalCenter: parent.horizontalCenter
    }
}
サンプルプロジェクトのダウンロード
 | 2018年10月29日