メインコンテンツまでスキップ

チュートリアルValueTreeクラス

📚 Source Page

の使い方を学ぶ。ValueTreeクラスを使用すると、アプリケーションでデータを効率的に管理できます。

レベル:中級

プラットフォーム:Windows, macOS, Linux, iOS, Android

クラス: ValueTree,var,Identifier

はじめに

についてValueTreeクラスはJUCEの秘密兵器です。これは、アプリケーションの内部的な複雑さを大幅に合理化する力を持っており、従来は受動的であったデータモデルを、実行時の操作に積極的に参加するものに変えます。このクラスは、データをGUIと連動させたり、データ変更の自動アンドゥやリドゥを提供したり、データのユニバーサルコンテナを提供したりといった、開発における面倒な部分を引き受けてくれます。また、本質的にシリアライズが可能なので、エクスポートやインポート処理が非常に簡単になります。

このガイドブックでは、サッカーに必要な知識をすべて網羅することを目的としている。このガイドでは、それらを使用するために知っておく必要があるすべてをカバーすることを目的としています。

必要不可欠な3つのクラス

についてValueTreeクラスはまさにアンサンブルであり、彼らと仕事をする際には、少なくとも3つの重要なクラスと常に関わり合うことになる。その3つとはValueTreeクラス自体がvarクラスとIdentifierクラスである。で意味のあることをするのは不可能だ。ValueTreeそのため、それらをどのように(そしてなぜ)使うべきかを理解することは有益である。したがって、このようなデータを操作する実際の実務について説明する前に、以下のことをよく理解しておく必要がある:

ValueTreeクラスの概要

についてValueTreeクラスは究極のコンテナ・クラスであり、あらゆる種類の情報を保持することができる。これが型の明らかな役割かもしれないが、型には他にも多くの側面があり、単なる構造的な役割を超えている。

データ保管

AValueTreeオブジェクトは柔軟な多目的データオブジェクトです。基本的なタイプという名前を持ち、名前付きプロパティの任意のセットを保持することができる。これは一種のユニバーサル構造体どのような(あるいはどの程度の)データを保持するかを定義する必要はない。

説明のために、1つのオブジェクト(ここでは疑似データ)は以下の情報を保持しているかもしれない:

Pet
Name = "Fluffmuff"
Animal = "Cat"
Size = 2.4

その名の通りValueTreeオブジェクトはツリー構造のノードとしても機能します。名前付きのプロパティとともに、任意の数の子ノードを含むことができます(そして、それ自体を親ノードに追加することもできます)。

Pet
Name = "Fluffmuff"
Animal = "Cat"
Size = 2.4
Accessories
Collar
Colour = "Pink"
Camera
HasFlash = false
Capacity = 32
ColourRepresents a colour, also including a transparency value.Definition juce_Colour.h:50

これらの構造は、XMLフォーマットで形成される構造と非常によく似ている。ValueTreeノードはXmlElementオブジェクトで、名前、プロパティ、子を持つ。大きな違いは、プロパティが、XMLフォーマットで使用されるテキスト・フォー・エヴリシング表現ではなく、実際の型付きデータとして格納されることである。つまり、より複雑なタイプのデータを保持し、より効率的にアクセスすることができ、一般的に(シリアライズだけでなく)データモデルでの使用に適しています。実際、XMLテキスト(または特殊なバイナリ形式)を任意のValueTreeノードを作成し、後でその構造を復元する。

参考カウント

各ノードは参照カウントされるため、その寿命は簡単に管理できる。データ自体は、実際には隠された共有インスタンスに格納される。ValueTreeクラスは、軽い、参照を保持するラッパー・インターフェイスに過ぎない。

を返すことで、ポインタを直接使用することなく、値で素早く渡すことができます。ValueTreeオブジェクトを関数からコピーしても、データはコピーされず、参照だけがコピーされる。

自分でノードを削除する心配はありません。ノードが使われなくなると、自動的に破棄されます。これは、非同期UIでダングリングポインタが発生しないようにするために特に便利です。

シンプルなインターフェース

