自問自答集

FAQにしようかと思ったのですが、自分で考えたネタがほとんどなので自問自答と しました。近況などから掘り出したりもしています。

開発用Wikiの質問集も参考にしてください。

気分転換という説もあり。


目次

総合

設計(swin側)

設計(vruby側)

利用

不具合?


総合

VisualuRuby(仮称)とは何ですか?

Windows上でRubyを使い、GUIを作れるようにしようというものです。 Visual Basicに腹立たしいことがいろいろあったので作りはじめました。

どうして(仮称)ってついてるのですか?

そもそもVisualuRubyと言うように、 変な名前になっています。「Visual Ruby」にしてしまうと、いかにもMSチックに なってしまうので、それを避けるためです。これでもまだ腑に落ちないところが あるので(仮称)となっています。
まあ、ネーミングセンスのなさの産物です。要は。

どこで手にはいるのですか?

ここで手に入ります。この文書読んでるくらいだから知ってるでしょ?
念のため書いておくと、 ここ です。

GUIを作る画面は無いのですか?

雪見酒氏がFormDesignerというのを作成して下さっています。ここからたどって下さい。こんなデザイン画面です。
もともとここにあったバラック建てのような物とは雲泥の差です。

プログラムを作るエディタは無いのですか?

助田氏のところにRubyWinという素晴らしい物があります。VisualuRuby(仮称)関係もこの上で動きます。ですので、RubyWinと一風違ったネタを考えつくまで作成しません。
って、断言出来ないような気もしますけど。
一部動かないようです。
RDEも登場しました。GUI環境でRubyスクリプトをデバッグできます。本計画(GUI)のスクリプトでもOKです。

設計(swin側)

ウインドウハンドルを第一引数にもつWindowsAPIはたくさんありますが、すべてSWin::Windowsのメソッドになるのですか?

昔、そういう気もしたことがありましたが、現在はそんな気はありません。 「もういいや」と思ったところはWin32API.dllを使って行こうと思っています。

SWin::BitmapとSWin::Canvasは何が違うんですか?

SWin::Bitmapは可搬性のある画像クラスで、SWin::Canvasはデバイスべったりの 画像クラスという位置づけのつもりです。このため、SWin::BitmapはMarshalが使えます。SWin::CanvasはGDIの関数群で描画ができます。

デバイスコンテキストのクラスは作らないのですか?

とりあえずすぐ方針変更できるようにはなってますが、当座、する予定はありません。使うときにめんどくさいからです。

フォントなどのリソースの寿命がよくわからないのですが?

作れますし消せます。これじゃ駄目ですか? というのはさておき。 問題となるのは、GCでリソースが消されてしまうと困る場合があることです。
例えばフォントを作成して、ボタンの文字のフォントとして割り当てます。 このフォント、ボタンの寿命の続く限り共に生き残って欲しいのですが、Ruby側のオブジェクトの寿命が尽きた時点でGCにより削除されてしまいます。これは困ります。
とはいえ、オブジェクトに用意されたdeleteメソッドを呼ばれるのを待っていたのでは、Rubyインタプリタの終了時まで生き残ってしまいそうな気がします。これはあまり楽しくないです。
そういうことなので、SWin::Window#propertiesというハッシュの中にオブジェクトを放り込む事により、Windowと寿命を共に出来るようにして下さい。
SWin::LWFactoryと寿命を共にすることにして寿命管理を自動化...ということも考えましたが、結局Rubyインタプリタの寿命と同じになってしまいそうなのでパス。
リソースの寿命管理を自動化しようとすると、「必要になったら毎回リソースを作る」というもっとも楽しくないパターンになりそうなので、これもパス。

SWin::LWFactory#newwindowは何故クラスを引数に持てるのでしょうか

