QML モジュールを書く

CMake QML Module API を使ってQML モジュールを宣言します:

  • qmldir ファイルと *.qmltypes ファイルを生成する。
  • QML_ELEMENT でアノテーションされた C++ 型を登録する。
  • QML ファイルと C++ ベースの型を同じモジュール内で結合する。
  • すべてのQMLファイルに対してqmlcachegenを呼び出す。
  • QMLファイルのコンパイル済みバージョンをモジュール内で使用する。
  • 物理ファイルシステムとリソースファイルシステムの両方にモジュールを提供する。
  • バッキング・ライブラリとオプションのプラグインを作成する。実行時にプラグインをロードしないように、バッキング・ライブラリをアプリケーションにリンクする。

上記のすべてのアクションは、個別に設定することもできます。詳細はCMake QML Module API を参照してください。

複数の QML モジュールを一つのバイナリに入れる

同じバイナリに複数の QML モジュールを追加することができます。各モジュールに CMake ターゲットを定義し、実行ファイルにリンクしてください。余分なターゲットがすべて静的ライブラリであれば、複数の QML モジュールを含んだバイナリができあがります。つまり、このようなアプリケーションを作ることができるのです:

myProject
    | - CMakeLists.txt
    | - main.cpp
    | - main.qml
    | - onething.h
    | - onething.cpp
    | - ExtraModule
        | - CMakeLists.txt
        | - Extra.qml
        | - extrathing.h
        | - extrathing.cpp

まず、main.qmlにExtra.qmlのインスタンスが含まれているとします:

import ExtraModule
Extra { ... }

Extraモジュールは静的ライブラリでなければならないので、メインプログラムにリンクすることができます。したがって、ExtraModule/CMakeLists.txtにそのように記述してください:

# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

qt_add_library(extra_module STATIC)
qt_add_qml_module(extra_module
    URI "ExtraModule"
    VERSION 1.0
    QML_FILES
        Extra.qml
    SOURCES
        extrathing.cpp extrathing.h
    RESOURCE_PREFIX /
)

これは2つのターゲットを生成します:バッキング・ライブラリ用のextra_module 、プラグイン用のextra_moduleplugin 。プラグインもスタティック・ライブラリであるため、実行時にロードすることはできません。

myProject/CMakeLists.txtでは、main.qmlとonething.hで宣言された型が属するQMLモジュールを指定する必要があります:

qt_add_executable(main_program main.cpp)

qt_add_qml_module(main_program
    VERSION 1.0
    URI myProject
    QML_FILES
        main.qml
    SOURCES
        onething.cpp onething.h

)

そこから、追加モジュールのサブディレクトリを追加します:

add_subdirectory(ExtraModule)

追加モジュールのリンクが正しく動作するようにするためには、以下のことが必要です:

  • 追加モジュールでシンボルを定義する。
  • メインプログラムからシンボルへの参照を作成する。

QMLプラグインには、この目的に使用できるシンボルが含まれています。このシンボルへの参照を作成するには、Q_IMPORT_QML_PLUGIN マクロを使用します。main.cppに以下のコードを追加します:

#include <QtQml/QQmlExtensionPlugin>
Q_IMPORT_QML_PLUGIN(ExtraModulePlugin)

ExtraModulePlugin は生成されたプラグインクラスの名前です。これは、モジュールURIに を付加したものです。次に、メイン・プログラムのCMakeLists.txtに、バッキング・ライブラリではなくプラグインをリンクする:Plugin

target_link_libraries(main_program PRIVATE extra_moduleplugin)

バージョン

QML には、コンポーネントやモジュールにバージョンを割り当てる複雑なシステムがあります。ほとんどの場合、このシステムを無視する必要があります:

  1. import文にバージョンを追加しない
  2. qt_add_qml_moduleでバージョンを指定しない。
  3. QML_ADDED_IN_VERSIONQT_QML_SOURCE_VERSIONS を使わない。
  4. Q_REVISIONREVISION() 属性を使用しない。Q_PROPERTY
  5. 修飾されていないアクセスを避ける
  6. import名前空間を多用する

バージョン管理は言語の外部で行うのが理想的です。例えば、QMLモジュールのインポートパスを別々に管理することができます。あるいは、オペレーティングシステムが提供するバージョン管理機構を使って、QML モジュールを含むパッケージをインストールしたり、アンインストールしたりすることもできます。

場合によっては、Qt の QML モジュールは、インポートされたバージョンによって異なる動作をすることがあります。特に、QML コンポーネントにプロパティが追加された場合、コードに同名の別のプロパティへの非限定的なアクセスが含まれていると、コードが壊れてしまいます。次の例では、topLeftRadius プロパティが Qt 6.7 で追加されたため、Qt のバージョンによってコードの動作が異なります:

import QtQuick

