Qt バインダブル・プロパティ

Qtにはバインダブル・プロパティがあります。バインダブル・プロパティは、値を持つか、または任意の C++ 関数(通常は C++ ラムダ式)を使用して指定されるプロパティです。C++関数を使用して指定された場合、依存関係が変更されるたびに自動的に更新されます。

バインダブル・プロパティは、データ・オブジェクトと管理データ構造へのポインタから構成されるクラスQProperty と、データ・オブジェクトのみから構成され、管理データ構造へのポインタを格納するためにカプセル化QObject を使用するクラスQObjectBindableProperty で実装されています。

なぜバインダブル・プロパティを使うのか?

プロパティのバインディングはQMLのコア機能の一つです。これによって、異なるオブジェクトのプロパティ間の関係を指定し、その依存関係が変更されるたびにプロパティの値を自動的に更新することができます。バインダブル・プロパティは、QMLのコードだけでなく、C++でも同じことを実現することができます。バインダブル・プロパティを使用することで、異なるオブジェクトの依存関係の更新を追跡し、それに対応するための定型的なコードを省くことができ、プログラムを簡素化することができます。

以下の入門例では、C++コードにおけるバインダブル・プロパティの使い方を示します。また、バインダブル・プロパティの例を参照して、バインダブル・プロパティがコードの改善にどのように役立つかを確認することもできます。

導入例

バインディング式は、他のQProperty 値を読み込んで値を計算します。舞台裏では、この依存関係が追跡されます。プロパティの依存関係の変更が検出されるたびに、バインディング式が再評価され、新しい結果がプロパティに適用されます。例えば

QProperty<QString> firstname("John");
QProperty<QString> lastname("Smith");
QProperty<int> age(41);

QProperty<QString> fullname;
fullname.setBinding([&]() { return firstname.value() + " " + lastname.value() + " age: " + QString::number(age.value()); });

qDebug() << fullname.value(); // Prints "John Smith age: 41"

firstname = "Emma"; // Triggers binding reevaluation

qDebug() << fullname.value(); // Prints the new value "Emma Smith age: 41"

// Birthday is coming up
age.setValue(age.value() + 1); // Triggers re-evaluation

qDebug() << fullname.value(); // Prints "Emma Smith age: 42"

新しい値がfirstname プロパティに割り当てられると、fullname のバインディング式が再評価されます。したがって、最後のqDebug() ステートメントがfullname プロパティの名前値を読み取ろうとすると、新しい値が返されます。

バインディングはC++関数なので、C++で可能なことは何でもできます。これには、他の関数を呼び出すことも含まれます。こ れ ら の関数がQProperty に よ っ て保持 さ れてい る 値にア ク セ スす る 場合、 それ ら は自動的にバ イ ンデ ィ ン グに依存す る よ う にな り ます。

バインディング式は、任意の型のプロパティを使用することができます。したがって、上記の例では、年齢は整数であり、整数への変換を使用して文字列値に折り畳まれていますが、依存関係は完全に追跡されます。

バインド可能なプロパティのゲッターとセッター

クラスがバインド可能なプロパティを持つ場合、QProperty またはQObjectBindableProperty を使用して、そのプロパティのゲッターとセッターを作成するときに特別な注意を払う必要があります。

バインダブル・プロパティ・ゲッター

自動依存関係追跡システムの適切な動作を保証するために、ゲッター内のすべての可能なコード・パスは、基礎となるプロパティ・オブジェクトから読み取る必要があります。さらに、ゲッターの内部にプロパティを書き込んではいけません。ゲッター内で何かを再計算したりリフレッシュしたりするデザインパターンは、バインド可能なプロパティとは互換性がありません。

そのため、バインド可能なプロパティでは些細なゲッターのみを使用することを推奨します。

バインド可能なプロパティ・セッター

自動依存関係追跡システムの適切な動作を保証するために、セッター内で可能なすべてのコードパスは、たとえ値が変更されなかったとしても、基礎となるプロパティオブジェクトに書き込む必要があります。

セッター内のその他のコードは、不正確である可能性が高くなります。新しい値に基づいて更新を行うコードは、バインディングによってプロパティが変更されたときにこのコードが実行されないため、バグである可能性が高いです。

したがって、バインド可能なプロパティでは、些細なセッターのみを使用することをお勧めします。

バインダブル・プロパティへの書き込み

バインダブル・プロパティは、各変更を依存プロパティに通知します。これにより変更ハンドラが起動し、任意のコードが呼び出される可能性があります。したがって、バインダブル・プロパティへの書き込みは、慎重に検査する必要があります。以下のような問題が発生する可能性があります。

バインダブル・プロパティへの中間値の書き込み

バインダブル・プロパティをアルゴリズムの変数として使ってはいけません。書き込まれたそれぞれの値は、依存するプロパティに伝達されることになる。例えば、以下のコードでは、myPropertyに依存する他のプロパティは、まず42への変更について知らされ、次にmaxValueへの変更について知らされることになる。

myProperty = somecomputation(); // returning, say, 42
if (myProperty.value() > maxValue)
    myProperty = maxValue;

代わりに、別の変数で計算を実行します。正しい使い方を次の例に示します。

int newValue = someComputation();
if (newValue > maxValue)
    newValue = maxValue;
myProperty = newValue; // only write to the property once

遷移状態でのバインド可能プロパティの記述

バインド可能なプロパティがクラスのメンバである場合、そのプロパティに書き込むたびに現在の状態が外部に公開される可能性があります。そのため、バインド可能なプロパティは、クラスの不変条件が満たされない過渡的な状態では記述してはいけません。

