たまに作業の関係で OS ネイティブの GUI アプリケーションを作りたくなる時があります。 適当に自分用につくったコマンドラインベースのものが "ウケ” がよくて GUI に昇格させてみんなに使ってもらう、なんてときですね。 🙂
加えてぼくは普段 Linux を使っていますが、折角なのでたまに使う Windows でも同じもの動かしたいなんて要求が加わってくると使う環境は絞られてきて、、 GTK + スクリプト系言語とか Mono とか Java + Swing とかで、、いろいろやってみましたが一番簡単にランタイム環境を用意できて綺麗に動かせるのは Java + SWT ではないかと思います。
というわけで、SWT。
しかも SWT あまり分かっていないのに、これを Groovy の groovy-swt モジュールを使って呼び出してみました。 …分からないことだらけで大変(笑)
GroovySWT is a wrapper around SWT, the eclipse Standard Widget Toolkit. It allows you to easily write Eclipse SWT applications by using Groovy’s builder mechanism.
これが何かといえば、Groovy お得意の動的言語の機能をつかって、GUI のツリーを Groovy 記述で構築してしまおうというものです。
GUI コンポーネントを扱ったことがある方なら分かりますが、これらは親子関係とかいろいろ大変ですのでこれを解決しようという動きです。 XML 定義に Groovy の制御構文が使える、というイメージで扱うのがよさそうです。
ちなみに、GroovySWT のダウンロードですが、現在あろうことかリンクが切れていますのでリポジトリからダウンロードします。
Revision 21442: /trunk/groovy/modules/groovy-swt/dist
without-swt のほうがうまいこと swt だけ除いてある jar になっていますので、Windows 以外の場合は without と各機種にあった swt をいっしょにクラスパスに通しておきます。
GroovySWT 0.5 時点での SWT 依存は 3.5 になっていましたので以下からダウンロードできます。(trunk のコミットコメントみると 3.5.1 でもよさそうな感じです)
swt-3.5-win32-win32-x86.zip
swt-3.5-win32-win32-x86_64.zip
swt-3.5-wpf-win32-x86.zip
swt-3.5-win32-wce_ppc-arm-j2se.zip
swt-3.5-win32-wce_ppc-arm-j2me.zip
swt-3.5-gtk-linux-x86.zip
swt-3.5-gtk-linux-x86_64.zip
swt-3.5-gtk-linux-ppc.zip
swt-3.5-motif-linux-x86.zip
swt-3.5-gtk-solaris-sparc.zip
swt-3.5-gtk-solaris-x86.zip
swt-3.5-motif-solaris-sparc.zip
swt-3.5-motif-hpux-ia64_32.zip
swt-3.5-photon-qnx-x86.zip
swt-3.5-motif-aix-ppc.zip
swt-3.5-cocoa-macosx.zip
swt-3.5-cocoa-macosx-x86_64.zip
swt-3.5-carbon-macosx.zip
各 OS の環境に合わせた x32 とか x64 とか使えばOKです。 Windows は WPF 版なんかが目を引きますね。 🙂
commons-logging-1.1.1.jar も動作時に class not found したので入れました。 swt.jar と groovy-swt-0.5.without-swt.jar とあとは Groovy の jar を入れれば準備OKです。
またこれを使ってプログラムかくにはクラスライブラリのソースコード必須です。 swt と groovy-swt のソースはダウンロードして jar にアタッチしておきましょう。(まずは groovy.swt.SwtBuilder からみると良さそうです。あとは SWT のコンポーネントソース見てプロパティ設定していきます)
ということでこの状態でから始めたのですが、、
[tegaki]どーやってもメニューが追加できない!![/tegaki]
サンプルもうまく動いていない模様。 ここの解明が疲れました。。
どうやら groovy-swt のライブラリに不具合があるようです(たぶん)。
これであっているかどうかな上にひどいコードですが、以下のようにパッチ。。(早く動かしたくて手抜きしたの図)
しかも diff 逆にとってしまったのはご愛嬌。。 + と – がさかさまです(笑)
Index: WidgetFactory.java
===================================================================
--- WidgetFactory.java (revision 5)
+++ WidgetFactory.java (revision 2)
@@ -119,19 +119,14 @@
Object[] arguments = { parent, new Integer(style) };
return constructor.newInstance(arguments);
}
+ // lets try to find the constructor with 1
+ // arguments
+ } else if (types.length == 1
+ && types[0].isAssignableFrom(parent.getClass())) {
+ Object[] arguments = { parent };
+ return constructor.newInstance(arguments);
}
}
- // lets try to find the constructor with 1
- // arguments
- for (int i = 0, size = constructors.length; i < size; i++) {
- Constructor constructor = constructors[i];
- Class[] types = constructor.getParameterTypes();
- if (types.length == 1
- && types[0].isAssignableFrom(parent.getClass())) {
- Object[] arguments = { parent };
- return constructor.newInstance(arguments);
- }
- }
}
}
return beanClass.newInstance();
SWT の org.eclipse.swt.widgets.Menu は 2引数コンストラクタがあって parent と style が設定できて、parent が Shell オブジェクトで style が SWT.BAR のときにメニューとしてはりつくと思うのですが、パッチ前の処理では 1引数コンストラクタを先に見つけてしまうため、style なしで new されてうまくメニューがでないという不具合のようです。(たぶん他の Widget は 2引数のコンストラクタが上のほうに定義されているのでしょう)
さて、そんなこんなで以下のつくったソースで動作させてみる。
変数定義の位置がおかしいとか大文字小文字が混在しているとかは気にしない方向で。 万が一動かしてみたい奇特な方がいらっしゃいましたら、画像のところはコメントアウトしてください。 🙂
import groovy.swt.SwtBuilder
import org.eclipse.swt.layout.GridData;
class Gonswt {
def shell
def sashForm1
def builder
def tab
def run() {
builder = new SwtBuilder()
shell = builder.shell (text:'Groovy on SWT', size:[410, 600], location:[300,200]) {
menu(style:"BAR") {
menuItem("バインダー" , style:"CASCADE") {
menu( style:"DROP_DOWN") {
menuItem(style:"PUSH", "&Open file")
menuSeparator()
menuItem(style:"PUSH", "E&xit"){
onEvent('Selection'){
shell.dispose()
}
}
}
}
menuItem("焼きそばん" , style:"CASCADE") {
}
}
gridLayout()
composite() {
rowLayout()
toolBar( style:"FLAT, WRAP, RIGHT") {
toolItem(style:"push", "再チャレンジ") {
image( src:'resource/item1.png' )
}
toolItem(style:"push", "里に帰ろう") {
image( src:'resource/item2.png' )
}
}
}
composite(layoutData:gridData(horizontalSpan:3
, horizontalAlignment:GridData.FILL
, verticalAlignment:GridData.FILL
, grabExcessHorizontalSpace:true
, grabExcessVerticalSpace:true)) {
fillLayout()
sashForm1 = sashForm( style:"vertical") {
tab = cTabFolder( id: 'myTabs', tabHeight:28, simple:false, style:"BORDER") {
fillLayout()
//gridData( style:'fill_both')
cTabItem( 'タイムマシーン', style:'none') {
table(linesVisible:true, headerVisible:true, style:"V_SCROLL") {
tableColumn("食べたい", width:80)
tableColumn("食べちゃいたい", width:60)
10.times {
tableItem(["", "ほげほげ"]) {
image:image( src:'resource/item3.png' )
}
tableItem(["", "もげもげ"]) {
image:image( src:'resource/item4.png' )
}
tableItem(["", "ほえほえ"]) {
image:image( src:'resource/item5.png' )
}
}
}
}
cTabItem( 'マンション', style:'none') {
text( style:'border, multi, v_scroll, h_scroll', 'Content for Tab1' )
}
cTabItem( 'ダイレクトショッピング', style:'none') {
text( style:'border, multi', 'Content for Tab2' )
}
}
composite( style:"none" ) {
fillLayout()
text( style: "border, multi, wrap")
}
}
// 2つの weight を決める
sashForm1.weights = [90, 10]
// デフォルト表示タブ
tab.setSelection(0)
}
composite(style:"BORDER") {
rowLayout()
toolBar( style:"FLAT, WRAP, LEFT") {
toolItem(style:"push", "ラーメンバー")
}
}
}
// fillLayout のときは縮められるので pack を false にする
shell.doMainloop(false)
}
public static void main(String[] args) {
new Gonswt().run();
}
}
とりゃ、実行!
Linux 版。
Windows 版。
Mac 版。 は、残念ながら手元に機械がないのでできません(笑)
てなことで、いい感じでネイティブ描画されて同じように表示されているのが分かります。 可変ウインドウもばっちりです。 斜めタブは Eclipse でおなじみの CTabFolder 。
自分で使いそうな GUI 部品はだいたい入れてみて動いたので、この後は知恵と勇気でなんとかいけそうです。。 間違ってるかもですが。。
まぁ正直なところ SWTBuilder 使わなくても当然 Groovy からは SWT を直接呼べるのでそっちでもいいのですが、これはお遊びということで。 ちなみに、JFace の Builder もありますので、そちらを使えばコンポーネント・データバインディングを使うことができます。
一応描画ができるようになったので、あとはイベントリスナーとスレッド系の操作が分かればなにかつくれそうです。 Groovy でやると確かにお手軽感あります!
もうちょっとやってみます。 🙂
types[0].isAssignableFrom(parent.getClass())
should be written as
types[0].isInstance(parent)