<前文>
で勉強しています。今回は、「9章8節 カスタム化したSWidget/UWidgetを作成する」を勉強します。
<本文>
<目的>
今まで、あまり教科書のレシピの前文は読んだ事がなかったのですが、レシピの目的をきれいにまとめてありました。
(ア) 今までは、既に存在するウィジェットを使用してカスタム化したUIを作成していました。しかし沢山のUIを一緒にしたコンポジションを使用した方が便利な場合が多々あります。具体的に言えばテキストボックスを既に持ったボタンとかです。
(イ) サブウィジェットで構成される合成物であるオブジェクトを宣言しないで、手動でC++の階層を特定した場合UMGを使用したグループをウィジェットとして初期化する事は出来ません。
ので、今回、沢山のウィジェットを含んだSWidgetの作成と、そのSwidget内のプロパティ―の操作方法と、UMG上で使用出来るSWidgetの混合物であるUWidgetラッパーの作成方法を学習します。
――コメント――
今回、前文の中にComposition、Compound、Consisting ofと似たような単語が出て来てました。これらの単語はこのレシピで何回も使われそうなので、ここでしっかりと訳しておきたいと思います。
Composition: 組成。(composite:混合物)
Compound: 化合物
Consisting of: 構成される
化学ならば、単に二種類以上の物質を混ぜた場合がcompositeで、二つ以上の元素もしくは分子を化学反応させて新しい物質を作成した場合はcompoundに成ります。Consist ofはどちらの場合も使えるみたいですね。で、コンピュターサイエンスの場合はどうなんでしょうか?
この例では、compositionは、「沢山のUIの要素を集めた構成(composition)」となっています。
この例では「サブウィジェットで構成される(consisting of)合成物(compound)」となっています。上に記した2つの文章は、compositionとcompoundをそれぞれ使用しているにもかかわらず、文脈上、同じものを違う言い方で表してるだけでした。更に、この文で使用する場合は、composition(組成)ではなくcomposite(混合物)でしょう。組成を使用する事は出来ません。この教科書の作者は、compositionとcompositeを間違えて覚えているのでしょう。ここでの訳はこうします。
Composition, composite, そしてcompoundは、構成物と訳します。Consisting ofは構成されると訳します。
正確ではないですが、ここの訳でうまくいくはずです。
――コメント終――
<方法>
Step.0
今回も、新しいクラスを作成します。Versionは20.0を使用します。と思ったら、Version 20.1が既にリリースされていました。のでVersion 20.1を使用します。Project名は、Chapter9part8_0731とします。
今回もSlate、SlateCoreモジュールを使用するはずなので、Build.csファイルから以下のコードをアンコメントします。
Step.1
はい。
Step.2
以下に示すように、追加しました。
Step.3
まず、UE4COOKBOOK_APIと書かれているので、クラスウィザードから作成しかつpublicが選択されたと考えられます。親クラスはSCompoudWidgetです。
そこで、CompoundWidgetで調べて見ますが該当するクラスはありませんでした。
VSから作成します。
名前はCustomButtonとします。アドレスはSourceファイルに変更しました。
コードを一々写すのは面倒なので、Packet Publish社が提供しているサンプルコードをそのままコピーペイストします。ただし、publicではないので、project名_APIの部分は端折りました。FOnClickedがエラーになっていたので、Include にSlateDelegates.hを追加しました。
次のStepに行きます。
うーん。良く分からないので、サンプルコードをそのままコピーします。Engine.hを追加し、Chapter9.hは外しました。更にSBoxPanel.hもインクルードしました。
試しにビルトしてみます。
ビルド出来たのでとりあえず、次に行きます。
Step.5
クラスウィザードにWidgetがあったのでクラスウィザードで作成してみます。
名前はCustomButtonWidget、privateを選択で作成します。
Step.6
以下に示すようにインクルードを追加しました。
Step.7
以下に示すように追加しました。
すっごいエラーになっています。
以下に示すように追加しました。
Step.9
以下に示すように追加しました。
Step.10
以下に示すようにサンプルコードからコピーしました。
とりあえず、ビルドすると、
ビルト成功しました。
<結果>
Step.11
以下に示すようにしました。
Step.12
はい。
以下に示すように見つけました。
Step.14
しました。
Step.15
変えました。
Step.16
しました。
Step.17
はい。
Step.18
しました。
エラーになってしまいました。
ウーン。どうも「UE4Editor-UMGEditor.pdbがあるフォルダーのパスが見つからない。」といっているみたいです。ならば、UE4Editor-UMGEditor.pdbを探してそのパスを追加すればいいじゃないか?と思ったのですが、何か突然、アルゴリズムのクラスで、目の前の問題ばっかし解いても全体として最適でない回答を得るパターンがあったのを思い出したので何か躊躇してます。何となくですが今回のエラーは結構直すのが大変な気がしています。
まずは、Googleで調べて見ましょう。UE4Editor-UMGEditor.pdbで調べて見ました。
152しか引っかからない。何か間違っているような気がしますが、とりあえず、最初から読んでいきます。
直接関係はないですが、ここに以下に示す回答がありました。
実はSCustomButtonクラスを作成する時、何故かファイルがIntermediateフォルダー内に作成されて、後でSourceフォルダー内に移動していました。重要ではないと考え記述しなかったのですがそれがこの問題を引き起こしているのかもしれません。
残りの検索結果はあまり参考になるものは見つかりませんでした。実行する前にもう少し情報が欲しいです。
次に、普通のボタンを配置してそのボタンをバインドしてもエラーになるか調べて見ます。ボタンにはバインドがないので、ボタン上に、テキストを配置してそのテキストのコンテントからBindしました。
普通に出来ました。
ウーン。とりあえず、リセットをしてみます。
ファイルを消します。
.uprojectを開きます。そしてVSプロジェクトファイルを作ります。
もう一度、トライしてみます。
おお。出来ました。危なかった。もしUE4Editor-UMGEditor.pdbを追加する処から始めていたら治らなかったかもしれません。
ちょっとしらべたところ、UE4C++でクラスを作成した時に、同時にgenerated.hファイルが作成されます。この後、作成したクラスのファイルの場所を変えても、generated.hはそれを知る事が出来ないので、エラーを引き起こす場合があるそうです。これが原因の場合は、上記の方法でプロジェクトをリセットする必要があるそうです。
Step.19
しました。
Step.20
しました。
Step.21
Step.20に示すように出来ています。
Step.22
こうなりました。
Step.23
はい。教科書の図に沿って作成しました。
Step.24
はい。たった今作成した新しいウィジェットの名前は、NewWidgetBlueprintです。
ので、以下に示すように、ClassからNewWidgetBlueprintを選択します。
Step.25
はい。
Step.26
はい。
Step.27
はい。
Step.28
はい。以下に示したように、出来ました。
<考察>
今回もHow it works…を読んでいきます。
基本的には、1.は
(ア) UWidgetはUMGモジュール内で定義されています。
(イ) UWidgetを使用するためにはbuild.csにUMGをインクルードする必要がある。
と言っています。以下に示すbuild.cs内のUMGモジュールを追加するためのコードが何故必要なのかについての解説です。
UCustomButtonWidgetクラスについての解説でしょうか?UCustomButtonWidgetには、以下に示すようにSWidgetがあります。
SCustomButtonを見ると、以下に示すように親クラスがSCompoundWidgetです。
また、ここでCompoundと言う言葉が出て来ました。ただしここでは、CompoundWidgetというクラスがあり、そのクラスの派生クラスを指す場合に使うようです。なので、SCompoudWidgetクラスについて調べて見ました。
まず、継承の階層からSCompoundWidgetクラスは、SWidgetクラスのサブクラスです。
Compound Widgetはほとんどの原始的でないウィジェットを作るための基礎である。
CompoundWidgetはChildSlotと言う名のプロテクトされたメンバーを持っている。
CompoundWidgetと言う名のクラスが存在する事は分かりました。そのクラスを継承したクラスは、2つ以上のウィジェットをそのクラス内で宣言して、一つのウィジェットとして扱えるようにしているみたいです。この場合、2つの物質を化学反応させて新しい一つの物質を作成した場合に似ています。のでCompoundが使用されているのだと思います。2つ以上のウィジェットを合体させて、新たな一つのウィジェットを作成するのですから、化合物(Compound)ですね。
はい。3.で述べた通りです。ただし、ここでウィジェットの階層をカプセル化してとあります。この意味が実際のコードの内側にある論理の説明なのか、それともそのために何か特別にコードを足さないといけないのかはまだ分かりません。
まず、5.ではSLATE_BEGIN_ARGSとSLATE_END_ARGEマクロを使用しているとあります。以下に実際のコードを示します。
それで、SLATE_BEGIN_ARGSとSLATE_END_ARGEマクロについて調べて見た所、直接その二つの機能を解説した文章は、ここしか見つからなかったです。そこでは、以下のように解説されていました。
SLATE_BEGIN_ARGSとSLATE_END_ARGEマクロは、SLATE_ARGMENTとSLATE_EVENTマクロで追加された変数を持つstructを作成する。
SLATE_BEGIN_ARGSとSLATE_END_ARGEマクロがStructを持つ事は分かったのですが、それ以上の情報は見つけられませんでした。
はい。使われています。サンプルコードは見にくいので、整理したものを下に示します。
サンプルコードではSLATE_ATTRIBUTEに与えたタイプはFStringとLabelです。これらにそれぞれTAttributeが作成されるのでしょうか?それとも一個のTAttributeが作成されるのでしょうか?とりあえず、TAttributeクラスのAPIを見てみます。
属性オブジェクト
とだけ説明されていました。どこかで一回定義を調べた記憶があるのですが、忘れてしまったので、もう一度調べて見ます。このサイトによると、
一般的に、属性とは特徴や特性を指します。例えば色は髪の属性です。
使用に関してもしくはコンピュターのプログラミングにおいては、属性は変える事の出来るプロバティーもしくは
違う値をセットされたプログラム内のいくつかのコンポーネントの特性です。
とありました。ウーン。単なるプロパティとどう違うのでしょうか?
間違えて理解していました。FStringがタイプで、Labelはその名前だったみたいですね。
更に、TAttributeのシンタックスで、以下のように紹介されていました。
SLATE_ATTRIBUTEが変数やオブジェクトを担当して、SLATE_EVENTが関数を担当するのかな?と漠然と考えていましたが、SLATE_EVENTはイベントなのでデリゲート担当でした。まあデリゲートは必ず関数を含みますので、また大体合ってました。
はい。以下にその部分のコードを示します。
他の今回のレシピでは使用しないが、良く使うマクロの紹介ですね。
Constructについての解説です。以下にコードを示します。
まず、FArgumentsについて調べて見ます。FArgumentsのAPIはここにあったのですが、Remarkは載ってなかったです。
実装部も示します。
これは普通ですね。解説の「ウィジェットが自分で初期化出来る実装」については良く分かりません。
以下に13.で解説されている部分のコードを示します。
ここで、属性オブジェクトの作成に詳しい人なら、アンダースコアの無いTAttributeやFOnClickedのインスタンスに驚いたりするのかも知れませんが、私には全然分かりません。正直このクラス自体が良く分かっていません。
突然、気が付いたのですが、const FArguments& InArgsは、前述のマクロで作成したStructの事みたいですね。そのマクロで作成されたstructの保持している値を、このクラス内のプロパティにも保持させているのですね。
はい。これも13.で述べた通りですね。以下に実際のコードを示します。
ああ、分かりました。アンダースコアがついているLabelやButtonClickedはsturctの方で、ついていないのはSCustomButtonクラスのメンバー変数の方なんですね。
以下に15.が解説している箇所のコードを示します。
.OnClickedにButtonClicked、Text_Lamba内のコードで表示するテキストの文字を決定する個所でLabelが使用されています。
はい。ウーン。絶対ラムダを使用しないといけないんでしょうか。別な方法でも出来そうな気がしますが。試してみたいですしまた試すべきなんでしょうが、この辺りのコードを何の参考もなく自分だけで正しく書ける自信はまだないので、今回はパスするしかないです。
17.は以下の部分のコードについての解説です。
全然問題ないですね。単にこの部分のコードの解説ですし。うーん。読んで理解するだけならば簡単なんですね。実際にコードを自分で書くとなると…。
最初の文の訳から躓いています。「Now that we have our SWidget declared…」を「今SWidgetを宣言したので…」と訳すと事実に反します。SWidgetの宣言はUWidgetを継承したUCustomButtonWidgetクラスで行われ、そのUCustomButtonWidgetクラスはこれから作成しますから。だから「…SWidgetを宣言します。」と訳しました。
今度は、UCustomButtonWidgetクラスについての解説です。以下に示すように、UCustomButtonWidgetクラスはUWidgetクラスのサブクラスです。
18.はこの部分の事を「UWidgetのオブジェクトのラッパーを作成する必要があります。」と評しているのでしょうか?これによってUMG上でこのクラスが使用出来るようになるのでしょうか?断言するには、まだ情報不足ですね。
What You See Is What You Getは懐かしい言葉です。大学の研究室にいた時、カッコつけて使っている奴がいました。
そうです。これが不思議なんです。何でUWidgetクラス何ですかね。後、18.で述べているように、UWidgetクラスを継承するとUMG上でこのクラスが使用出来るようになるのでしょうか?疑問は尽きないですね。そこで気が付いたのですが、UWidgetクラスのAPIをまだ見ていなかったです。
これはUObjectに晒されたラップされたSlateコントロールのための
ベースのクラスです。
ウーン。このRemarkのUObjectをUMG上と変換すると、18.の解説と全く同じですね。しかし新たな情報は得られなかったとも言えますね。
以下に20.で解説している部分のコードを示します。
最初に、SWidgetクラスのAPIを調べます。
長すぎる。以下に、分けてまとめました。
読むに当たって再確認しとくべきなんですがSWidgetについて何も知らないです。だからSWidgetの情報なら何でも欲しいと言う部分があります。簡潔かつ有用な説明があれば一番いいですが、ざっと見てもこのremark
は簡潔かつ有用なSWidgetの目的や機能は説明していないです。
最初からスゴイです。SLATE_ATRIBUTEは廃止します。FArguements& IsChecked()を使用して曖昧な宣言を防いでください。と説明されています。完全にSWidgetについて何も知らない人が最初に読む内容ではないです。ですが情報がなにもないよりは遥かにいいですので読み進めます。
この場合、ウィジェットとはSWidgetの事でしょう。SWidgetはそもそも継承される事を考えて作成された訳ではないのですね。この部分の説明はHow it works…の19.の説明と一致します。しかしその後「LeafWidget もしくはPanelを使用してください」と言っています。今回のサンプルでは、
TSharedRef<SWidget>を使用しています。LeafWidgetを調べて見ると、
となっていて、単なるSWidgetの子クラスです。ここでの解説と、今回のサンプルの解説は一見似ていますが全く違う事を述べているのでしょう。
一応、Panelの方も調べて見ます。Panelが何を指すのかは不明ですがグーグルに“Panel unreal document”で検索すると一番最初に出てくるのがUPanelWidgetのAPIです。
このUPanelWidgetはUWidgetの子クラスなのでこちらの例はひょっとすると今回のレシピにおけるSWidgetの使い方と同じかもしれません。
ところが、SWidgetのAPIを見てみたら、
と成っていて、SPanelというのがありました。こっちの方の説明をしていたと考える方が理にかないますね。Panelがこちらの説明だった場合は、Panelの場合もLeafWidgetと全く同じ結論になります。つまり、この部分の説明は今回のレシピとは全く関係ないです。
おお!初めてSWidgetクラスの目的が書かれた文に当たりました。これが知りたかったのです。
まず、「SWidgetが全てのインターアクティブなSlateの基礎に当たるクラスである。」と述べています。インターアクティブとは双方向性と言う意味です。例えばプレイヤーがボタンをクリックしたらSlateがそれに対して何かをする場合、そのslateはインターアクティブなSlateに成ります。今までのレシピは、既に存在するウィジェットを使用していたのでそのウィジェットがどのようにして双方向性を獲得していたのかは、全く知りませんでしたがそれをSWidgetが担当していたようです。更に「その結果、SWidgetのインターフェイスはウィジェットが出来る全てを記述する必要が生まれ、かなり複雑になってしまいました。」とありました。これも良く分かります。
正直ここの説明だけで十分だったのですが、ここまで訳してしまったので、最後の文章も訳しておきます。
では、20.に戻ってもう一度、以下のコードを見ています。
まず、オーバーライドしているので、UWidgetに基になる関数があるはずです。それのAPIを見てみましょう。
基礎となるSWidgetの構築が必要な時に、UWidgetの全てのサブクラスで実装されるこの関数は呼ばれます。
ここまでの解説で大体全体像が見えて来ました。まずカスタム化したウィジェット、このレシピの場合はテキストボックスを持つボタン、を作成する場合はSWidgetクラスが必要です。しかしSWidgetクラスは直接継承出来ません。のでUWidgetクラスを継承したクラスを作成して、UWidgetクラスの関数、RebuildWidget()をオーバーライドした関数を使ってSWidgetを使用します。
以下に示す部分のコードの解説です。
まず、コンストラクターがあります。次に、UPROPERTY()で宣言されたFButtonClickedデリゲートがあります。その次のOnButtonClicked()関数は21.では説明されていませんね。そして次のLabel変数は21.で説明されたようにBlueprintReadWriteとしてマークされています。その次のFGetStringはデリゲートですが、21.では特に解説はありません。
FGetStringはここで説明がありました。
以下に23.で解説されているコードを示します。
まず、SynchronizePropertiesのAPIを示します。
全てのプロパティを可能な限りネイティブのウィジェットに提供します。
これはウィジェットが構築された後で呼ばれます。
修正された状態をアップデートするためにエディターからも呼ばれます。
なので、ウィジェットのプロパティの全ての初期化をここで行う事を必ずしましょう。さもないとプロパティと可視性の状態は同期しないかもしれません。
23.の解説とAPIのRemarksによると、この関数がここで変化したプロパティをネイティブのウィジェットのプロパティに同期して同時に変化させる事を担当しているみたいです。
ここからは、UCustomButtonWidgetクラスの実装部についての話です。以下にRebuildWidget関数の実装部を示します。
24.では以下の事を述べています。
(ア) RebuildWidgetは、UWidgetが関連するネイティブなウィジェットを再構築します。
(イ) SCustomButtonウィジェットのインスタンスを構築するためにSNewを使用します。
(ウ) ネイティブなウィジェットの内部でUWidgetのOnButtonClikcedメソッドをButtonClickedデリゲートにバイントするためにSlateの宣言的なシンタックスを使用します。
まず、(ア)ですが、ここで述べているUWidgetが関連するネイティブなウィジェットとはこのUCustomButtonWidgetクラスで宣言されている新しいウィジェットの事だと思います。つまり、
の事ですね。(イ)で(ア)で述べたウィジェットの初期化を行います。この時にSNewを使用します。更に、OnButtonClikcedメソッドをButtonClickedデリゲートにバイントするためにSlateの宣言的なシンタックスを使用します。これは(ウ)で述べられている部分です。ここでも、ネイティブなウィジェットの内部でと述べられていますが、それがMyButtonを指していると考えると意味が通ります。
ここで、UCustomButtonWidgetクラスとSCustomButtonクラスの関係が分かって来ました。今までの考察から分かっている事を以下にまとめます。
(ア) 新しいカスタム化したウィジェットを作成したい場合、SWidgetクラスで作成する必要がある。
(イ) しかし、SWidgetクラスは継承出来ないので、UWidgetクラスからSWidgetクラスを呼ぶ必要がある。
(ウ) UWidgetクラスを継承したクラスを新たに作成してそこからSWidgetクラスを呼ぶ。これがUCustomButtonWidgetクラスです。
(エ) UWidgetクラスを継承したクラスに、実際にカスタム化した新しいウィジェットをオブジェクトとして宣言し初期化します。この実際にカスタム化した新しいウィジェットがSCustomButtonクラスから作成されます。
(オ) (エ)で作成された新しいウェジェットをSWidgetにパスします。これで、(ア)で述べる「新しいカスタム化したウィジェットを作成したい場合、SWidgetクラスで作成する必要がある。」が達成されました。
とうとう今回のレシピと言うか、カスタム化したウィジェットの作成方法の全体の工程が見えて来ました。
25.は以下に示す部分のコードの説明をしています。
SCustomButtonにデリゲートとしてOnButtonClicked関数をバインドしただけです。Slateの文法としても今まで散々やってきたことなので、特に不思議はないです。ここで注意しないといけないのはButtonClickedとOnButtonClickedです。OnButtonClickedはUCustomButtonWidgetクラスの関数で、ButtonClickedはSCustomButtonクラスの関数です。
26.は以下の部分のコードについて解説しています。
FReplyについては忘れてしまったのですが、確かDelegateで使用したような気がしますので、特に不思議なところはないです。
27.では以下の事が述べられています。
(ア) これはUObjectとUMGのシステムが、ネイティブなボタンのウィジェットへのレファレンスを持たないでもボタンかクリックされたと連絡される事を意味します。
(イ) それについて連絡されるためにUCustomButtonWidget::ButtonClickedにバインドする事も出来ます。
まず、(ア)ですが、これはブロードキャストについての説明だと思います。ブロードキャストすればレファレンスがなくても連絡されますので(ア)はそれを指していると考えられます。
(イ)はちょっと何を言っているのか分かりません。25.で述べた通りOnButtonClickedはUCustomButtonWidgetクラスの関数でButtonClickedはSCustomButtonクラスの関数です。UCustomButtonWidgetクラスにはButtonClicked関数はありません。あるのはOnButtonClicked関数です。ひょっとするとUWidgetクラスがその関数を持っているのでしょうか?調べて見ましたがありませんでした。更にその上のクラスであるUObjectにもありませんでした。
ウーン。(イ)については明確に間違いと断定しましょう。
FReply::Handled()について調べて見ます。まず、APIによると、
このイベントはハンドルされたとシステムに知らせるためにFReply::Handledを返します。
はい。28.と全く同じ事が述べられていました。
はい。SynchronizeProperties関数についての説明です。以下にSynchronizeProperties関数の実装部を示します。
29.では最初にSuper::SynchronizeProperties();が呼ばれる理由が説明されています。今まで、散々勉強した部分ですね。
30.は、TAttribute<FString> LabelBinding = OPTIONAL_BINDING(FString, Label);についての解説です。以下の事が述べられています。
(ア) UWidgetクラス内のLabelDelegateデリゲートを、実際にはネイティブなボタンのラベルであるTAttributeにリンクするためにOPTIONAL_BINDINGマクロを使用します。
(イ) OPTIONAL_BINDINGマクロはそのマクロの2番目のパラメーターに基づいてデリゲートがNameDelegateと呼ばれる事と思っています。
(ア)はTAttribute<FString> LabelBinding = OPTIONAL_BINDING(FString, Label);の文字通りの解説ですね。ここで、またネイティブな…と言う表現がありました。ここでネイティブと言う場合は、常にUCustomButtonWidgetクラスで宣言されたウィジェットと言っていましたが、それだとここでは、意味が通らないです。ここで、ネイティブなウィジェットと言う場合は、SCustomButtonクラスで宣言されたウィジェットでなければ意味が通らないです。ウーン。ネイティブなウィジェットとは何を指すのか?をもう一度考えないといけないみたいです。
その他にもおかしい部分があります。(ア)で述べられているLabelDelegateデリゲートはここでは使用されていません。というかこのクラスで宣言はされていますがどこにも使われていません。(イ)のNameDelegateについてはそんな名前のデリゲートは宣言していません。
ここら辺は、正しい解答を導く事は出来るでしょうが、大変な労力と元になる知識が必要と考えられます。今回はこれまで得られた知識で我慢すべきと思いますので、31.の考察はここでやめます。
はい。ここだけの説明ならば意味は分かります。しかしOPTIONAL_BINDINGについて調べて見ると、ここに、
OPTIONAL_BINDINGは廃棄し、PROPERTY_BINDINGに変えてください…
と書かれています。ここで紹介されているOPTIONAL_BINDINGがこのレシピで使用されているOPTIONAL_BINDINGなのかも分かりません。
これだけ長い文章だと、もう訳すの無理…と言いたいです。変ですよこの文章!何が言いたいんですかね。
<まとめ>
今回は、SWidget/UWidgetを使用してUMG上で使用出来る新しいウィジェットを2つの元からあるウィジェットを使用して作成する方法を学びました。
(ア) インターアクティブな機能を持つウィジェットは全てSWidgetから作成しなければなりません
(イ) しかしSWidgetクラスは継承するように作られていないので、UWidgetクラスから呼び出す必要があります。その場合SWidgetはオブジェクトとして呼び出されます。
(ウ) 一方で、SCompoundWidgetクラスを継承したクラスを作成し、そのクラス内に2つのウィジェットを持たせます。
(エ) 最後にSCompoundWidgetクラスを継承したクラスをSWidgetクラスを初期化する時にパスします。
更に、SCompoudWidgetクラスを継承したクラスはその作成にSLATE_BEGIN_ARGS、SLATE_END_ARGSを使用してstructを作成しなければいけないなど、更に細かいルールがありますが、基本的には上記の方法で作成します。
<おまけ>
なしです。と言おうと思ったのですが、このブログを公開するに当たってもう一度読み直していたら、SCompoundWidgetクラスはSWidgetクラスのサブクラスと述べていました。すっかり忘れていました。これは結構重大な事で、まとめで述べた(ア)、(イ)、(ウ)、そして(エ)の解釈より、より正しい解釈が以下に示すよう成り立つと思われます。
(ア) インターアクティブな機能を持つウィジェットは全てSWidgetから作成しなければなりません
(イ) しかしSWidgetクラスは継承するように作られていないので、SWidgetクラスのサブクラスであるSCompoundWidgetクラスを継承してクラスを作成し、そのクラス内に2つのウィジェットを持たせます。
(ウ) 一方で、...
やっぱり、止めます。これは、おまけで済ませられる内容ではないです。今回はこのレシピの初めての勉強でしたので、ここまでが成果です。として一区切りし、次回もう一度このレシピを勉強します。次回は、SCompoundWidgetクラスはSWidgetクラスのサブクラスと言う事実に基づいてこのレシピを勉強し直します。