全力わはー RSSフィード

2014-12-21

普通のコンポーネントで【初期化】やってみた。

| 00:00 | 普通のコンポーネントで【初期化】やってみた。を含むブックマーク

この投稿はDelphi / Appmethod Advent Calendar 2014の21日目の記事です。

ところで皆さん何か忘れてませんか?

「Object Pascal / C++」Advent Calendar」じゃないですよ!「Delphi / Appmethod」Advent Calendarですよ!

もっと開発環境の話もぶっ込んでいきましょう!

前フリ

最近じゃ珍しい機能でも何でもないですが、DelphiのIDEと言えばやっぱりポトペタですよね。コンポーネントパレットから選んで、クリックで配置。そのコンポーネントを配置した際、毎回やることってありません?

例えば、TEditやTMemoを置いた際に、TextやLinesを空にする、みたいな。

例えば、TPanelを置いた際に、Captionを空にしたり、ShowCaptionをFalseにする、みたいな。

例えば…。

そう、コンポーネントの各プロパティの初期値って必ずしもよく使う状態にはなっていなくて、置く度に毎回「自分好みの初期化」をすることって、よくありますよね。

じゃあ今まではどうしていたか

もちろん手で毎回入力してるという方がほとんどだと思いますが、この初期化問題を解消しようと思った場合、今まではカスタムコンポーネントを作っていました。

こんな感じに。

unit MyEdit;

interface

uses
  System.Classes, Vcl.Controls, Vcl.StdCtrls;

type
  TMyEdit = class(TEdit)
  public
    constructor Create(AOwner: TComponent); override;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Samples', [TMyEdit]);
end;

{ TMyEdit }

constructor TMyEdit.Create(AOwner: TComponent);
begin
  inherited;
  ControlStyle := ControlStyle - [csSetCaption]; // これで置いた時からTextが空になる
end;

end.

こうやってコンポーネントを自作すれば処理を追加し放題なので、もちろんプロパティの初期化もできます。

しかし初期化だけをしたい場合、新しくコンポーネントを作るというのは大げさですし、何よりこれを使用したソースを配布する場合、カスタムコンポーネントのソースも添付し、尚且つ利用者の環境にインストールしてもらわなければなりません。非常に手間です。

そこで提案

カスタムコンポーネントを作らず、尚且つ初期化も可能だとしたらどうでしょう?それを実現するのが今回紹介するIDE用プラグイン、Component Initializerです。とハードルを上げて登場しておきながら、実はこのプラグイン、インストールしても何も起きません。

…そのままでは。

自分でカスタマイズ

このプラグインは、使用者がプログラミングをすることで初めて真価を発揮します。

プラグインのソースを見てみると、IDE上でコンポーネントが作成された時に呼ばれる関数があり、この中に自由に処理を記述できるようになっています。つまり、そこでコンポーネントの初期化をするコードを書けば、新しくカスタムコンポーネントを作ることなく、好きな状態でコンポーネントを作成できるわけです。

作成されるのはカスタムコンポーネントではなく元のコンポーネントなので、このプラグインが入っていない環境にソースを持って行ったとしても、プラグインが設定した値はオブジェクトインスペクタで指定した時と同様にdfm内に記録されるため、カスタムコンポーネントの時のデメリットも解消されています。

実際に使ってみる

まずはプラグインをダウンロードして下さい。

Component Initializer ver.0.0.1をダウンロード

中身を適当なフォルダに展開したら、DelphiのIDEからCompInitPkg.dpkをロードし、プロジェクトマネージャを見るとContainsの中にUMain.pasがあるので、ダブルクリック等で開いて下さい。そうするとユニットの下の方に以下のような定義が見つかると思います。

procedure OnCreateComponent(Self: TObject; Parent: TComponent; const Rect: TRect);
begin
  { コンポーネントに対する初期化コードをここに記述してください }
end;

ここに初期化コードを記述すればいいわけですが、サンプルとしてユニットの上の方にコメントアウトされた処理があるので、最初はここからコピーすると分かりやすいと思います。が、今回はAdvent Calendar用に以下のような記述をしてみます。

procedure OnCreateComponent(Self: TObject; Parent: TComponent; const Rect: TRect);
begin
  if Self is Vcl.StdCtrls.TCustomEdit then
  begin
    Vcl.StdCtrls.TCustomEdit(Self).Text := 'Delphi / Appmethod Advent Calendar 2014'#13#10'21日目!';
  end
end;

このように書き換えたら、プロジェクトマネージャのパッケージ名の上で右クリックし、インストールを選択します。そして試しに新規フォーム上でTMemoを配置してみるとこうなります。

f:id:tales:20141220221622g:image

このようにpublishedなプロパティに対する処理であれば、実行時のように好きなコードを書くことができます。この例ではVCLコンポーネントのプロパティを設定しましたが、もちろんFireMonkeyでも可能です*1

余談

f:id:tales:20141220221941p:image:w360

