Delphi カスタムコンポーネントを作成・利用したい

<h1>はじめに</h1>
会社で Delphi 使用しているのですが、コンポーネントを作成するという文化が無かった為、この度勉強してみました。

シチュエーションとして、外部への配布は考えず、社内で共用利用する事を想定しています。あ、VCLです。Delphi XE3 での例となります。
</br>

<h1>手順（長いです）</h1>
作成例：「一定時間アニメーションすると、自動でアニメーションが停止するトレイアイコンを作りたい」
（常駐アプリで使用する事を想定）


<h2>１．パッケージを新規作成する</h2>
Delphiを起動し、ファイル → 新規作成 → パッケージ　を選択します。
![ss1.png](https://qiita-image-store.s3.amazonaws.com/0/33684/c4bf508d-a580-8aa9-dbba-54a934064514.png)

プロジェクトが作られたので、最初に「プロジェクトに名前を付けて保存」を実行します。
![ss2.png](https://qiita-image-store.s3.amazonaws.com/0/33684/e5530f18-350e-9d1f-9cfc-c270ff3a31ac.png)


ここでは、D:\MyComponent\MyPackage.dproj としました。
</br>

<h2>２．コンポーネントを新規作成する</h2>
プロジェクトマネージャ画面にて、「Contains」を右クリック、新規追加→その他
![ss3.png](https://qiita-image-store.s3.amazonaws.com/0/33684/3880cfd9-a193-0ac8-ce7e-746d65386825.png)

新規作成ウィザードにて、「コンポーネント」を選択
![ss4.png](https://qiita-image-store.s3.amazonaws.com/0/33684/d58acd15-670c-3855-e5e1-ef54860ae433.png)

VLC for Delphi Win32 を選択
![ss5.png](https://qiita-image-store.s3.amazonaws.com/0/33684/a7ffefb8-bf8f-a1a2-9d95-e01fe4d0e9e2.png)

継承元コンポーネントで、TCustomTrayIconを選択。
（TTrayIconとどちらを継承元にすべきかは、追加したい機能にて判断する、と自分は解釈しました）
![ss6.png](https://qiita-image-store.s3.amazonaws.com/0/33684/99930cc7-7624-3217-f1f8-28cd7764ebeb.png)

クラス名、パレットページ名、ユニット名をそれぞれ変更しました。
![ss7.png](https://qiita-image-store.s3.amazonaws.com/0/33684/a2a141a8-713e-bbd8-2af5-74446af7fd88.png)

完了すると、まずユニットの保存を要求されますので、「D:\MyComponent\」を選択（直前の設定がデフォルトになる筈？）。
![ss8.png](https://qiita-image-store.s3.amazonaws.com/0/33684/a6b832a4-dd18-234f-2d0e-8d8b513019f5.png)

最後に、以下のような確認ダイアログが表示されます。
「はい」で問題無いかと思います。
![ss9.png](https://qiita-image-store.s3.amazonaws.com/0/33684/52940d4f-8be9-a4bf-9433-62ad48ac1b23.png)
</br>

<h2>３．実装する</h2>
（何故クラスを２個作っているかと聞かれると、答えられません。VCLの他のコンポーネントの作りに従いました。）


```delphi
unit AutoStopTrayIcon;

interface

uses
  System.SysUtils, System.Classes, Vcl.ExtCtrls, Winapi.MMSystem;

type
  TAutoStopCustomTrayIcon = class(TCustomTrayIcon)
  private
    FAnimationStartTime:Integer;
    FAnimationLifetime:Integer;
    procedure SetAnimationLifetime(Value:Integer);
    function GetAnimate: Boolean;
  protected
    constructor Create(Owner: TComponent); override;
    procedure DoOnAnimate(Sender: TObject); override;
  public
    procedure DoAnimate;
    property AnimationLifetime:Integer read FAnimationLifetime write SetAnimationLifetime default 1000;
    property Animate:Boolean read GetAnimate;
  end;

  TAutoStopTrayIcon = class(TAutoStopCustomTrayIcon)
  published
    property AnimationLifetime;
    property AnimateInterval;
    property Hint;
    property BalloonHint;
    property BalloonTitle;
    property BalloonTimeout;
    property BalloonFlags;
    property Icon;
    property Icons;
    property IconIndex;
    property PopupMenu;
    property Visible;
    property OnBalloonClick;
    property OnClick;
    property OnDblClick;
    property OnMouseMove;
    property OnMouseUp;
    property OnMouseDown;
    property OnAnimate;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('MyComponent', [TAutoStopTrayIcon]);
end;

{ TAutoStopCustomTrayIcon }

constructor TAutoStopCustomTrayIcon.Create(Owner: TComponent);
begin
  inherited;
  FAnimationLifetime:= 1000;
end;

procedure TAutoStopCustomTrayIcon.DoAnimate;
begin
  FAnimationStartTime := TimeGetTime;
  SetAnimate(True);
end;

procedure TAutoStopCustomTrayIcon.DoOnAnimate(Sender: TObject);
begin
  inherited;

  if (TimeGetTime > FAnimationStartTime+FAnimationLifetime ) then
    SetAnimate(False);
end;

function TAutoStopCustomTrayIcon.GetAnimate: Boolean;
begin
  Result := inherited Animate;
end;

procedure TAutoStopCustomTrayIcon.SetAnimationLifetime(Value: Integer);
begin
  if (Value < 0) then
    FAnimationLifetime := 0
  else
    FAnimationLifetime := Value;
end;

end.
```

<h3>プロパティのデフォルト値</h3>
マジックナンバー「1000」が２回も出てきて気持ち悪いですが、それぞれ役割が違いました。
（interfaceの方）

```delphi
property AnimationLifetime:Integer read FAnimationLifetime write SetAnimationLifetime default 1000;
```

これは、（恐らく）DelphiのIDE上で、「プロパティに設定されている値がデフォルト値かどうか」を判定する為に使用されています。
上の例だと、プロパティに1000が設定されていると細字、それ以外の値だと太字強調されるようになります。
</br>

(constructorの方）

```delphi
constructor TAutoStopCustomTrayIcon.Create(Owner: TComponent);
begin
  inherited;
  FAnimationLifetime:= 1000;
end;
```

こちらは、コンポーネントをフォームに配置した際の「IDEのプロパティにデフォルトで入力されている値」を決めています。
基本、宣言部と同値で良いような気がしました。（別にしておくとこういう使い方が！など有りましたら是非・・）

<h3>継承元プロパティの書き込みを防ぐ</h3>
今回はアニメーションの停止を自動で行うので、通常のアニメーション状態を制御するAnimateプロパティを読み取り専用とし、アニメ開始時はDoAnimateメソッドを呼び出すように設計してみました。
プロパティを再宣言してあげれば良い様子。デス。

このあたり。

```delphi
property Animate:Boolean read GetAnimate;
```

```delphi
function TAutoStopCustomTrayIcon.GetAnimate: Boolean;
begin
  Result := inherited Animate;
end;
```

</br>

<h2>４．使ってみる</h2>
<h3>コンポーネントのインストール</h3>
最初にメニューのプロジェクト → MyPackageのコンパイル を選択します。

特にエラーがなければ、コンポーネントのインストールが可能です。
プロジェクトマネージャ上で、MyPackage.bplを右クリックし、インストールを選択します。
![ss10.png](https://qiita-image-store.s3.amazonaws.com/0/33684/ecb8946c-5f7e-c7ca-0049-68fbd58b53b7.png)

上手く行けば、以下のようなダイアログが出るはず。
![キャプチャ-11.png](https://qiita-image-store.s3.amazonaws.com/0/33684/d40dce9d-f93b-8a6a-adbf-abb193f928db.png)
</br>

<h3>ライブラリパスの設定</h3>
<b>コンポーネントを使用してみる前に</b>、ライブライパスを設定しておきましょう。
</br>
メニューの「ツール」→「オプション」を選択し、
![ss12.png](https://qiita-image-store.s3.amazonaws.com/0/33684/64d750b2-4ba3-4d46-af89-0deaddab204e.png)

ライブラリ・・の・・ライブラリパス・・
![ss13.png](https://qiita-image-store.s3.amazonaws.com/0/33684/74f8179d-a28a-589f-eb11-989439a78b87.png)
こうで！
![ss14.png](https://qiita-image-store.s3.amazonaws.com/0/33684/cf3848c9-c9f1-c2a3-981b-3853974a3300.png)
こう！
![ss15.png](https://qiita-image-store.s3.amazonaws.com/0/33684/bcd079a5-567e-ba99-cf45-4941f7edaedf.png)
</br>

<h3>テストアプリケーションの作成</h3>
疲れてきました。いよいよ大詰めです。
MyPackegeは一旦閉じて、新規にプロジェクトを作ってみましょう。

コンポーネントパレット内にMyPackageが・・居た！！
![ss16.png](https://qiita-image-store.s3.amazonaws.com/0/33684/2355cae9-d020-57a4-4c7d-51a2696312be.png)

配置すると、無事プロパティにもデフォルト値が設定されていました。
![ss17.png](https://qiita-image-store.s3.amazonaws.com/0/33684/736061ed-21cc-db35-39ee-c31460f51d26.png)
</br>

あとはImageListコンポーネントを配置し、画像を読み込みます。
![icon.png](https://qiita-image-store.s3.amazonaws.com/0/33684/0f6e2e39-0bb7-360b-f4f9-6f63158a11c5.png)（←ペイントで64px＊16pxの画像を作った）

![ss18.png](https://qiita-image-store.s3.amazonaws.com/0/33684/9944d677-d78a-a051-bb15-ea5318ee57fa.png)

AutoStopTrayIcon1のIconsプロパティにImageList1を設定したり、ボタンを配置します。
![ss21.png](https://qiita-image-store.s3.amazonaws.com/0/33684/5e39e86f-bb85-ec4b-4740-5d46a437a015.png)

</br>
ボタンクリックイベントかいてー！

```delphi
procedure TForm1.Button1Click(Sender: TObject);
begin
  AutoStopTrayIcon1.Visible := True;
  AutoStopTrayIcon1.DoAnimate;
end;
```

こうだ！
![ss20.png](https://qiita-image-store.s3.amazonaws.com/0/33684/107a9157-171f-b169-b2c3-09b09d436196.png)


で、結果はこう！（地味）
![ss19.png](https://qiita-image-store.s3.amazonaws.com/0/33684/017638ba-a894-bfb6-6714-ee51217afdea.png)

上手く行けば、１０秒間、１秒ごとに画像が切り替わります。


<h1>終わりに</h1>
作ったコンポーネント／パッケージを社内で共有する場合は、
D:\MyConponent　内のファイルをバージョン管理下に置けばよいのかな？と思います。
各自ローカルにチェックアウトして、パッケージのインストール・ライブラリパスの設定が必要になると思います。

こうした方が楽！とか、間違っている点がありましたら、ご指摘いただければと思います。