倭マン's BLOG

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

はじめての幻獣 Griffon 研 (25) : マルチ MVC への道 (2) : MVC グループをコーディングする

今回は、前回作成した「MonolineFunction」 MVC グループのコーディングを行います(一覧)。 見た感じはこんなのでした:


当面はこのグループ自体には処理を実装しないので、Controller はひとまず置いておきます。 また、この View を内部に含む「FunctionPlotter」 MVC グループも変更する必要がありますが、それは次回以降に見ていくことにして今回は触れません。

MonolineFunctionModel.groovy


まずは Model。 この Model に持たせる情報は、JFreeChart でグラフに描画できるデータを生成するのに必要な情報です。 もう少し具体的に言うと、JFreeChart で関数を表すインターフェース Function2D を通して XYSeries オブジェクトを生成するのに必要な情報です。 この情報は

プロパティ名 説明
name String 関数名。 凡例にも使用。
function String 関数の内容を表す Groovy コード。
samples int サンプル数。

です。 Model のコードはこんな感じ:

package functionplotter

class MonolineFunctionModel{

    @Bindable String name
    @Bindable String function
    @Bindable int samples = 1000

    // org.jfree.data.xy.XYSeries オブジェクトを生成するのに関連するメソッド
}

簡単のため View から値を変更できるのは function プロパティのみとしますが、他のプロパティも @Bindable アノテーションを付加しています。 また、各プロパティの値の初期化は以下のようになっています(いくらか先取りしている部分もありますが):

プロパティ 初期化方法
name MVC グループが作成される際に渡されるパラメータによって「'f(x)'」に初期化される(次回以降に見ます)
function 以下の MonolineFunctionView から「'sin(x)'」に初期化される
samples 1000」に初期化される

我ながらいろいろな初期化方法が出てきて満足:-) 上記のどの方法でも(さらに他の方法でも)初期化されない場合は null 値が設定されるのでしょう。

MonolineFunctionView.groovy


さて、次は View のコーディング。 View はこんな感じにするのでした:

コードはこんな感じ:

package functionplotter

import net.miginfocom.swing.MigLayout

panel(id:'content', border:BorderFactory.createTitledBorder('Groovy'), layout:new MigLayout()){

    label text: bind(source:model, 'name')
    label '='
    textField 'sin(x)', columns:15, text: bind(target:model, 'function')
}

ちょっといくつか注意を(追記の項にもっとシンプルなコードあります)。

コンポーネントの id

コンポーネントid を付けることで Controller などからそのコンポーネントを参照できるようになります。 例えば、上記の content で指定されるパネルに Controller からアクセスするためには

class MonolineFunctionController {

    def model
    def view

    def someAction = { event = null ->
        def content =  view.content
        // content に対する処理
    }
}

とします(これは以前やりましたね)。 このような id の設定は、他にも下記のような方法があります:

content = panel(border:BorderFactory.createTitledBorder('Groovy'), layout:new MigLayout()){ ... }

Griffon に付属しているサンプルではむしろこちらの方が多く使われています。

Model のプロパティからのバインド

1つ目のラベルは関数名を表示させます。 これは Model の name プロパティの値が変更されれば、その変更をこのラベルへ反映させるようにしたいので、バインドには source を指定します:

    label text: bind(source:model, 'name')


図中の矢印は「反映の方向」です。

Model のプロパティへのバインド

3つ目のテキストフィールドは関数の内容を入力させます。 これはテキストフィールドの内容を Model の function プロパティへ反映させるようにしたいので、バインドには target を指定します(これも以前もやりましたね):

    textField 'sin(x)', columns:15, text: bind(target:model, 'function')

バインドに target を指定するには

    bind{ model.function }

とすることも可能ですが、今の場合のこれを行うと 'sin(x)' による初期化が上手く Model に反映されませんでした。 単なるバグかな?

まぁ、ともかく「MonolineFunction」 MVC グループのコーディングはこれでひとまず終了。

追記


View 部分のコードに関してコメント(Andres Almiray 氏から!?)頂いたので、少々(というか大々的に?)修正。 修正箇所は

  • BorderFactory.createTitledBorder('Groovy') → titledBorder(title: 'Groovy')
  • layout:new MigLayout() → migLayout() (import 文も削除)
  • bind(source:model, 'name') → bind{ model.name }

です。 コードはこんな感じ(コメント欄のコピペ):

package functionplotter

panel (id: 'content', border: titledBorder(title: 'Groovy')) {
    migLayout()
    label text: bind{ model.name }
    label '='
    textField 'sin (x)', columns: 15, text: bind (target: model, 'function')
}

おぉ、スッキリ! クロージャで bind を指定できるのは source を指定する場合だそうです。 完全に勘違いしてました ^^;) 'migLayout()' で MigLayout の import 文がいらなくなるのも嬉しい。

Griffon in Action

Griffon in Action