まあ、中途半端なことですが、エラーメッセージが読みにくかったと言うこと、継承を使いたければdelegationすることになって面倒くさかったということでこうしました。
普通、newwindowで作成したオブジェクトに色々特異メソッドを定義してウインドウを特徴づけていくのだと思いますが、これだとウインドウ関係でエラーが発生したとき、なんのウインドウがエラーを出したのか非常にわかりづらいのです。全部SWin::Windowがエラーだという表示になりますし。あと、本計画のキラーアプリのruby-chanがめんどくさそうなコトを行って継承を利用していたと言うこともあり、ウインドウの所属クラスを変更できるようにしました。ただし、SWin::Windowの子クラスであることが条件ですが。

設計(vruby側)

なぜ、Formがモジュールなのですか?

基本的には歴史的経緯、というやつです。
ただし、VRFormは既にクラスになっており、これを親クラスとして作った子クラス をVRLocalScreen.newform()の引数として渡すことが出来ます。
独自のフォームをクラスとして定義するか、モジュールとして定義するかはお好み 次第です。

VisualBasicみたいなイベントハンドラ名になっているのは?

単純に、Visual Basicを手本にしたからです。ただ、フォームの孫、 曾孫ウインドウの上にあるコントロールのイベントを拾う場合は
container1_container2_container3_control_handler
などのように"_"を使ってつなぎ合わせた形で拾うようになっているところが 違うところでしょうか。(このようにして拾うためには多少の準備が必要)

独自の形式でイベントハンドラ名を定義したいのですが...

とりあえず出来ませんと言っておきます。
独自の形式でイベントハンドラ名を定義できる自由を得る代わりに、 イベントハンドラ名をいちいち定義する義務を負うことになります。 めんどくさいのでイベントハンドラ名は勝手に付けさせていただいております。

Rubyの他のGUIライブラリではイベントハンドラが部品(ボタンなど)の特異メソッドになっていますが、どうして違う方法にしたのですか?

これまたVisual Basicを手本にしたから、なんですが、 他にも気になる点があったからです。
特異メソッドでやると、変数のスコープがややこしくなってグローバル変数が 増えたりしないのでしょうか。コードの再利用がめんどくさくならないでしょうか。 (疑問。使ったこと無いので)
あと、Windowsのシステムがボタンが押されたというイベントをボタン自身に返さず、 ボタンが貼り付けられた親ウインドウに送りつけて来るというのもあります。 Windows用のライブラリなので、そこで逆らおうという気は起きませんでした。
swin.dllを使って、特異メソッド型のライブラリは作れると思いますので、 こだわりのある方は作成してみてはいかがでしょう。(宣伝?)

ウインドウ関係のクラスにはvrinitというのがありますが、これはinitializeと何が違うのでしょう?

initializeはオブジェクトが作成されるときに呼び出されます。そしてvrinitはウインドウが実際に作成された後に呼び出されます。
ウインドウ関係のクラスは、オブジェクトが作成されるタイミングと、実際にWindows上でウインドウが作成されるタイミングは異なっています。オブジェクトが作成された後、パラメータが設定されてからウインドウの実体が作成されるようになっています。
同じような意味合いのメソッドに、constructというのがありますが、こちらは各クラスで上書き定義が前提で、vrinitはsuperで親クラスと繋いでいくことを前提として使い分けています。

利用

Windowsのシステムが送ってくるWM_MOUSEMOVEを拾って処理をしたいのですが...

vruby側では、フォームなどにVRMouseFeasibleを取り込んで下さい。
swin側ではSWin::Windowのインスタンスをwとして
$WM_MOUSEMOVE = 0x200
w.addEvent $WM_MOUSEMOVE
def w.msghandler(msg)
  if msg.msg == $WM_MOUSEMOVE then
    (process...)
  end
end

Susie Plug-inが利用できるって聞いたのですが?

よしだむ氏の susie用拡張ライブラリが必要です。
SWin::Bitmap.createBitmap(*spi.getPicture(bitmapstring)).saveFile(filename) などとすれば、ビットマップファイルとしてセーブできたりします。

spi=Susie::Plugin.new(spiname)
content=open(filename).read
$bmp=SWinBitmap.createBitmap(spi.getPicture(content))

アイドル時に別スレッドを実行できますか?

vruby000616からやり方が変わっています。

