著作一覧 |
RjbでSwingを使いたいという問い合わせが来た。
JRuby使え、と返したら、そうは行かない事情として、組み込みRubyだからという理由が戻ってきた。なるほど。言われてみれば、Win32OLEを使う場合も同じかも。
使い方にもよるが、もちろん、できなくはない。
問題は、Swingのワーカスレッドと、イベントリスニングスレッドは、どちらもメインスレッドではあってはならないというSwing側の事情と、GC(メモリアロケーションとか)と独自のスレッド管理の都合から、RubyスクリプトはすべてメインスレッドでなければならないというRuby側の事情が、見事に食い違っている点にある。
どちらかが妥協すれば良い。つまり、他のスレッドでは動作できない(たまたま動作しているのは、動作しているとは言えない)Rubyではなく、JavaでSwingのイベントリスニングスレッドからの通知を一度、メインスレッドで受けて、そこでRubyとやり取りすれば良い。
以下に、Swingで作ったダイアログを呼び出して、ボタンが押されたというイベントの受信と、結果の文字列を受け取るRubyのスクリプトを示す。
require 'rjb' Rjb::load('./SwingPop.jar') SwingPop = Rjb::import('info.noip.arton.rjb.swing.QueryWindow') class QueryListener # イベント受信用インターフェイス def buttonPressed puts 'buttonPressed !' # ボタンを押されたら出力 end end ql = Rjb::bind(QueryListener.new, 'info.noip.arton.rjb.swing.QueryWindow$QueryListener') puts "you choose #{SwingPop.queryLanguage(ql)}" # 呼び出し結果を受ける。
このスクリプトの実行例を以下に示す。
このプログラムでは、Swingのイベントリスニングスレッドとメインスレッドの通信には単純にダイアログそのものを利用したwait/notifyで行っている。以下にボタン押下時の処理を示す。なお、ここではボタンクリックのイベント直後ではなく、エラーなどを表示した後にメインスレッドへの通知を行っている。
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) { String ans = textLanguage.getText(); if (ans.length() == 0) { error("please enter the answer"); } else if (ans.equalsIgnoreCase("java") || ans.equalsIgnoreCase("ruby")) { answer = ans; setVisible(false); // ダイアログの終了 } else { error("Think !"); } wakeup(); // 通知 } void wakeup() { synchronized (this) { notify(); } }
メインスレッド側は以下のように、ダイアログ表示後、通知を受ける。
for (;;) { synchronized (t.dialog) { if (t.dialog.answer != null) { return t.dialog.answer; // 結果を受けたら戻る } else { try { t.dialog.wait(); } catch (InterruptedException e) { return null; } } } listener.buttonPressed(); // これによりRjbへ通知が行われる }
Javaのソースは、jarに同梱してある。
ジェズイットを見習え |