倭マン's BLOG

くだらない日々の日記書いてます。 たまにプログラミング関連の記事書いてます。 書いてます。

はじめての幻獣 Griffon 研 (26) : マルチ MVC への道 (3) : アプリケーション開始時に MVC グループを作成し、ビューを埋め込む createMVCGroup / mvcGroupInit

今回は、前回作成した「MonolineFunction」 MVC グループを、それまで作成していた「FunctionPlotter」 MVC グループに埋め込みます(一覧)。

今回扱う埋め込みはアプリケーション開始時に行われ、アプリケーションが実行状態になればあまり他の部分との違いはありません(少なくとも View の挙動は)。 気持ち的には、埋め込むというより、もともとのアプリケーションが複雑になってきたので1つの MVC グループに切り出したといった感じでしょうか。 正確ではありませんが「コンポジション」とか「静的」といったイメージの埋め込みです。

この埋め込みに必要なコードは

  • View での widget() による埋め込みコード
  • Controller#mvcGroupInit() での MVC グループの生成 (createMVCGroup())

の2つ。

FunctionPlotterModel.groovy


とその前に、埋め込みには関係ありませんが、前回 FunctionPlotterModel のいくつかのプロパティを MonolineFunctionModel へ切り出したので、切り出された方の FunctionPlotterModel の方を載せておきます:

package functionplotter

class FunctionPlotterModel {

    @Bindable double from
    @Bindable double to
    @Bindable double min
    @Bindable double max

    def copyProperties(){
        return [from:from, to:to, min:min, max:max]
    }
}

残ったプロパティはグラフを描画する変域に関するものだけです。 copyProperties() メソッドはグラフ作成を別スレッドで行うためにプロパティ値をコピーするメソッド。

FunctionPlotterView.groovy


では気を取り直して、埋め込みの方法を見ていきましょう。 まずは View の埋め込み。 Griffon 0.9.5 以降を使用している場合は「追記」を参照

埋め込まれる側の MonolineFunctionView はルート・コンポーネントを JPanel として作成しましたが、FunctionPlotterView にはそれを子コンポーネントとして付加されるコンポーネントを用意する必要があります。 ここでは 'functionSection' と id を付けたパネル (JPanel) にします。 コードはこんな感じ:

package functionplotter
...    // actions など
application(...){
    ...  // メニューバー
    panel(border:emptyBorder(6)){
        migLayout()

        panel(id:'controlSection', constraints:'west', border:emptyBorder(3)){
            migLayout()

            // この JPanel の子コンポーネントとして MonolineFunctionView を埋め込む
            panel(id:'functionSection'){
                migLayout(layoutConstraints:'wrap 1')
                widget(app.views.f.content)    // MonolineFunction の View を埋め込むコード
            }
            ...  // 'Paint All' ボタン
        }
        ...    // チャート描画部分
    }
}

ちょっとゴチャゴチャしてますが、アプリケーションの実行結果で説明するとこうなってます:

  • controlSection パネルは埋め込みには特に関係ありません。(じゃあ書くなって?)
  • また、MonolineFunctionView を埋め込むために作った functionSection パネルは、今回行う埋め込みには id を指定する必要はありません(説明と今後のため id を指定しました)。

埋め込みを行うコードは

widget(app.views.f.content)

の部分です。 widget() は親コンポーネントに引数のコンポーネントを付加するメソッドで、お呪い(?)みたいなものです。 引数の部分は要説明:

app GriffonApplication オブジェクト。
views GriffonApplication 内に存在する View 全て*1の Map。
f MVC グループのインスタンスを特定する識別子(以下の FunctionPlotterController 内で指定)。
content MonolineFunctionView 内で指定した id。

となってます。 ある MVC グループ内のコードから他の MVC グループ内のオブジェクト、プロパティなどにアクセスする場合には主にこの方法を用いるようなので、このアクセス方法は超重要

ちなみにこんな感じに View にアクセスすることもできます*2

widget(app.groups.f.view.content)

FunctionPlotterController.groovy


残るは Controller の処理。 Controller では mvcGroupInit() メソッド内に、埋め込む MVC グループを生成するコード (createMVCGroup()) を書きます*3Griffon 0.9.5 以降を使用している場合は「追記」を参照)。 FunctionPlotterController のコードはこんな感じ:

package functionplotter

class FunctionPlotterController {

    def model
    def view

    void mvcGroupInit(Map args) {
        createMVCGroup('MonolineFunction', 'f', [name:'f(x)'])
    }
    ...
}
  • mvcGroupInit() メソッドは(この Controller が属する)MVC グループのインスタンスが作成されたときに呼び出されます。 返り値は void。 一部ドキュメントでは def で宣言しているものもありますが、Griffon 0.9.2-beta-3 ではコンパイル・エラーになります*4
  • createMVCGroup() メソッドに関して
    • 第1引数で、埋め込みたい MVC グループの名前(ここでは 'MonolineFunction')を指定します。 これは Application.groovy に登録されている名前です。
    • 第2引数で、この MVC グループの groupId を指定します。 ここでは 'f' としていますが、ダブらないような名前を付ける方がいいでしょう。 1つの MVC グループに関して複数のインスタンスを作成することができるので*5、それらを識別するためにこの groupId を使用します。
    • 第3引数で、作成した MVC グループの初期化に使用するパラメータを指定します。 