プロパティや子プロパティを操作するためのシンプルで汎用的なインターフェイスを持つ。汎用的であることで、コンテンツの種類や構成に関係なく、コンテンツにアクセスするための単一のインターフェイスを持つことができます。普遍的なvarクラスを共通のプロパティ・タイプとすることで、さまざまな入力を取ることができる。そのためIdentifierクラスを使って、これらのプロパティを名前別に保存したり取得したりすることで、直感的にデータを整理することができます。

取り消しとやり直し

コンテンツを変更するすべての関数に、あらかじめ定義された取り消し可能なアクションが組み込まれている。UndoManagerオブジェクトを使うことで、アプリケーションにそのような機能を持たせることができます。まだ納得されていない方のために言っておくと、これはValueTreeオブジェクトをデータモデルに追加する。

お知らせ

もう一つの非常に強力な機能は、バリューツリーのコンテンツが変更されたときに通知を送信する機能である。例えばComponentノードの内容を表示するために使用されているオブジェクトは、データが編集されたことを確認するたびに、それ自身をリフレッシュすることができます。ValueTree::Listenerサブクラスである。

概要

についてValueTreeクラスは、アプリケーションのデータモデルにとって奇跡のクラスのようなもので、アプリケーションの内部をまとめる方法を単純化するための豊富な機能を難なく提供してくれる。

クラス概要

についてvarクラスは普遍的なバリアントクラスは、さまざまな型のデータを保持する。その機能は、JSONデータ構造を表現するのに適している。

従来は、コード内の変数に格納するデータの種類をあらかじめ決めておく必要がありました(たとえば、整数を格納したい場合はintその変数が使えるのはそれだけである)。しかしvar様々なタイプに対応しているので、そのような決定を下す必要はない。

これは汎用のカメレオン変数のようなもので、基本的な数値 (intまたはdouble)、テキスト(aStringオブジェクト)、およびbool値と同様にvoid状態(なぜなら0またはfalseとは概念的に異なることがある。何もない).から派生したクラスへのポインタを保持することもできる。ReferenceCountedObjectクラス(想像しうるあらゆる種類の複雑なデータで構成されうる)。これだけでは十分でないかのように、単一のvarオブジェクトは、複数のvarオブジェクトがある!

この多用途性により、一般的なコンテナ(たとえばValueTree基本的な型には暗黙のキャスト(とオーバーロードされた代入演算子)があり、コード上のやりとりをシンプルにする。さらに、現在の値の文字列表現を自動的に返すこともできる(テキスト以外の型も含む)。これらの贅沢がすべて適用されないのはvarオブジェクトはReferenceCountedObjectオブジェクトにキャストする必要があります。これらは未知の型なので、自分でキャストしなければならない。dynamic_castを返す。nullptr正しい型でない場合)。

においてである。ValueTreeオブジェクトとして保持されます。varオブジェクトにアクセスできます。この多目的クラスをプロパティタイプとして選択することで、どのようなものであれ、それらにアクセスするための関数を1つにまとめることができます。のために1つの関数を使う必要はない。intこのような関数はすべて、以下のように統一することができる。varオブジェクトがある。

識別子クラスの概要

このクラスは、人間が読むことができるキーデータの識別に使用される。

基本的にはIdentifierオブジェクトは文字列です。オブジェクトはStringオブジェクトとして取り出すこともできます。StringオブジェクトのコンテキストではValueTreeクラスでは、ノードのタイプ名を指定するためと、各プロパティを一意にラベル付けするために使用されます。

なぜStringクラスを使わないのか?

