[ top ] [ prev ] [ up ] [ next ]
Last-Modified:
2003/10/23
Apollo ドキュメントを書かなくちゃ
- Apollo が何に使えるのか書いてこなかった。道具ってのは使って何ぼのもんだから,活用事例をまとめてみるのは,普及のために大事なことだ。
チュートリアルはみずほちゃんががんばってくれてるから安心して任せておけるけど, Delphi での 組み込み Ruby の説明は私の責務だな。ぼちぼち書きためよう。
Marshal
- 実は, Phi::Component は Marchal に対応している。
- ex. apollo/sample/marshal/c_marshal.rb
-
(fail to include c:/apollo/sample/marshal/c_marshal.rb)
*.dfm
を読み込む
- Phi::Component の Marshal は
TStream.WriteComponent, TStream.ReadComponent
を _dump, _load
に対応付けることで実現している。だから _dump, _load
を直接使うと *.dfm
を読み書きできる。
- ex. apollo/sample/marshal/c_dump.rb
-
(fail to include c:/apollo/sample/marshal/c_dump.rb)
ruby c_dump.rb
- この結果,
form.afm, btn1.afm
が作られる。*.afm
の書式は *.dfm
と同じだが,object
で指定されるクラス名が若干違う(Delphi では,例えば TForm を使うときには継承して TForm1 が作られ,Form1.dfm
の頭は object TForm1
となる)。だから拡張子を *.dfm
(Delphi Form) ではなくて *.afm
(Apollo Form) とした。
- form.afm
-
(fail to include c:/apollo/sample/marshal/form.afm)
- btn1.afm
-
(fail to include c:/apollo/sample/marshal/btn1.afm)
- また,見ていただくと分かるように現状の
_dump
はインデントへの配慮が欠けている。手抜きだが,大目に見てやってほしい。
*.afm
を _load
で読み込むとコンポーネントを元の姿に戻せる。
- ex. apollo/sample/marshal/c_load.rb
-
(fail to include c:/apollo/sample/marshal/c_load.rb)
イベントハンドラ
基本
- Delphi イベントハンドラでは引数を変換してそれらを Ruby に渡す。Ruby 側ではイベントハンドラは特異メソッドか手続きブロックで定義する。Ruby オブジェクトに変換したイベントハンドラの引数は,特異メソッドや手続きブロックの引数になる。
例えば, TWebBrowserHandle.doCommandStateChange は TWebBrowser.OnCommandStateChange に代入されるイベントハンドラである。
procedure TWebBrowserHandle.doCommandStateChange(Sender: TObject;
Command: Integer; Enable: WordBool);
var
recv, data: Tvalue;
begin
recv := TComponent(Sender).tag;
data := rb_ary_new;
rb_ary_push(data, rb_intern('on_command_state_change'));
rb_ary_push(data, recv);
rb_ary_push(data, ap_Fixnum(Command));
rb_ary_push(data, ap_bool(Enable));
PhiCallProtect(data);
end;
- これを, WebBrowser オブジェクトの初期設定手続き WebBrowser_setup では
procedure WebBrowser_setup(obj: TValue; real: TWebBrowser);
begin
...
if @real.OnCommandStateChange = nil then
real.OnCommandStateChange := Handle.doCommandStateChange;
...
end;
- のように使っている。
上書き
- phi ライブラリでは, TPhiHandle 型の Handle という変数に代入されているひとつのオブジェクトがすべてのイベントハンドラを持つ。 また, Phi 拡張ライブラリでは,それぞれ自前の Handle を用意する。例えば, webbrowser ライブラリでは TWebBrowserHandle 型を定義している。だから,イベントハンドラを設定するために,対応する Handle のメソッドポインタを代入するわけだ。単純に考えると
real.OnCommandStateChange := Handle.doCommandStateChange;
- これでよい。でも,これでは都合が悪い。既存のアプリケーションに Apollo を組み込むことを考えてみてほしい。設計時にすでに real.OnCommandStateChange にイベントハンドラを登録していたら,それは上書きされてしまう。設計時に登録されたイベントハンドラを全て上書きされてしまってはアプリケーションが全く使えなくなってしまうよね。したがって,実際には
if @real.OnCommandStateChange = nil then
real.OnCommandStateChange := Handle.doCommandStateChange;
- このように,上書きを禁止している。こうすると今度は,イベントハンドラを書き換えたくても Ruby 側からは書き換えられなくなってしまった。作者としては,これくらいの制限があるほうがアプリケーションの保守が楽になって良いと思っている。どうしても書き換えたり Ruby 側からもイベントを捕捉したいときには,その Delphi 側のイベントハンドラの中に
Handle.OnCommandStateChange(Sender, Command, Enable);
- と記述すればよい。
引数
- 見ていただくとわかるように,イベントハンドラの引数は Ruby オブジェクトに変換されたあと,data という Array オブジェクトに push され, data を引数にして PhiCallProtect を呼ぶ。data への push 順は固定されている。
- id
-
Ruby 側でのイベントハンドラ名を intern した値
- recv
-
Sender に対応する Ruby オブジェクト
基本的には,recv に続いて Delphi イベントハンドラの引数をそのままの順で push していく。
ただし,変数パラメータ (var) が存在する場合は,変数パラメータを recv の直後に持ってくる。
- ex. TPhiHandle.MouseWheelOnMouseWheel
-
procedure TPhiHandle.MouseWheelOnMouseWheel(Sender: TObject;
Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint;
var Handled: Boolean);
var
recv, data, ret: Tvalue;
pointp: PPoint;
pointv: Tvalue;
begin
recv := TComponent(Sender).tag;
if recv = 0 then Exit;
data := rb_ary_new;
rb_ary_push(data, rb_intern('on_key_up'));
rb_ary_push(data, recv);
rb_ary_push(data, ap_bool(Handled));
rb_ary_push(data, ap_set_to_ary(Shift));
rb_ary_push(data, INT2FIX(WheelDelta));
new(pointp);
pointv := rb_data_object_alloc(ap_cPoint, pointp,
nil, @ap_dispose);
rb_ary_push(data, pointv);
pointp^.X := MousePos.X;
pointp^.Y := MousePos.Y;
ret := PhiCallProtect(data);
if ret <> Qnil then handled := RTEST(ret);
end;
- 変数パラメータが複数ある場合はそれらをひとつの Array オブジェクトに push して,その Array オブジェクトを recv の直後に push しなければならない。
- ex. TPhiHandle.CanResizeOnCanResize
-
procedure TPhiHandle.CanResizeOnCanResize(Sender: TObject;
var NewWidth, NewHeight: Integer; var Resize: Boolean);
var
recv, data: Tvalue;
ary, ret, val: Tvalue;
begin
recv := (Sender as TComponent).tag;
data := rb_ary_new;
rb_ary_push(data, rb_intern('on_can_resize'));
rb_ary_push(data, recv);
ary := rb_ary_new;
rb_ary_push(ary, INT2FIX(NewWidth));
rb_ary_push(ary, INT2FIX(NewHeight));
rb_ary_push(ary, ap_bool(Resize));
rb_ary_push(data, ary);
ret := PhiCallProtect(data);
if RTYPE(ret) <> T_ARRAY then Exit;
val := rb_ary_shift(ret);
NewWidth := FIX2INT(val);
val := rb_ary_shift(ret);
NewHeight := FIX2INT(val);
val := rb_ary_shift(ret);
Resize := RTEST(val);
end;
戻り値
- 変数パラメータが存在する場合は PhiCallProtect の戻り値を処理して変数パラメータに反映させなければならない。
設定の自動化
- ここでは,手続きの中身でなく,手続きの名前に注目してほしい。
KeyPressOnKeyPress
- これはイベント型名とメソッド名に由来している。
TKeyPressEvent + OnKeyPress
- この命名規則が設定自動化の肝である。
uProp.pas の AssignPropMethod で,この命名規則を用いている。
ATypeInfo := APropInfo.PropType^;
name := ATypeInfo^.Name;
name := Copy(name, 2, Length(name)-6) + APropInfo.Name;
- これで,メソッドポインタの代入先を機械的に決めることができる。
[ top ] [ prev ] [ up ] [ next ]