前回でマルチ MVC は終了の予定でしたが、ちょっと追加記事(一覧)。
短寿命の MVC グループ
今まで作成した MVC グループはアプリケーション開始時、もしくはアクション実行時にインスタンスが作成され、View は全体として、もしくはその部分に埋め込まれて存在してました。 ただしこれは必須条件ではなく、ダイアログやウィザードのような短期間しか存在しない(使い捨ての)オブジェクトに対しても MVC グループを使用することもできます。
で、まぁ MVC グループの生成や破棄の方法は前回までにやったのと全く同じ方法でできますが、一連の
という処理はクロージャを用いるとパターン化できます。 具体的には
def withMVC(String type, String id, Map params, Closure closure) { closure(buildMVCGroup(params, type, id)) destroyMVCGroup(id) }
というメソッドを定義しておくと、以下のように
def someAction = { evt -> Map params = ... withMVC('《MVC グループ名》', '《グループ ID》', params){ mvc -> // MVC グループのインスタンスを用いた処理 } }
クロージャを使って処理だけを書くことが出来ます。 MVC グループの生成や破棄は気にしなくて OK。 クロージャ、すごいンジャ〜! ちなみに、以下のサイトを参考にしてます:
関数描画アプリケーション
では、例によって「関数描画アプリケーション」に機能を追加しましょう。 追加機能は、関数の詳細を設定するダイアログ(ウィザード)とします*1。 見た感じはこんなの:
単純なメッセージだけなら JOptionPane の static メソッドなどで事足りますが、ある程度複雑なコンテンツを持ち、ユーザーの複数の入力をオブジェクトとして取得したい場合などは、それ専用の MVC グループを作成する方がコーディングが楽かと思います。
新たに作成する MVC グループは「FunctionDetailWizard」 MVC グループとします。 また、今回作成(修正)するファイルは以下の通り:
- FunctionPlotter/griffon-app/models/FunctionDetailWizardModel.groovy
- FunctionPlotter/griffon-app/views/FunctionDetailWizardView.groovy
- FunctionPlotter/griffon-app/controllers/FunctionDetailWizardController.groovy
- FunctionPlotter/griffon-app/views/MonolineFunctionView.groovy (修正)・・・ボタンとアクションの追加だけなので省略
- FunctionPlotter/griffon-app/controllers/MonolineFunctionController.groovy (修正)
FunctionDetailWizard MVC
まずは「FunctionDetailWizard」 MVC グループのコーディング。 このグループのコーディングは特に問題ないかと。 ちなみに、以下のコマンドで MVC グループを生成します:
griffon create-mvc FunctionDetailWizard
Model
Model では、設定する項目を保持しておくフィールドを定義しておきます。 ダイアログを閉じた後、ユーザーの入力値をこの Model から取得します(Controller にて)。
package functionplotter class FunctionDetailWizardModel { @Bindable String name @Bindable String samples }
MonolineFunctionModel と異なり、samples フィールドを String 型として宣言しています(この辺りは実装者の勝手)。 各フィールドの値はこの MVC グループの作成の際に初期化されるようにします(後述の MonolineFunctionController 参照)。
View
次は View。 この View では、ダイアログのメッセージ(コンテンツ)になる JPanel オブジェクトを定義します。 ダイアログ自体は Controller にて生成。 参照の必要がある Bean には id 属性を付加しておきます。
package functionplotter panel(id:'content', border:emptyBorder(6)) { migLayout(layoutConstraints:'wrap 3') label 'Name' label ' : ' textField id:'nameField', columns:5, text: bind(source: model, sourceProperty:'name', mutual:true) label 'Samples' label ' : ' textField id:'samplesField', columns:5, text: bind(source: model, sourceProperty:'samples', mutual:true) }
テキストフィールドの内容と Model のフィールドをバインドしていますが、「mutual:true」と指定することによって、相互に値の変更を反映することができます。 これはナカナカ便利。 「mutual:true」にする場合、バインドには source を指定するようですが、target を指定したらどうなるのかは試してません*2。
各テキストフィールドの初期テキストは、「mutual:true」によって Model のフィールドの値にセットされます。
Controller
最後は Controller。 ここでは View をメッセージとして(message:view.content)、optionPane を介してダイアログを生成・表示しています。 ダイアログの種類は「OK / Cancel」ダイアログとしています(→)。
package functionplotter import javax.swing.JOptionPane class FunctionDetailWizardController { def model def view def builder def show = { evt = null -> def pane = builder.optionPane( message:view.content, messageType:JOptionPane.PLAIN_MESSAGE, optionType:JOptionPane.OK_CANCEL_OPTION) def dialog = pane.createDialog(app.windowManager.windows[0], 'Function Detail') dialog.visible = true if(pane.value == JOptionPane.OK_OPTION) return model else return null } }
ユーザーが「OK (了解)」を選択すると、Model 自体を返すようにしています。 それ以外(「Cancel (取消し)」もしくは「閉じる」ボタンをクリック)なら null を返します。
MonolineFunctionController
さて、上記の「FunctionDetailWizard」 MVC グループの実装を踏まえて、MonolineFunctionController に上記のダイアログ(ウィザード)を表示してユーザーの入力を取得する処理を追加しましょう。 View のコーディングは一辺倒なので省略。 処理は detail として Controller に記述します。
package functionplotter import javax.swing.JOptionPane class MonolineFunctionController { def model def view def detail = { evt = null -> def params = [name:model.name, samples:model.samples.toString()] withMVC('FunctionDetailWizard', 'functionDetail', params){ mvc -> // 「FunctionDetailWizard」 MVC グループのインスタンスを使った処理。 def result = mvc.controller.show() if(result != null){ model.with{ name = result.name samples = result.samples as int } } } app.groups.FunctionPlotter.view.controlPanel.updateUI() } def withMVC(String type, String id, Map params, Closure closure) { closure(buildMVCGroup(params, type, id)) destroyMVCGroup(id) } }
- withMVC() メソッドは記事の最初に説明した通り。 3つ目の引数として渡している Map は FunctionDetailWizardModel のフィールドを初期化するのに使われます。
- detail 処理の内部では、生成された MVC インスタンスを利用してダイアログ(ウィザード)を介してユーザーの入力を取得し、Model (MonolineFunctionModel) のフィールドに設定しています。 実際には入力値の妥当性検証なども必要ですが、ここでは省略。
これでコーディング完了。
上記で実装した内容を新たな MVC グループを作らずに(例えばダイアログだけで)行おうとするとかなり大変かと。 一方、今回見てきた方法では、ほとんど単なる MVC グループの作成だけでストレートフォワードにコーディングできてますネ。 Model を介したユーザー入力の取得とスクリプトによる View の構築はすごく簡単で柔軟性もあるし、Controller の処理はほとんど定型文といってもいい内容。 ん〜、MVC パターン、ハマるとやめられませんなぁ〜。
追記
Griffon 0.9.3-beta-1 から withMVC() メソッドがデフォルトで使えるようになりました。
- 作者: Andres Almiray,Danno Ferrin,James Shingler
- 出版社/メーカー: Manning Pubns Co
- 発売日: 2012/06/28
- メディア: ペーパーバック
- クリック: 8回
- この商品を含むブログ (19件) を見る
- 作者: Dierk Konig,Andrew Glover,Paul King,Guillaume Laforge,Jon Skeet,杉浦孝,櫻井正樹,須江信洋,関谷和愛,佐野徹郎,寺沢尚史
- 出版社/メーカー: 毎日コミュニケーションズ
- 発売日: 2008/09/27
- メディア: 単行本(ソフトカバー)
- 購入: 5人 クリック: 146回
- この商品を含むブログ (121件) を見る
*1:Griffon のプラグインに「Wizard Plugin」というのがありますが、Griffon 0.9.2-beta-3 では動きませんでした。
*2:他にも converter を指定して自動で型変換するようにしてるとどうなるかとかなんてのも気になる(けど試さない:-P)。