汎用クラスではなく特殊クラスを使う主な理由は2つある。Stringクラスである。

  • 限られた文字セットを強制することができる:第一に、このクラスは有効なキーを構成する文字の制限を強制します。_-:#$%.これは少しくだらなく聞こえるかもしれないが、同じ制限(例えばXMLフォーマットやスクリプト)を持つ他のシステムとの互換性を確保することが可能になる。
  • 目的に応じて最適化できる:2つ目の(しかし最も重要な)理由は、それらをどのように使いたいかに起因する。リストは、任意のサイズのリストから1つの項目を特定するためのキーとして機能するものであり、そのため、それらを使って実行される最も一般的な操作は比較である。しかしStringオブジェクトはかなり時間がかかる。実際にテキストをチェックする必要があり、最初に異なる文字を見つけて初めて、2つのStringオブジェクトは同じではない。ほとんど同じ文字列の場合、ほとんどの文字をチェックすることになるかもしれない(一致する文字列はすべての文字がテストされることになる)。しかし、1つの文字列を1つのリストと比較する場合(キーとして使用する場合)、すべての処理に長い時間がかかる可能性があります。

特別なクラスを使うことで、素早く比較できるように最適化されていることを保証できる。この最適化(と文字セットの制限)のため、一般的なテキスト・ハンドリングに使用することは意図されていないが、文字列のようなデータをキーとして使用するには最適である。

どうすればもっと早く比較できるのか?

の最適化について知っておくことは有益である。Identifierクラスで使用されます。ひとつには、コードが各テストで密かにすべての文字をチェックしているわけではないという安心感がある。しかしもっと重要なのは、多くの最適化にはコストがつきものであり、それがどこにあるのかを知っておくことで、問題になるのを避けることができるということだ。

を使用することは完全に可能である。Identifierこのようなことを知らなくても、トレードオフがどこで発生するかは理解できるだろう。

注記

この最適化の詳細は変更される可能性がありますが(これはクラスのセカンダリー・ナレッジです)、あなたの実装に影響を与える可能性は低いでしょう。

文字列の比較における特殊なケース

ということを知る。Stringオブジェクトが同じであることを証明しなければならない。たまたまStringクラスは参照カウントされたテキストを保持する。Stringオブジェクトが実際に同じデータを指しているのであれば、すぐにわかる(どちらも同じアドレスを保持しているからだ)。

このような特殊なケースの方がはるかに速い。Stringクラスがそれを利用できるのは、それが起こった場合だけである。アドレスが同じでない場合、その内容が等価でないことの証明にはならないので、やはり文字をチェックしなければならない。

特殊なケースを利用する

についてIdentifierクラスはこの挙動を利用し、すべての等価なIdentifierオブジェクトは常に同じデータを指す。最適化されているため、異なるIdentifierオブジェクトが異なるメモリーアドレスに存在することはない。つまり特例したがって、両者が異なることを証明するのは、両者が異なるアドレスを保持していることを見抜くのと同じくらい簡単である。

しかし、これはマジックではない。

この動作を強制するために、すべてのIdentifierオブジェクトは、単一の、隠された、ユニークな文字列のグローバルなプールを共有する。すべてのIdentifier実行時に使用されるオブジェクトは、このプールに格納される。実行時にIdentifierオブジェクトからStringオブジェクトがある場合、そのプールに同等のコピーがすでにあるかどうかがチェックされる。もしそうでなければ、新しいStringオブジェクトが追加され、次のIdentifierオブジェクトを探す。

コスト

したがってIdentifierオブジェクトからStringオブジェクトは、その運営において最も高価な部分である。一度存在すれば、常にそこからコピーしていれば、二度とその価格を支払う必要はない。やむを得ない場合もある(具体的には、ある選手を獲得する場合)。Identifierオブジェクト、または通常のStringオブジェクト)を比較するよりも遥かに少ない頻度である。素Stringオブジェクトを(データから、またはコード内のリテラルとして)代入する場合、最適化を実施するためにプール・チェックが必要になります。代わりに別の既存のIdentifierオブジェクトがあれば、そのようなチェックがすでに行われていることが保証される。

コストを最小限に抑える方法

を初期化するのが良い方法です。Identifierインスタンスに変換されます。コードから、リテラル文字列の代わりにこれらのインスタンスを使用することができます。グローバルにアクセス可能な名前空間に置いたり、ファイルの静的インスタンスを使ったり、静的クラス・メンバを使って整理することもできます。

ValueTreeクラスの基本的な使い方

