MVC Groupの切り替えを分かった気になってやってみた。
元ネタはこちら。→ http://griffon.codehaus.org/FileViewer
Griffonは,MVCの組み合わせを複数持てるんだけど,あるMVCグループから別のMVCグループを生成するってのを試してみた。
スクリーンショットで見ると(↓)こんな感じに,JFrameを持つ大元の画面から,あるイベント(リストをダブルクリック)を起こすと,タブが増えていくってのを作ってみた。
大元の画面ってのと増えてくタブ(赤枠部分)ってのは,別々のMVCグループになってんのね。
#何作ろうとしているかは,ナイショだ。:-)
叩いたGriffonのコマンドはこれだけ。
> griffon create-app MvcApp > cd MvcApp > griffon create-mvc DetailPanel
これで,それぞれMvcApp,DetailPanelという接頭子を持つMVCの組み合わせができる。その定義情報は,$APP_HOME/griffon-app/conf/Application.groovy に記述される。
application { title='MvcApp' startupGroups = ['MvcApp'] } mvcGroups { // MVC Group for "DetailPanel" DetailPanel { model = 'DetailPanelModel' view = 'DetailPanelView' controller = 'DetailPanelController' } // MVC Group for "Mvcapp" MvcApp { model = 'MvcAppModel' view = 'MvcAppView' controller = 'MvcAppController' } }
create-app
で作成したMVCグループが,application/startupGroupsに指定されてるんで,Griffonを実行(run-app
)するとそのMVCグループが動くワケだな。
で,肝心のMVCグループの切り替え(つうか作成?)はどうするかっていうと,こうする。
#全部のコードはうしろのほうに載せとく。
= MvcAppController.groovyの一部抜粋 = def listClickedListener = { evt -> : createMVCGroup('DetailPanel', item, [rootPane:view.tab, item:item]) } }
createMVCGroup()
の引数はこんな感じ。
- 新しく作るMVCグループの名前(Application.groovyに登録してある名前を指定)。
- MVCグループのインスタンス名
- ユニーク名がいいかどうかは用途次第
- 指定した名前を使って,
app.controllers.名前, app.models.名前, app.views.名前
でそれぞれの要素の直接アクセスすることもできるそうだ。
- 指定したMVCグループに渡す引数
createMVCGroup()
が呼び出されると,指定したMVCグループのコントローラのmvcGroupInit(Map)
が呼び出される。もちっと具体的に言うと,こうだった。
MvcAppController createMVCGroup() ---> 1.new DetailPanelModel() 2.DetailPanelController.mvcGroupInit(Map) 3.DetailPanelView.build()
DetailPanelControllerのmvcGroupInit(Map)
は,引数にMapを受け取るので連携が分かりやすいが,どうもこのMapの値,DetailPanelViewにも渡るみたいだった。
実際,DetailPanelViewのコード見るとわかるけど,変数:rootPane
やitem
はMvcAppControllerから渡ってきたものだ。
= DetailPanelView.groovyの一部抜粋 =
tabbedPane(id:'tab', rootPane, selectedIndex:rootPane.tabCount) {
panel(title:item) {
borderLayout()
:
「わーい,これでMVCグループの切り替えもバッチリだ」とよろこんでみたものの幾つか謎が残る。
あと一番最初の動くコントローラの初期化処理はどこでやるのが正しいんだろか?一応,MvcAppControllerにもmvcGroupInit(Map)
があるから,そこに書いてみたけど,こんな例を良く見かける。
$APP_HOME/griffon-app/lifecycle/Startup.groovyに,
app.controllers.MvcApp.loadPages()
みたいに書いて,MvcAppControllerにloadPages()
を定義する。
= MvcAppController.groovyの一部抜粋 = void loadPages() { // 初期化処理を書くぞー : }
まあ,Griffonもまだ0.1Betaだかんね。ここら辺の記述がこなれるには,もちょっとかかるんだろうな。
でも慣れてくるとスゴく気軽にGUI作れて,良い感じよ。>Griffon
どのこのリポジトリにも入れてないんで,せめてここにコードを残す。といってもコントローラとビューしか作ってないよ(モデルは使ってない)。
MvcAppController.groovy
EDTの使い方は,まったくもって当てずっぽう。いい加減,ちゃんと覚えねば。:-(
import javax.swing.* import javax.swing.event.* import javax.swing.tree.* class MvcAppController { def model def view void mvcGroupInit(Map args) { doOutside { def contents = new DefaultMutableTreeNode("hudson") (1..10).each { def node = new DefaultMutableTreeNode("job-${it}") contents.add(node) } doLater { view.jobTree.model = new DefaultTreeModel(contents) view.jobTree.selectionModel.selectionMode = TreeSelectionModel.SINGLE_TREE_SELECTION view.jobTree.addTreeSelectionListener( treeSelectionListener ) view.buildList.mouseClicked = listClickedListener } } } def treeSelectionListener = { evt -> def path = evt.path if (path.pathCount == 2) { doLater { def node = view.jobTree.lastSelectedPathComponent if( !node ) return def listModel = new DefaultListModel() (1..10).each { listModel.addElement("${node.userObject}::build-${it}") } view.buildList.model = listModel } } } as TreeSelectionListener def listClickedListener = { evt -> if (evt.getClickCount() != 2) return def idx = view.buildList.locationToIndex(evt.point) if (idx < 0) return def item = view.buildList.model.elementAt(idx) createMVCGroup('DetailPanel', item, [rootPane:view.tab, item:item]) } }
MvcAppView.groovy
SwingBuilderにおけるJTabbedPaneの使い方がよくわからんかった。
import static java.awt.BorderLayout.* application(title:'mvc-app', size:[500,400], locationByPlatform:true) { tabbedPane(id:'tab') { splitPane(title:"jobs") { panel { borderLayout() scrollPane(constraints:CENTER) { tree(id:'jobTree') } } panel { borderLayout() scrollPane(constraints:CENTER) { list(id:'buildList') } } } } }
DetailPanelController.groovy
import javax.swing.* import javax.swing.tree.* import javax.swing.event.* class DetailPanelController { def model def view void mvcGroupInit(Map args) { doOutside { def contents = new DefaultMutableTreeNode(args.item) (1..20).each { def node = new DefaultMutableTreeNode("${args.item}::test-${it}") contents.add(node) } doLater { view.testTree.model = new DefaultTreeModel(contents) view.testTree.selectionModel.selectionMode = TreeSelectionModel.SINGLE_TREE_SELECTION view.testTree.addTreeSelectionListener( treeSelectionListener ) } } } def treeSelectionListener = { evt -> def path = evt.path if (path.pathCount == 2) { doLater { def node = view.testTree.lastSelectedPathComponent if( !node ) return def writer = new StringWriter() (1..100).each { writer << "${node.userObject}::console-${it}\n" } view.consoleText.text = writer.toString() } } } as TreeSelectionListener def closeTab = { def tabIndex = view.tab.selectedIndex if (tabIndex < 0) return view.tab.remove(tabIndex) view.tab.selectedIndex = 0 } }
DetailPanelView.groovy
import java.awt.* import static java.awt.BorderLayout.* tabbedPane(id:'tab', rootPane, selectedIndex:rootPane.tabCount) { panel(title:item) { borderLayout() panel(constraints:NORTH) { button('CLOSE', actionPerformed:controller.closeTab) } panel(constraints:CENTER) { borderLayout() splitPane(constraints:CENTER) { panel { borderLayout() scrollPane(constraints:CENTER) { tree(id:'testTree') } } panel { borderLayout() scrollPane(constraints:CENTER) { textArea(id:'consoleText', editable:false, font:new Font('Monospaced', Font.PLAIN, 12)) } } } } } }