例えば、radiusと areaの2つのメンバを一貫して保持する円を表すクラスでは、セッターは次のようになります(radiusはバインド可能なプロパティです):

void setRadius(double newValue)
{
    radius = newValue; // this might trigger change handlers
    area = M_PI * radius * radius;
    emit radiusChanged();
}

ここで、変更ハンドラでトリガされるコードは、新しい半径を持ちながら、古い面積を持つ円を使用するかもしれません。

仮想セッターとゲッターによるバインド可能なプロパティ

通常、プロパティのセッターとゲッターは最小限のものであるべきで、プロパティを設定する以外には何もしない。派生クラスが行うことに意味はありません。

しかし、いくつかのQtクラスは仮想セッターを持つプロパティを持つことができます。そのようなQtクラスをサブクラス化する場合、そのようなセッターをオーバーライドするには特別な注意が必要です。

いずれにせよ、バインディングが正しく動作するためには、ベースとなる実装が呼び出されなければなりません

以下にこの方法を示します。

void DerivedClass::setValue(int val)
{
    // do something
    BaseClass::setValue(val);
    // probably do something else
}

バインディング可能なプロパティへの記述に関する一般的なルールや推奨事項は、すべてここでも適用されます。ベースクラスの実装が呼び出されるとすぐに、すべてのオブザーバーにプロパティの変更が通知されます。これは、ベースクラスの実装を呼び出す前に、クラスの不変性が満たされていなければならないことを意味します。

このような仮想ゲッターや仮想セッターが必要な場合は、ベースクラスがオーバーライドに課す要件を文書化する必要があります。

プロパティ結合の定式化

正しい型に評価される任意の C++ 式をバインディング式として使用し、setBinding() メソッドに渡すことができます。ただ し 、 正 し いバ イ ンデ ィ ン グ式を作成す る には、 い く つかの規則に従 う 必要があ り ます。

依存関係の追跡は、バインド可能なプロパティに対してのみ機能します。バインディング式で使用するすべてのプロパティがバインド可能なプロパティであることを確認するのは開発者の責任です。バインド式で非バインド・プロパティが使用されている場合、それらのプロパティの変更はバインド・プロパティの更新をトリガーしません。コンパイル時にも実行時にも、警告やエラーは発生しません。バインド・プロパティが更新されるのは、バインド式で使用されているバインド可能なプロパティが変更されたときだけです。バインドされない依存関係が変更されるたびに、バインドされるプロパティでmarkDirtyが呼び出されることを保証できる場合、バインドされないプロパティがバインドで使用されることがあります。

バインドされたプロパティは、その存続期間中にバインディングを数回評価する可能性があります。開発者は、バインディング式で使用されるすべてのオブジェクトが、バインディングよりも長生きすることを確認する必要があります。

バインド可能なプロパティシステムはスレッドセーフではありません。あるスレッドのバインディング式で使用されるプロパティは、他のスレッドで読み込んだり変更したりしてはいけません。バインディングを持つプロパティを持つQObject 派生クラスのオブジェクトは、別のスレッドに移動してはいけません。また、QObject-派生クラスのオブジェクトで、バインディングで使用されるプロパティを持つオブジェクトは、別のスレッドに移動してはなりません。この文脈では、同じオブジェクトのプロパティのバインディングで使用されるか、別のオブジェクトのプロパティのバインディングで使用されるかは関係ありません。

バインディング式は、それがバインディングであるプロパティから読み取るべきではありません。さもないと、評価ループが発生します。

バインディング式は、バインディング先のプロパティに書き込んではいけません。

バインディングとして使用される関数や、バインディングの内部で呼び出されるすべてのコードは、co_awaitしてはいけません。co_awaitを行うと、プロパティ・システムによる依存関係の追跡を混乱させる可能性があります。

バインダブル・プロパティとマルチスレッド

バインダブル・プロパティは、特に断りのない限りスレッドセーフではありません。バインダブル・プロパティは、作成されたスレッド以外のスレッドで読み込んだり変更したりしてはいけません。

バインダブル・プロパティの追跡

プロパティ間の関係をバインディングで表現できないことがあります。その代わりに、プロパティの値が変更されるたびにカスタムコードを実行し、その値を他のプロパティに代入する代わりに、アプリケーションの他の部分に渡す必要があるかもしれません。例えば、ネットワーク・ソケットにデータを書き込んだり、デバッグ出力を印刷したりするような場合です。QProperty 、トラッキングのための2つのメカニズムが用意されています。

onValueChanged()を使用して、プロパティの値が変更されるたびに呼び出されるコールバック関数を登録できます。プロパティの現在値に対してもコールバックを呼び出したい場合は、代わりに subscribe() を使用してコールバックを登録します。

Q_PROPERTY との相互作用

BINDABLE を定義するQ_PROPERTYをバインドして、バインド式で使用することができます。このようなプロパティを実装するには、QPropertyQObjectBindableProperty 、またはQObjectComputedProperty を使用します。

NOTIFY シグナルを定義している限り、BINDABLE を持たない Q_PROPERTY もバインドしてバインド式で使用することができます。QBindable(QObject* obj, const char* property) コンストラクタを使用して、プロパティをQBindable でラップする必要があります。その後、QBindable::setBinding ()を使用してプロパティをバインドするか、QBindable::value ()を使用してバインド式で使用することができます。プロパティがBINDABLE でない場合に依存性追跡を有効にするには、通常のプロパティREAD 関数(またはMEMBER )の代わりに、QBindable::value() をバインディング式で使用する必要があります。

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