に加えてvarそしてIdentifierクラスと組み合わせて使用されるクラスは、実際にはもっとたくさんあります。ValueTreeオブジェクトは、後で説明する。しかし、これらのオブジェクトは、次のような基本的な操作のほとんどに必要である。ValueTreeクラスである。

3つの必須クラスについて学んだら、基本的なことをピックアップしていくのに良い状態になるはずだ。

このガイドブックは、「サッカー」の本質的なコア機能のすべてを紹介する基本的なガイドです。ValueTreeクラスがあります。あなたのコードで使い始めるために必要な機能をすべて網羅している。

ValueTreeオブジェクトの作成

を簡単に使うことができる。ValueTreeオブジェクトをスタック上に(またはクラスのメンバーとして)作成するだけである。

無効なValueTreeオブジェクト

AValueTreeオブジェクトがデフォルトである、無効.つまりValueTreeで初期化されたデフォルトコンストラクタはまったくデータを持たない。明示的にIdentifier(または既存の有効な、ValueTreeつまり、空っぽの殻なのだ。

juce::ValueTree myNode; // this object is invalid - it does not hold any node data

これはヌル・ポインタに似ているが(実際、内部的にはまさにそうである)、固有の危険性はない。有効なノードが割り当てられるまでは、何も参照しない。ここでの主な違いは、(操作されるものが何もないため)ほとんど何の効果もないとはいえ、インターフェース関数をまだ使用できることである。

無効なノードでアクセス関数を "偶然 "呼び出すことは安全だが、それはあまり得策ではない!

ノードが無効かどうかをチェックする方法

あなたのコードでは、メンバ関数ValueTree::isValid()を確認する。ValueTreeは空である:

if (myNode.isValid())
{
// This will not be reached for an invalid node
}

有効なValueTreeオブジェクト

有効な手段を生み出す主な手段ValueTreeオブジェクトを有効なIdentifierオブジェクトを使用します。これは名称新しいノードを表し、そのノードのタイプ.

static juce::Identifier myNodeType ("MyNode"); // pre-create an Identifier
juce::ValueTree myNode (myNodeType); // This is a valid node, of type "MyNode"
注記

を明示的に指定する代わりに、文字列リテラルを使用することができます。Identifierオブジェクトを使用するのがよいでしょう。Identifierインスタンスを作成する。参照Identifier class overviewを参照)。

有効なノードが型を持つことを要求することで、どのようなデータを含むべきかを示すメカニズムができる。逆にValueTreeオブジェクトの型をチェックすることができます。ValueTree::getType()関数の中に何があるのかを知ることができる。

void foo (const juce::ValueTree& someNode)
{
if (someNode.getType() == myNodeType)
{
// This would be hit for nodes created as “MyNode”
}
}
注記

一度ノードが作成されると、そのタイプを変更することはできません。これは意図的なもので、このような情報は実際にはオブジェクトのプロパティではなく、オブジェクトの基本的なエッセンスだからです。

有効なValueTreeオブジェクトの共有

第3の方法ValueTreeコピーコンストラクタを使って、既存のValueTreeオブジェクトがある。

[1]:    juce::ValueTree myNode (myNodeType); // creates a new node of type "MyNode"
[2]: juce::ValueTree sameNode (myNode); // This object points to the same data as myNode

この結果同じノードデータを既存のオブジェクトとして使用する。

ここで重要なのは、参照以外は何もコピーされないということだ。同じインスタンスは、元のノード・データのどちらかに加えられた変更(共有ノードが有効な場合)は、もう一方のノードの検査で発見されます。

代入演算子を使っても同じ結果(ノード・データの共有)を得ることができる。

[3]:    juce::ValueTree otherNode (myNodeType); // This creates a second (new) "MyNode" node...
[4]: otherNode = sameNode; // ...but the object now points to the first instance

ここで質問がある:

で作成されたノード・インスタンスはどうなるのか?[3]で置き換えると、次のようになる。[4]?

最後には、3つの変数はすべて同じ初期インスタンスを指している。もはやValueTreeオブジェクトは2番目のインスタンスを参照している。滅びる.

