カスタム Qt タイプの作成

概要

Qt を使ってユーザーインターフェースを作成する場合、特に特殊なコントロールや機能を持つユーザーインターフェースを作成する場合、開発者は、Qt の既存のデータ型のセットと一緒に、またはその代わりに使用できる新しいデータ型を作成する必要があります。

QSizeQColorQString などの標準的な型は、QVariant オブジェクトに格納したり、QObject ベースのクラスでプロパティの型として使用したり、シグナル・スロット通信で発行したりすることができます。

このドキュメントでは、カスタム型を取り上げ、それをQtのオブジェクトモデルに統合し、標準のQt型と同じように格納できるようにする方法を説明します。次に、カスタム型をシグナルやスロット接続で使用できるように登録する方法を示します。

カスタム型の作成

始める前に、作成するカスタム型が、QMetaType のすべての要件を満たしていることを確認する必要があります。つまり

  • パブリックなデフォルトコンストラクタ
  • パブリックなコピーコンストラクタ
  • パブリック・デストラクタです。

以下のMessage のクラス定義には、これらのメンバが含まれています:

class Message
{
public:
    Message() = default;
    ~Message() = default;
    Message(const Message &) = default;
    Message &operator=(const Message &) = default;

    Message(const QString &body, const QStringList &headers);

    QStringView body() const;
    QStringList headers() const;

private:
    QString m_body;
    QStringList m_headers;
};

また、このクラスは、通常使用するコンストラクタと、プライベート・データを取得するために使用する 2 つのパブリック・メンバ関数も提供します。

QMetaTypeによる型の宣言

Message クラスを使用するためには、適切な実装が必要です。しかし、Qtの型システムは、このクラスのインスタンスを保存、取得、シリアライズする方法を理解することができません。例えば、QVariantMessage の値を格納することはできません。

Qtでカスタム型を担当するクラスはQMetaType です。このクラスに型を知らせるには、Q_DECLARE_METATYPE() マクロを、そのクラスが定義されているヘッダーファイルで呼び出します:

Q_DECLARE_METATYPE(Message);

これにより、Message の値をQVariant オブジェクトに格納し、後で取り出すことができるようになります:

QVariant stored;
stored.setValue(message);
    ...
Message retrieved = qvariant_cast<Message>(stored);
qDebug() << "Retrieved:" << retrieved;
retrieved = qvariant_cast<Message>(stored);
qDebug() << "Retrieved:" << retrieved;

また、Q_DECLARE_METATYPE ()マクロによって、これらの値をシグナルの引数として使用することもできるようになるが、これはシグナル・スロットの直接接続に限られる。カスタム・タイプをシグナルとスロットのメカニズムで一般的に使えるようにするには、いくつかの追加作業が必要です。

カスタム・オブジェクトの作成と破棄

前節の宣言により、この型はシグナルとスロットの直接接続で使用できるようになりましたが、異なるスレッドのオブジェクト間で行われるような、キューイングされたシグナルとスロットの接続には使用できません。これは、メタ・オブジェクト・システムが、実行時にカスタム・タイプのオブジェクトの生成と破棄を処理する方法を知らないためです。

実行時にオブジェクトを作成できるようにするには、qRegisterMetaType ()テンプレート関数を呼び出して、メタ・オブジェクト・システムに登録します。これにより、その型を使用する最初の接続を行う前にこの関数を呼び出す限り、キューイングされたシグナル・スロット通信でもその型を使用できるようになります。

キューイング・カスタム・タイプの例では、main.cpp ファイルに登録されたBlock クラスを宣言しています:

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    ...
    qRegisterMetaType<Block>();
    ...
    return app.exec();
}

この型は後に、window.cpp ファイル内のシグナル・スロット接続で使用されます:

Window::Window(QWidget *parent)
    : QWidget(parent), thread(new RenderThread(this))
{
    ...
    connect(thread, &RenderThread::sendBlock,
            this, &Window::addBlock);
    ...
    setWindowTitle(tr("Queued Custom Type"));
}

もし型が登録されずにキューイングされた接続で使用されると、コンソールに警告が表示されます:

QObject::connect: Cannot queue arguments of type 'Block'
(Make sure 'Block' is registered using qRegisterMetaType().)

型を印刷可能にする

以下のコードのように、デバッグのためにカスタム型を印刷可能にすることは非常に便利です:

Message message(body, headers);
qDebug() << "Original:" << message;

これは、その型用のストリーミング演算子を作成することで実現され、多くの場合、その型のヘッダーファイルで定義されている:

QDebug operator<<(QDebug dbg, const Message &message);

このMessage 型の実装では、印刷可能な表現を可能な限り読みやすくするための工夫がなされている:

QDebug operator<<(QDebug dbg, const Message &message)
{
    const QList<QStringView> pieces = message.body().split(u"\r\n", Qt::SkipEmptyParts);
    if (pieces.isEmpty())
        dbg.nospace() << "Message()";
    else if (pieces.size() == 1)
        dbg.nospace() << "Message(" << pieces.first() << ")";
    else
        dbg.nospace() << "Message(" << pieces.first() << " ...)";
    return dbg;
}

デバッグ・ストリームに送られる出力は、もちろん好きなように単純にも複雑にもできる。この関数によって返される値はQDebug オブジェクトそのものであることに注意してください。しかし、これは多くの場合、QDebugmaybeSpace() メンバ関数を呼び出すことで得られます。この関数は、読みやすくするために、ストリームをスペース文字で埋め尽くします。

さらなる読み方

Q_DECLARE_METATYPE()マクロとqRegisterMetaType()関数のドキュメントには、その使用方法と制限に関する詳細な情報が記載されています。

キュー・カスタム・タイプの例では、このドキュメントで説明されている機能を使ってカスタム・タイプを実装する方法を示しています。

Debugging Techniquesドキュメントでは、上記で説明したデバッグ・メカニズムの概要を説明しています。

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