アイドルループで、他のスレッドに処理を渡します。
ただし、スレッドの切り替えの起こりにくい処理を行わせると、大元のメッセージループ側が重くなってWindowsのメッセージに応答できません。適度にアイドルループ側に処理を戻してやって下さい。特に、cygwin1.dllが古いと、sleepでも固まるようです。

以下のようにすると、他のスレッドがアイドル時に走ります。

swin側
SWin::Application.messageloop do Thread.pass end
vruby側
VRLocalScreen.addIdleproc Proc.new{ Thread.pass }
VRLocalScreen.messageloop

アイドル時に押したCTRL-Cを拾いたいのですが

以下のような感じで。メッセージ待ち受け中にCTRL-Cを押すと、messageloopからSWin::MsgloopInterruptが上がります。

begin
  VRLocalScreen.messageloop
rescue SWin::MsgloopInterrupt
  (........)
end
rescue節でretryしてもかまいません。再度メッセージループに入り直します。ただし、MsgloopInterrupt以外の割込、エラーを受けてretryしても、メッセージループには再入出来ません。この場合、RuntimeErrorが上がります。

受けたメッセージに値を返してやりたいのですが

Windowsのメッセージを受けるだけでなく、時々値を返してやらなければならないことがあります。値を要求するメッセージですね。
swin側のmsghandler(msg)は、このメソッドの返し値としてnilが与えられるとシステムデフォルトのメッセージハンドラを呼び出しません。このとき、msg.retvalをメッセージの返値として扱います。ですので、msg.retvalに返値を代入してmsghandlerはnilを返すようにして下さい。
vruby側では、メッセージハンドラの返値として、SKIP_DEFAULTHANDLERを返すと、swin側と同様にシステムデフォルトのハンドラを呼び出さず、スクリプトからメッセージの返値を指定できます。メッセージの返値を指定するには、SKIP_DEFAULTHANDLER[32]などとします。
このvruby側の機能は、self_????系のハンドラに対して有効です。

不具合?

rubyで開いたウインドウの上で時々マウスが引っかかります。

rubyの動作が重くなっているときに起こりやすいようです。例えばrubyがコンソールに山のように文字を吐き出しているときなど。イマイチ原因を把握してません。また、スクリプトが最初に開いたウインドウの上に最初にマウスを当てた時にも引っかかるようです。ちょっと気になりますが、特に問題を生じると言うものではなさそうです。

コンソールを必要としないrubyw.exeでサンプルを走らせたらrubyw.exeが頓死しました。

サンプルの中にはコンソールに文字を吐き出そうとする物が多数含まれています。 ところがrubyw.exeはコンソールを持たないのでおかしくなります。コンパイラ処理系(mswin32/cygwin)の違いや、cygwinでもcygwin1.dllの差異で挙動が違うようです。よくわかりませんが、サンプルはコンソール付きで動かして下さい。
rubyがエラーを吐いたときはどうなるんでしょうね...

Rubyのスレッドを使ったら動作が不安定になり、落ちてしまいます。

ruby本体が古い場合このようになります。Windowsの構造化例外との相性がらみで、このような現象が起きていました。ruby-1.8以降では起きないと思います。
もし発生した場合は、メッセージループに入る前に SWin::Application.thread_critical=true としておいてください。
以下、古い文章
mswin版で特に頻発するようです(なんとなく)。本計画の問題か、Ruby本体側の問題か、現状では切り分けが出来ておりません。ややこしそうなところにThread.critical=trueとThread.critical=falseの組を入れておくと良いようです。
その後どうやら、mswin版でメッセージ処理中にスレッドが切り替わると問題が出るらしいと言うことが判明。mswin版ではそういう状態で切り替えが起こらないように抑制しました。
この点、cygwin版とmswin版で明らかに動作が異なります。
この問題のスレッド切り替えを許可するかどうかはSWin::Application.thread_criticalという属性を変更することで切り替えられます。mswin版はデフォルトでtrue。cygwin版はfalseです。

戻る

Email: nyasu@osk.3web.ne.jp