を使用する場合は常にValueTreeオブジェクトが再割り当てされる(あるいはスコープ外に出る)と、そのオブジェクトが指していた基礎データは参照を失う。そのデータが他の場所に保持されていなければ、自動的に破棄される。これは、データがいつ自己破壊してもおかしくない世界で操作しているようで怖く聞こえるかもしれないが、実際には、不注意でノードを失うことはほとんどない!非常に堅牢なシステムなのだ。その上、データが重要であれば、すでにどこかに保存されていることに気づくでしょう。

警告

これは、オーディオ・コールバックのようなリアルタイム・コードでは注意が必要であることも意味する。コールバックのValueTreeオブジェクトをオーディオ・コールバックで使用すると、誤ってdelete演算子は、以前に割り当てられたデータへの参照がなくなった場合に使用する。

UndoManagerクラスについてのメモ

のアクセス関数はすべてValueTreeの使い方を理解している。UndoManagerオブジェクト。それらは適切なUndoableActionオブジェクトへのポインタを与えるだけでよい。UndoManagerオブジェクトを呼び出すと、操作可能で取り消し可能な変更記録が自動的に追加される。

これを管理するために必要な知識はそれほど多くない。Tutorial: Using an UndoManager with a ValueTree.

当面は、履歴を保存することなく、関数を利用するためにnullptrの値である。このような修正関数には、少なくともNULLポインタが与えられなければならない。UndoManagerもちろん、そのようなこともあるだろう。nullptr例で見ることができる値。

リスナーとして通知を受け取る

のリスナーとして登録する。ValueTreeを使うと、データが変更されたときに同期的に通知を受けることができる。リスナーへのポインターはValueTree従って、通常、そのコピーを取るのがベストである。ValueTreeこのように登録する前に

struct ExampleListener  : public juce::ValueTree::Listener
{
ExampleListener (juce::ValueTree v)
: tree (v)
{
tree.addListener (this);
}

juce::ValueTree tree;
};
AccessibilityRole::tree@ tree

そして、通知を受けたい動作に応じて、以下のコールバック関数を実装することができる:

  • valueTreePropertyChanged():でプロパティの変更が発生したときに呼び出されます。ValueTreeまたはその子である。
  • valueTreeChildAdded():子ノードがValueTreeまたはその子である。
  • valueTreeChildRemoved():子ノードがValueTreeまたはその子である。
  • valueTreeChildOrderChanged():子ノードがValueTreeまたはその子である。
  • valueTreeParentChanged():が変更されたときに呼び出されます。ValueTreeは親ノードから追加または削除される。

コールバックはツリー階層の上方に伝搬されるため、親ノードのリスナーは子ノードからのプロパティ変更コールバックも受け取ることになり、型とプロパティ名をチェックする必要がある。

警告

コールバックは同期的なので、時間が重要なアプリケーションではAsyncUpdaterプロパティの変更を処理する。

基本的な物件へのアクセス

プロパティの設定

についてValueTree::setProperty()関数は、有効なノードにプロパティを設定する直接的な方法の1つです。有効なノードにプロパティを設定するにはIdentifierオブジェクトで、設定したいプロパティの名前を指定します。varの値を取る:

static juce::Identifier propertyName ("name");
myNode.setProperty (propertyName, "Fluffmuff", nullptr);

物件取得

プロパティを取得するには、2つの基本的な方法があります。それぞれの方法でIdentifierで、要求する名前付きプロパティを指定する。

を使用している。ValueTree::getProperty()関数である:

juce::String name (myNode.getProperty (propertyName));
nameint UnityEventModifiers const char * nameDefinition juce_UnityPluginInterface.h:204

あるいは、添え字演算子(つまりValueTree::operator[]関数):

name = myNode[propertyName];

上記の行はどちらも、同じ結果をname変数に格納することができます。これらのプロパティはvar型である。したがって、以下のコードも有効である:

static juce::Identifier propertySize ("size");
myNode.setProperty (propertySize, 2.4, nullptr);
double size = myNode[propertySize];