Item {
    // property you want to use
    property real topLeftRadius: 24

    Rectangle {

        // correct for Qt version < 6.7 but uses Rectangle's topLeftRadius in 6.7
        objectName: "top left radius:" + topLeftRadius
    }
}

qmllintを使えば、このような問題を見つけることができます。次の例では、実際に指定したプロパティに安全な方法でアクセスします:

import QtQuick

Item {
    id: root

    // property you want to use
    property real topLeftRadius: 24

    Rectangle {

        // never mixes up topLeftRadius with unrelated Rectangle's topLeftRadius
        objectName: "top left radius:" + root.topLeftRadius
    }
}

また、QtQuick の特定のバージョンをインポートすることで、非互換性を回避することもできます:

// make sure Rectangle has no topLeftRadius property
import QtQuick 6.6

Item {
    property real topLeftRadius: 24
    Rectangle {
        objectName: "top left radius:" + topLeftRadius
    }
}

バージョン管理によって解決されるもう1つの問題は、異なるモジュールによってインポートされたQMLコンポーネントが、互いにシャドウ(影)になってしまうことです。次の例では、MyModule が新しいバージョンでRectangle という名前のコンポーネントを導入した場合、この文書で作成されたRectangle はもうQQuickRectangle ではなく、MyModule によって導入された新しいRectangle になってしまいます。

import QtQuick
import MyModule

Rectangle {
    // MyModule's Rectangle, not QtQuick's
}

シャドーイングを回避する良い方法は、QtQuick および/またはMyModule を、以下のように型名前空間にインポートすることである:

import QtQuick as QQ
import MyModule as MM

QQ.Rectangle {
   // QtQuick's Rectangle
}

あるいは、MyModule を固定バージョンでインポートし、QML_ADDED_IN_VERSION またはQT_QML_SOURCE_VERSIONS を介して新しいコンポーネントが正しいバージョンタグを受け取るようにすれば、シャドウイングも回避できます:

import QtQuick 6.6

// Types introduced after 1.0 are not available, like Rectangle for example
import MyModule 1.0

Rectangle {
    // QtQuick's Rectangle
}

これが機能するためには、MyModule でバージョンを使用する必要があります。注意すべき点がいくつかあります。

バージョンを追加する場合は、あらゆる場所に追加してください。

qt_add_qml_moduleVERSION 属性を追加する必要があります。バージョンはモジュールが提供する最新バージョンでなければなりません。同じメジャーバージョンの古いマイナーバージョンは自動的に登録されます。古いメジャーバージョンについては、以下を参照してください。

x.0x は現在のメジャーバージョン)で紹介されていないすべての型には、QML_ADDED_IN_VERSION またはQT_QML_SOURCE_VERSIONSを追加してください。

バージョンタグを追加し忘れると、そのコンポーネントはすべてのバージョンで利用できるようになり、バージョン管理が効かなくなります。

しかし、QMLで定義されたプロパティやメソッド、シグナルにバージョンを追加する方法はありません。QML文書をバージョン管理する唯一の方法は、変更ごとにQT_QML_SOURCE_VERSIONSを分けて新しい文書を追加することです。

バージョンは遷移的ではありません

あなたのモジュールA のコンポーネントが別のモジュールB をインポートし、そのモジュールの型をルート要素としてインスタンス化した場合、B のインポートバージョンは、A のどのバージョンがユーザーによってインポートされたとしても、結果として得られるコンポーネントから利用可能なプロパティに関連します。

モジュールA のバージョン2.6 を持つファイルTypeFromA.qml を考えてみましょう:

import B 2.7

// Exposes TypeFromB 2.7, no matter what version of A is imported
TypeFromB { }

ここで、TypeFromA のユーザーを考えてみましょう:

import A 2.6

// This is TypeFromB 2.7.
TypeFromA { }

このユーザーは、バージョン2.6 を見ることを期待しますが、実際には基本クラスTypeFromB のバージョン2.7 が表示されます。

したがって、安全のためには、QMLファイルを複製し、自分でプロパティを追加するときだけでなく、インポートするモジュールのバージョンを上げるときにも新しいバージョンを与える必要があります。

修飾されたアクセスはバージョニングに影響しない

バージョン管理は、ある型のメンバや型そのものへの非限定的なアクセスにのみ影響します。topLeftRadius の例では、this.topLeftRadius と記述すると、Qt 6.7 を使用している場合、import QtQuick 6.6 と記述しても、プロパティは解決されます。

バージョンとリビジョン

QML_ADDED_IN_VERSION と、Q_REVISIONQ_PROPERTY の 2 つの引数を持つバリアントREVISION() では、QMetaMethod::revisionQMetaProperty::revision で公開されているように、metaobject's のリビジョンと緊密に結合しているバージョンしか宣言できません。これは、型階層内のすべての型が、同じバージョニング・スキームに従わなければならないことを意味します。これには、Qt 自身が提供する型を継承したものも含まれます。