第3引数の初期化パラメータは次の2つに使われているようです:

  • Controller (今の場合は MonolineFunctionController)などに mvcGroupInit() メソッドを定義した場合、そのパラメータとして渡される。
  • Model, View, Controller のプロパティに Map のキーと一致するものがあれば、その値でプロパティを初期化する。

今の場合、[name:'f(x)'] をパラメータとして渡していますが、これによって MonolineFunctionModel の「name」プロパティが 'f(x)' によって初期化されるようです。 同様のことは View でもできるようで、id:masanobuimai さん「MVC Groupの切り替えを分かった気になってやってみた。」の記事中で DetailPanelView の rootPane などがパラメータの値で初期化されてます。

mvcGroupInit() メソッド

mvcGroupInit() の引数で渡されるパラメータは、デフォルトで以下のようなエントリーがセットされています:

パラメータ名 パラメータ値(上記の例での)
mvcType 'MonolineFunction' (グループ名)
mvcName 'f' (groupId)
app griffon.swing.SwingApplication オブジェクト
model MonolineFunctionModel オブジェクト
view MonolineFunctionView オブジェクト
controller MonolineFunctionController オブジェクト
builder org.codehaus.griffon.runtime.builder.UberBuilder オブジェクト
execSync
execAsync
execOutside
execFuture
isUIThread

これらの値もプロパティ(フィールド)の値の初期化に使えます:

package functionplotter

class FunctionPlotterController {
    def mvcName    // 他の設定をしなくても 'f' がセットされる(上記の例で)
    ...
}

createMVCGroup() メソッド

最後に createMVCGroup() メソッドに関して軽くまとめておきます。 createMVCGroup() メソッドはいくつかオーバーロードされています:

createMVCGroup(String groupName)
createMVCGroup(String groupName, String groupId)
createMVCGroup(String groupName, Map params)
createMVCGroup(String groupName, String groupId, Map params)

シグニチャをまとめると下表の通り:

引数 必須 説明
groupName String Yes MVC グループの名前。 Application.groovy に登録されている名前。
groupId String No グループ ID。 MVCインスタンスにアクセスする際に指定するキーとして使う。
params Map No MVC インスタンスの初期化に使用するパラメータ。

また、返り値は生成された Model, View, Controller の List です(順序もこの通り)。 使う場合は以下のように使うのが通例のようです:

def (m, v, c) = createMVCGroup('MonolineFunction', 'f', [name:'f(x)'])

結局のところ


何か思ったよりも説明がゴチャゴチャしちゃいましたが、MVC グループをコーディングしてしまえば、埋め込むのに必要なのはほんの数行だけです:

// FunctionPlotterView.groovy
widget(app.groups.f.view.content)

// FunctionPlotterController.groovy
void mvcGroupInit(Map args) {
    createMVCGroup('MonolineFunction', 'f', [name:'f(x)'])
}

追記


Griffon 0.9.5 では上記のコードではうまくいかないようです。 FunctionPlotterView で widget() ノードによって MonolineFunction グループの view を埋め込む時点で、まだこの MVC グループがインスタンス化されていないのが原因のようです(これを考えると、以前にこのコードがなぜ動いてたか疑問でもありますが・・・)。 幸い数行のコード変更でこれを修正できます。 やることは MonolineFunction の view の埋め込みを FunctionPlotterController で行うだけです。

// FunctionPlotterView.groovy
application(...){
    ...
                panel(id:'functionSection'){
                    migLayout layoutConstraints:'wrap 1'
                    // 上記のサンプルではここに widget() ノードを書いてましたが、それを削除
                }
}

// FunctionPlotterController.groovy
void mvcGroupInit(Map args) {
    def (m, v, c) = createMVCGroup('monolineFunction', 'f', name:'f(x)')
    view.functionSection.add(v.content)    // Controller から埋め込みを実行
}

埋め込み処理を書いているコードは FunctionPlotterController です。 MonolineFunctionController ではありません。 ここでやっていることは、次回に見る「アプリケーション実行中に MVC グループを作成して埋め込む」方法の、埋め込み処理を mvcGroupInit() メソッド内でやっているだけです。

Griffon in Action

Griffon in Action

*1:groupId を指定せずに生成した MVC グループは登録されているかどうか確かめていませんが、アクセスするなら groupId を指定しておくべきでしょう。

*2:groups プロパティは GriffonApplication 内に存在する MVC グループ全てを Map として返します。 キーは各 MVC グループを生成したときの groupId

*3:MVC グループを生成するには buildMVCGroup() メソッドも使えますが、こちらは MVC 以外に actions や dialogs などのメンバがある場合に使うそうです。 機会があればそのうちに。

*4:おそらく、Griffon の最近のバージョンで Controller などのクラスが Java クラスを継承するように変更されたためかと。

*5:この意味で、MVC グループは「型の定義」といえますね。