ここでは、プロパティはdouble値として簡単に読み戻せます。また、既存のプロパティを異なるタイプの値で置き換えることも可能です。

個々の値をValueTreeノード(それ以上複雑なものはない)であれば、すでに始めるのに十分な知識がある!

物件を探す

構造体を使う場合は、これらのメンバ変数を直接設定したり取得したりする機能しかない。しかしValueTreeノードを使用することで、(実行時に)変数がある。

についてValueTree::getNumProperties()関数は、ノードが持つプロパティの数を示します:

int numProperties = myNode.getNumProperties();

についてValueTree::getPropertyName()関数はIdentifierオブジェクトが、指定された位置のプロパティ名を与える。これをValueTree::getNumProperties()関数を使えば、そのプロパティの内容を事前に知らなくても、繰り返し実行することができる。

for (int i = 0; i < numProperties; ++i)
{
juce::Identifier name (myNode.getPropertyName (i));
// …
}

についてValueTree::hasProperty()関数は、単に特定の名前のプロパティがノードに設定されているかどうかを知らせます:

if (myNode.hasProperty (nameProperty))
{
// property was found
}

これらの関数を使うことで、リフレクションと呼ばれる、プログラムがオブジェクトの性質を調べることができるようになります。あるオブジェクトを扱うとき、あなたのコードがそのオブジェクトを利用できるようにするには、必ずしも定義(たとえばクラス・ヘッダー)が必要なわけではない。

基本的な子供のアクセス

より複雑な構造を作りたい場合は、これらのオブジェクトを階層のノードとして使い始めることになる。

子供の追加

ノードには多くのプロパティを付加することができるが、配列のようにいくつかの子ノードを含むこともできる。

子ノードとして追加したいノードがあれば、単純にValueTree::addChild()関数を呼び出す。当然、新しい子ノードを渡しますが、その子ノードの位置も指定しなければなりません。特定の位置に子ノードを挿入する必要がない場合は、次のように指定します。-1を端に置くだけでいい。

juce::ValueTree childNode (myNodeType);
myNode.addChild (childNode, -1, nullptr);

ここではchildNodeノードはmyNode.

なお、この所有も追加参照である。もしchildNode変数を使用しても、既存のデータが失われることはない。

childNode = juce::ValueTree (myNodeType);
myNode.addChild (childNode, -1, nullptr);

もはや何もないのかもしれないがValueTreeそのインスタンスを直接指している直属のスコープ内のオブジェクトは、元のノード内で生かされている。

子供を得る

子ノードを取り出す方法はいくつかある。どのようなタスクに対してどの方法を選択すべきかは、選択した構造がどのように構成されているかに依存します。

これらすべてにおいて、子プロセスに対応しないリクエストは無効なオブジェクトを返します。

についてValueTree::getChild()関数は、ノードの内部リスト内で指定された位置にある子を返します。常に末尾に追加している場合は0は最初に入れたものに対応する:

childNode = myNode.getChild (0);

についてValueTree::getChildWithName()関数は、指定された名前識別子をノード型として持つ最初の子を返します:

childNode = myNode.getChildWithName (myNodeType);

についてValueTree::getChildWithProperty()関数は、指定されたプロパティが指定された値に設定されている最初の子を返します:

childNode = myNode.getChildWithProperty (nameProperty, "Fluffmuff");

他の条件(例えば、マッチするノード・タイプとプロパティ値の組み合わせ)で子を検索する独自の関数を書くのは簡単です。

また、任意のノードの現在の所有者を取得するにはValueTree::getParent()関数である:

ValueTree parent (childNode.getParent());
ValueTreeA powerful tree structure that can be used to hold free-form data, and which can handle its own undo ...Definition juce_ValueTree.h:84

概要

このチュートリアルではValueTreeクラスと関連するクラスがあります。特に

  • の重要性を知る。varそしてIdentifierクラスである。
  • どのように作るかValueTreeのオブジェクトを渡している。
  • のプロパティを追加してアクセスする方法ValueTreeオブジェクトがある。
  • 子ノードをValueTreeオブジェクトがある。

参照