qmlRegisterType と関連する関数を使用すると、メタオブジェクトのリビジョンと型のバージョン間のマッピングを登録することができます。その場合、Q_REVISION の1引数形式や、Q_PROPERTYREVISION 属性を使用する必要があります。 しかし、これはかなり複雑で分かりにくくなる可能性があり、お勧めできません。

同じモジュールから複数のメジャーバージョンをエクスポートする

qt_add_qml_module のデフォルトでは、QT_QML_SOURCE_VERSIONSQ_REVISION を使って、個々の型が追加された特定のバージョンで他のバージョンを宣言していたとしても、VERSION 引数で指定されたメジャーバージョンを考慮します。 モジュールが複数のバージョンで利用可能な場合、個々の QML ファイルがどのバージョンで利用可能かを決定する必要もあります。さらにメジャーバージョンを宣言するには、qt_add_qml_modulePAST_MAJOR_VERSIONS オプションや、個々のQMLファイルのQT_QML_SOURCE_VERSIONS プロパティを使います。

set_source_files_properties(Thing.qml
    PROPERTIES
        QT_QML_SOURCE_VERSIONS "1.4;2.0;3.0"
)

set_source_files_properties(OtherThing.qml
    PROPERTIES
        QT_QML_SOURCE_VERSIONS "2.2;3.0"
)

qt_add_qml_module(my_module
    URI MyModule
    VERSION 3.2
    PAST_MAJOR_VERSIONS
        1 2
    QML_FILES
        Thing.qml
        OtherThing.qml
        OneMoreThing.qml
    SOURCES
        everything.cpp everything.h
)

MyModule はメジャーバージョン1、2、3で利用可能です。最大バージョンは3.2です。Thing.qmlとOtherThing.qmlについては、明示的なバージョン情報が追加されています。Thing.qmlはバージョン1.4から、OtherThing.qmlはバージョン2.2から利用可能です。メジャーバージョンを上げると、モジュールからQMLファイルが削除される可能性があるため、 。OneMoreThing.qmlには明示的なバージョン情報はありません。つまり、OneMoreThing.qmlはマイナーバージョン0からすべてのメジャーバージョンで利用可能です。set_source_files_properties()

この設定により、生成された登録コードは、各メジャー・バージョンに対してqmlRegisterModule() を使用してモジュールversions を登録します。こうすることで、すべてのバージョンをインポートすることができます。

カスタムディレクトリレイアウト

QMLモジュールを構造化する最も簡単な方法は、モジュールをURIで命名されたディレクトリに格納することです。例えば、My.Extra.Moduleというモジュールは、それを使用するアプリケーションに相対するMy/Extra/Moduleというディレクトリに置かれます。こうすることで、実行時やどのツールでも簡単に見つけることができます。

より複雑なプロジェクトでは、この規約は制限が多すぎる場合があります。例えば、プロジェクトのルートディレクトリを汚さないために、すべてのQMLモジュールを1つの場所にまとめたいかもしれません。あるいは、1つのモジュールを複数のアプリケーションで再利用したい場合もあるでしょう。そのような場合には、QT_QML_OUTPUT_DIRECTORYRESOURCE_PREFIXIMPORT_PATHを組み合わせて使うことができます。

QML モジュールを特定の出力ディレクトリ(例えば、ビルドディレクトリQT_QML_OUTPUT_DIRECTORY のサブディレクトリ "qml")に集めるには、トップレベルの CMakeLists.txt で以下のように設定してください:

set(QT_QML_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/qml)

QML モジュールの出力ディレクトリは新しい場所に移動します。同様に、qmllintqmlcachegen 、新しい出力ディレクトリをインポートパスとして使用するように自動的に変更されます。新しい出力ディレクトリはデフォルトのQMLインポートパスの一部ではないので、 QMLモジュールが見つかるように、実行時に明示的に追加する必要があります。

これで物理的なファイルシステムは片付きましたが、QMLモジュールをリソース ファイルシステム内の別の場所に移動したい場合もあるでしょう。これがRESOURCE_PREFIXオプションです。各qt_add_qml_moduleで個別に指定する必要があります。QMLモジュールは指定されたプレフィックスの下に置かれ、URIから生成されたターゲットパスが付加されます。例えば、次のようなモジュールを考えてみましょう:

qt_add_qml_module(
    URI My.Great.Module
    VERSION 1.0
    RESOURCE_PREFIX /example.com/qml
    QML_FILES
        A.qml
        B.qml
)

