Qt でクライアント向けのアノテーションクラスを実装する際、C++ の主要データを QML に公開するために Q_PROPERTY を多用すると、十数個しか使えないという制限に遭遇しました。
class ImageShow : public QQuickPaintedItem
{
Q_OBJECT
Q_PROPERTY(QJsonObject minio_source READ minio_source WRITE setMinio_source NOTIFY minio_sourceChanged)
Q_PROPERTY(QVariant source READ source WRITE setSource NOTIFY sourceChanged)
Q_PROPERTY(zy::BaseShape::ShapeType base_shape READ base_shape WRITE setBase_shape NOTIFY base_shapeChanged)
Q_PROPERTY(int edit READ edit WRITE setEdit NOTIFY editChanged)
// 以下、コメントアウトされたプロパティ多数…
// Q_PROPERTY(bool canScaled …)
// Q_PROPERTY(bool canMoved …)
Q_PROPERTY(QRectF clipRoi READ clipRoi WRITE setClipRoi NOTIFY clipRoiChanged)
// … 複数行省略
};
そのため、QML の Item が anchors に {} を利用して多くのプロパティを定義できるよう、マクロで簡易的な処理を実装し、後々の機能追加に備えることにしました。
毎回同じようなコードを書くのを避けるため、以下のマクロを定義します。
#ifndef DEMOHEADER_H
#define DEMOHEADER_H
// Q_PROPERTY も含む完全版マクロ
#define QML_PROPERTY(type, name, setter, initial, handler) \
Q_PROPERTY(type name READ name WRITE setter NOTIFY name##Changed) \
public: \
type name() const { return m_##name; } \
void setter(type val) { \
if (m_##name != val) { \
m_##name = val; \
handler(val); \
emit name##Changed(m_##name); \
} \
} \
Q_SIGNALS: \
void name##Changed(type val); \
private: \
type m_##name = initial;
// 簡易版(Q_PROPERTY 行を別途記述したい場合)
#define QML_PROPERTY_SIMPLE(type, name, setter, initial, handler) \
public: \
type name() const { return m_##name; } \
void setter(type val) { \
if (m_##name != val) { \
m_##name = val; \
handler(val); \
emit name##Changed(m_##name); \
} \
} \
Q_SIGNALS: \
void name##Changed(type val); \
private: \
type m_##name = initial;
#endif // DEMOHEADER_H
次に、拡張データを保持するクラス RoiData でこれらのマクロを活用します。
#ifndef ROIDATA_H
#define ROIDATA_H
#include "demoheader.h"
#include <QObject>
#include <QVariant>
class RoiData : public QObject
{
Q_OBJECT
// 手動で Q_PROPERTY を定義
Q_PROPERTY(bool canScale READ canScale WRITE setCanScale NOTIFY canScaleChanged)
Q_PROPERTY(bool canMove READ canMove WRITE setCanMove NOTIFY canMoveChanged)
Q_PROPERTY(QVariantMap handles READ handles WRITE setHandles NOTIFY handlesChanged)
Q_PROPERTY(int fillMode READ fillMode WRITE setFillMode NOTIFY fillModeChanged)
Q_PROPERTY(int statusFlags READ statusFlags WRITE setStatusFlags NOTIFY statusFlagsChanged)
public:
enum LabelStatus { Annotation = 0, Training, Testing };
Q_ENUM(LabelStatus)
enum FillMode { DefaultFit = 0, Stretch, CenteredPreserve };
Q_ENUM(FillMode)
// マクロでプロパティ自動生成(Q_PROPERTY も含む)
QML_PROPERTY(int, statusFlags, setStatusFlags, 1, reshaped)
QML_PROPERTY(bool, canScale, setCanScale, false, reshaped)
QML_PROPERTY(bool, canMove, setCanMove, true, reshaped)
QML_PROPERTY(bool, canHover, setCanHover, false, reshaped)
QML_PROPERTY(QVariantMap, handles, setHandles, {}, reshaped)
QML_PROPERTY(FillMode, fillMode, setFillMode, DefaultFit, reshaped)
QML_PROPERTY(LabelStatus, label, setLabel, Annotation, reshaped)
explicit RoiData(QObject *parent = nullptr);
signals:
void reshaped(QVariant data); // 再描画トリガー
};
Q_DECLARE_METATYPE(RoiData::LabelStatus)
#endif // ROIDATA_H
そして、RoiData を子プロパティとして持つメインクラス MainItem を以下に定義します。
#ifndef MAINITEM_H
#define MAINITEM_H
#include <QObject>
#include "roidata.h"
#include <QVariant>
#include <QDebug>
class MainItem : public QObject
{
Q_OBJECT
// 手動で Q_PROPERTY 追加(マクロを使わず、子オブジェクトは専用に定義)
Q_PROPERTY(RoiData* roi READ roi WRITE setRoi NOTIFY roiChanged)
public:
explicit MainItem(QObject *parent = nullptr) : QObject(parent)
{
m_roi = new RoiData(this);
qDebug() << m_roi->statusFlags() << m_roi->handles()
<< m_roi->canHover() << m_roi->canMove()
<< m_roi->canScale();
}
RoiData* roi() const { return m_roi; }
void setRoi(RoiData* val) {
if (m_roi != val) {
m_roi = val;
emit roiChanged();
}
}
signals:
void roiChanged();
private:
RoiData* m_roi = nullptr;
};
#endif // MAINITEM_H
続いて、main.cpp にて QML 側に型を登録します。
#define URI "cpp"
qmlRegisterType<MainItem>(URI, 1, 0, "MainItem");
qmlRegisterType<RoiData>(URI, 1, 0, "RoiData");
最終的に main.qml から下記のように使えます。子プロパティを {} で初期化できるので、可読性が向上します。
import QtQuick 2.12
import QtQuick.Window 2.12
import cpp 1.0
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
MainItem {
id: item
roi {
statusFlags: 12
canScale: true
}
}
Component.onCompleted: {
item.roi.statusFlags = 2
item.roi.label = RoiData.Training
item.roi.handles = { type: "12312", data: { index: 1, value: "dasfads" } }
item.roi.fillMode = RoiData.CenteredPreserve
console.log(JSON.stringify(item.roi))
}
}
実行結果(略)。後日データを拡張する際は、RoiData クラスに新しいプロパティを QML_PROPERTY マクロで追加するだけで済みます。