本当はこのような設定用UIも途中まで作りかけてはいたんですが、値の設定部分が恐ろしく面倒だったので断念しました。直接コードで書けば1行で済むようなことが、汎用性を持たせたUIにするだけで数百行を要するような苦行に早変わりするんですよね…。しかもコードで書いた方が柔軟性が高いと、いいところ特になかった。

おまけ

これを作る過程で懐かしの痛IDEプラグインにも近代化改修を施してみました。

これに伴いXE以下の対応を切りましたが、代わりにXE5〜XE7で動作するようになったかもしれません。また、各バージョン毎のパッケージライブラリを用意するのが面倒だったので、オープンソース化します。動作テストはめんどくさいのでXE4のみで行いましたが、今のところ特に問題なく動いているようです。

痛IDEプラグイン ver.0.0.6をダウンロード

さらにおまけ

Component Initializer内で使用しているDelphi Detours Library、超絶便利です。

https://code.google.com/p/delphi-detours-library/

前回のDelphi 3分ハッキングで使用した関数フックは、フック先関数を使用せずに自前関数に飛ばすので、自前関数内でフック先関数を呼びたい場合は対応ができませんでした。フック先の先頭をジャンプ命令に書き換えてしまった関係上、正常動作しないからです。

しかしこのライブラリはいわゆるトランポリンフックを行うので、先頭をジャンプ命令に書き換えてしまった関数でも、正しく呼び出すことができます。

これができると何が便利かというと、任意の関数の前後に好きな処理を追加する、といったことが超簡単にできます。例えば以下のコードは、TStringList.Addに処理時間の計測を後付けしています。TStringList.Addメソッドを関数のように書く方法は一昨年のAdvent Calendarの記事を参考にして下さい。

uses
  ..., DDetours;

var
  OrgTStringList_Add: function(Self: TStringList; const S: string): Integer;
  Stopwatch: TStopwatch;
  CallCount: Cardinal;

function MyTStringList_Add(Self: TStringList; const S: string): Integer;
begin
  Stopwatch.Start;
  try
    Inc(CallCount);
    Result := OrgTStringList_Add(Self, S);
  finally
    Stopwatch.Stop;
  end;
end;

initialization
  Stopwatch := TStopwatch.Create;
  @OrgTStringList_Add := InterceptCreate(@TStringList.Add, @MyTStringList_Add);
finalization
  InterceptRemove(@OrgTStringList_Add);
end.

これで普通にTStringList.Addを使っていれば、Stopwatchに総経過時間が、CallCountに呼び出し回数がそれぞれ入るようになります。

おまけのおまけ

全然関係ない上にあんまり需要無いかもですが、最近こんなの作ったのでついでに置いておきます。

Whitebox ver.0.0.1をダウンロード

f:id:tales:20141220223156p:image:w640

x86、x64両対応のPE情報表示ツールです。使ってる方には分かると思いますが、見た目からしてeXeScopeのパクインスパイアで、あれからリソース機能を全カットしてx64に対応した感じです。主にEXEのインポート関数やDLLのエクスポート関数の確認に使います。

リソースに関してはResource HackerやXN Resource Editorという使いやすいツールがすでにあるのでまぁ非対応でいいかなと。そんな程度なので特筆すべきところは特にないですが、DLLのエクスポート転送や遅延ロードテーブルに対応してるのが売りと言えば売りです。同系のCUIツールだとだいたい見れるんだけど、GUI系の似たようなツールだと意外と見れないんですよね、この辺の情報。

*1:わざわざ「Vcl.StdCtrls.TCustomEdit」みたいな記述をしてるのはVCLとFMXで同名のコンポーネントが存在するため

ぺこぺこ 2016/02/01 11:55 Component Initializer凄いですね!こう言うの欲しかったです!!
ありがとうございます!!

それで早速インストールさせてもらって、おーできたできた、と喜んでて、
フォントサイズやフォント名変更したかったので変更した後に、
一旦アンインストールしてインストールしたんですが・・・
なぜだか反映しないのです。

フォント名の指定がおかしいのか、と思い、Font.Name指定をコメントアウトしてもだめ。
「コンポーネント>パッケージのインストール」には「ComponentInitializer」出ています。
原因究明する方法は何かありますでしょうか?

環境はDelphi10 Seattle Windows7です。

talestales 2016/02/02 23:36 どこでもいいんですが、initialization内などにShowMessage('hogehoge')のような文を追加して、
インストール時にダイアログが表示されるか試してみて下さい。
表示されないようであれば、編集したものとは別のソースコードがコンパイル&インストールされています。

よくあるケースとしては、一度編集してdskファイルが生成されたプロジェクトをフォルダごとコピーして、
そちらのプロジェクトを開くとコピー前のソースコードをIDE上にロードしてしまう
(dskにはフルパスで開いていたファイル名が記録されているため)というのがあります。
この場合はIDE上で開いているユニットを一度全て閉じたり、プロジェクトをロードする前にdskを削除すれば直ります。

逆に言えば上記以外だと原因はちょっと分からないですね…すいません。

リンク元