これはリソースファイルシステムにexample.com/qml/My/Great/Module というディレクトリを追加し、その中に上で定義したQMLモジュールを配置します。モジュールは物理的なファイルシステムで見つかるので、QMLのインポートパスに リソースの接頭辞を追加する必要はありません。しかし、ほとんどのモジュールでは、物理ファイルシステムから読み込むよりも、 リソースファイルシステムから読み込む方が速いため、一般的には、QMLのインポートパスに リソースの接頭辞を追加するのがよいでしょう。

QML モジュールを複数のインポートパスを持つ大規模なプロジェクトで使用する 場合は、追加の手順を行う必要があります:実行時にインポートパスを追加しても、qmllint のようなツールはインポートパスにアクセスできず、正しい依存関係を見つけることができないかもしれません。IMPORT_PATH を使用して、ツーリングに考慮すべき追加パスを伝えてください。例えば

qt_add_qml_module(
    URI My.Dependent.Module
    VERSION 1.0
    QML_FILES
        C.qml
    IMPORT_PATH "/some/where/else"
)

ランタイムファイルシステムアクセスの排除

すべてのQMLモジュールが常にリソースファイルシステムからロードされる場合、 アプリケーションを単一のバイナリとしてデプロイすることができます。

QTP0001ポリシーがNEW に設定されている場合、qt_add_qml_module()RESOURCE_PREFIX 引数のデフォルトは/qt/qml/ であるため、モジュールはリソースファイルシステムの:/qt/qml/ に配置されます。これはデフォルトのQMLインポートパスの一部ですが、Qt自身は使用しません。アプリケーション内で使用するモジュールには、この場所が適切です。

代わりにカスタムリソース(RESOURCE_PREFIX )を指定した場合は、QML インポートパスにカスタムリソースプレフィックスを追加する必要があります。複数のリソース接頭辞を追加することもできます:

QQmlEngine qmlEngine;
qmlEngine.addImportPath(QStringLiteral(":/my/resource/prefix"));
qmlEngine.addImportPath(QStringLiteral(":/other/resource/prefix"));
// Use qmlEngine to load the main.qml file.

これは、サードパーティのライブラリを使用する際に、モジュール名の衝突を避けるために必要になる場合があります。サードパーティライブラリを使用する場合、モジュール名の衝突を避けるために必要な場合があります。

パス:/qt-project.org/imports/ もデフォルトのQML インポートパスの一部です。異なるプロジェクトや Qt のバージョン間で頻繁に再利用されるモジュールについては、:/qt-project.org/imports/ をリソース接頭辞として使用することができます。ただし、Qt独自のQMLモジュールはここに置かれます。上書きしないように注意してください。

不必要なインポートパスは追加しないでください。QMLエンジンがあなたのモジュールを間違った場所で見つけてしまうかもしれません。これは、特定の環境でしか再現できない問題を引き起こす可能性があります。

カスタムQMLプラグインの統合

image provider をQMLモジュールにバンドルする場合、QQmlEngineExtensionPlugin::initializeEngine() メソッドを実装する必要があります。このため、独自のプラグインを書く必要があります。このような場合、NO_GENERATE_PLUGIN_SOURCEを使うことができます。

独自のプラグイン・ソースを提供するモジュールを考えてみよう:

qt_add_qml_module(imageproviderplugin
    VERSION 1.0
    URI "ImageProvider"
    PLUGIN_TARGET imageproviderplugin
    NO_PLUGIN_OPTIONAL
    NO_GENERATE_PLUGIN_SOURCE
    CLASS_NAME ImageProviderExtensionPlugin
    QML_FILES
        AAA.qml
        BBB.qml
    SOURCES
        moretypes.cpp moretypes.h
        myimageprovider.cpp myimageprovider.h
        plugin.cpp
)

このように、myimageprovider.hで画像プロバイダーを宣言することができます:

class MyImageProvider : public QQuickImageProvider
{
    [...]
};

plugin.cppでは、QQmlEngineExtensionPlugin を定義します:

#include <myimageprovider.h>
#include <QtQml/qqmlextensionplugin.h>

class ImageProviderExtensionPlugin : public QQmlEngineExtensionPlugin
{
    Q_OBJECT
    Q_PLUGIN_METADATA(IID QQmlEngineExtensionInterface_iid)
public:
    void initializeEngine(QQmlEngine *engine, const char *uri) final
    {
        Q_UNUSED(uri);
        engine->addImageProvider("myimg", new MyImageProvider);
    }
};

これで、画像プロバイダーが利用可能になります。プラグインとバッキング・ライブラリは同じCMakeターゲットimageproviderpluginにあります。これは、リンカーがさまざまなシナリオでモジュールの一部を落とさないようにするためです。

このプラグインはイメージ・プロバイダを作成するため、initializeEngine 関数を持たなくなりました。そのため、プラグインはオプションではなくなりました。

©2024 The Qt Company Ltd. 本書に含まれる文書の著作権は、それぞれの所有者に帰属します。 本書で提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。