groovyの勉強:swingを使ってみた

テキストファイルを読み込んで、指定された行にマッチした行だけを表示するプログラム。

とりあえず、次のことを試してみた。

  • JFileChooserのmetaclassを使って、ファイルオープンと保存の処理内容をクロージャで定義して、簡易に読みだすことができるようにしてみた。
  • 使えそうな所にbindを使ってみた。

絶賛格闘中のところ。

  • クラス内のメソッドからプロパティを書き変えた場合、@Bindableが有効に動いてくれない。なぜ?

感想

  • 素のSwingよりは簡単に書けるけど、まだ冗長に見える。もっと省略して色々書けるようにライブラリを整備したい。> Griffon?
  • bindingが少し使いづらい。どうせなら、Model->Swingだけでなく、Swing->Model(JTextFieldに文字列を入力すると、モデルに自動で反映させるみたいな)のようなものも簡単に記述できると嬉しい。
  • LayoutManagerにForms使いたい。
import java.awt.BorderLayout;

import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.filechooser.FileNameExtensionFilter

import groovy.beans.Bindable;
import groovy.swing.SwingBuilder;

// JFileChooserに、「ファイルを開くダイアログを開いて、ファイルが選択されたら指定された処理を実行する」メソッドを追加
JFileChooser.metaClass.openFile = { window, action ->
	if( delegate.showOpenDialog(window) == JFileChooser.APPROVE_OPTION ){
		action.call(delegate.getSelectedFile())
	}
}
// JFileChooserに、「ファイル保存ダイアログを開いて、ファイルが選択されたら指定された処理を実行する」メソッドを追加
JFileChooser.metaClass.saveFile = { window, action ->
	if( delegate.showSaveDialog(window) == JFileChooser.APPROVE_OPTION ){
		action.call(delegate.getSelectedFile())
	}
}

// 本プログラムのデータを格納しておくモデル
class TextFilterModel {
	@Bindable File inputFile = null
	@Bindable String matcher = ""
	@Bindable String filteredText = ""
}
// フィルタ処理の定義(TextFilterModel内にメソッド定義するとなぜか動かない。。。)
def filter( model ){
	if( model.inputFile != null ){
		model.filteredText = model.inputFile.filterLine { line -> line =~ model.matcher }
	}
}
model = new TextFilterModel()

// 以降、画面周りの定義
swing = new SwingBuilder()
swing.lookAndFeel("system")
chooser = swing.fileChooser(
	dialogTitle: "Choose Text File.",
	acceptAllFileFilterUsed: true,
	fileSelectionMode: JFileChooser.FILES_ONLY,
	fileFilter: new FileNameExtensionFilter("*.txt", "txt")
)

// メインウィンドウの定義
frame = swing.frame(
	id: 'mainwindow',
	title: swing.bind(source:model, sourceProperty:'inputFile', converter:{f->"TextFilter ver 0.1 : ${f}"}),
	defaultCloseOperation: JFrame.EXIT_ON_CLOSE,
	pack: true,
	visible: true,
){
	// メニューバーの定義
	menuBar(){
		menu(text: "File"){
			menuItem(text: "Open File", actionPerformed: {
				chooser.openFile(mainwindow){ file ->
					model.inputFile = file
					filter(model)
				}
			})
			menuItem(text: "Save File", actionPerformed: {
				chooser.saveFile(mainwindow){ file ->
					file.write(model.filteredText)
				}
			})
			separator()
			menuItem(text: "Exit", actionPerformed: {dispose()})
		}
	}
	
	// フィルタ用テキストの定義
	textField(
		id: 'matcherField',
		constraints: BorderLayout.NORTH,
		actionPerformed: {
			model.matcher = matcherField.text; 
			filter(model)
		}
	)
	
	// フィルタ後テキストの表示領域の定義
	scrollPane(constraints: BorderLayout.CENTER){
		textArea(
			columns: 80, rows: 10,
			editable: false,
			text: bind(source: model, sourceProperty: 'filteredText')
		)
	}
}