Delphi FMXのTImageからカスタムコンポーネントを作る際の注意点

Delphi 11.0でFMXのTImageからカスタムコンポーネントを作ったのですが，その際に引っ掛かったことを記録しておきたいと思います。
~~私の不勉強から間違った理解をしているのかもしれません。より良い方法をご存知の方，ぜひ教えてください！~~
@pik Jun HOSOKAWA様 いつもありがとうございます。加筆修正しました。

#1. コンポーネントの新規作成
VCLのカスタムコンポーネントと作り方は同じです。
__コンポーネント__メニューから__コンポーネントの新規作成__を選択
![Snap0001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33902/2ce73ae6-0ac5-d8f4-b7a5-fddf42cb96b5.png)
__継承元コンポーネント＿＿ダイアログで**TImage**を選択し， __次へボタン__ をクリック
![Snap0002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33902/efb02622-5904-ba9b-160f-68b4f993a8fa.png)
新規コンポーネントの名前とユニットの名前を選択し， __次へボタン__ をクリック
![Snap0003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33902/906b081b-a2a7-cd10-703d-b31abcfb0b84.png)
ユニットの作成ダイアログで， __ユニットの作成__ を選択し， __完了__ ボタンをクリック
![Snap0004.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33902/f1865f87-6a3a-3684-2be0-18cd993ee536.png)
これで以下のようなユニットが生成されます。

```Image1.pas
unit Image1;

interface

uses
  System.SysUtils, System.Classes, FMX.Types, FMX.Controls, FMX.Objects;

type
  TImage1 = class(TImage)
  private
    { Private 宣言 }
  protected
    { Protected 宣言 }
  public
    { Public 宣言 }
  published
    { Published 宣言 }
  end;

procedure Register;

implementation

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

end.
```

#2. コンストラクターの生成
以下のようにPublic宣言に，コンストラクターを記述します。

```Image1.pas
・・・
  public
    { Public 宣言 }
    constructor Create(Sender:TObject); override;
・・・
```
**override; は必ず記述する必要があります。**
~~これを書かなくてもコードから生成する場合には問題なく動作しますが，このカスタムコンポーネントを，パッケージに入れてインストールしてIDEからフォームに張り付けると，なぜかこのCreateで記述したコードが全く実行されていません。(Delphi 11.0 2022/01/06現在)
私はカスタムクラスを作るときにCreateするときに引数を追加することが結構あるので，overrideを書かないことがこれまでもありました。引数が追加したときにoverrideをつけるとコンパイルエラーが出てしまうためです。~~
一般的に引数を追加した新しいconstructor Createを作らない限りは必ずoverrideします。overrideしないと別のコンストラクタと見なされ，上位のコンストラクタが実行されるようです。
インストール可能なカスタムコンポーネントを作るときには引数の追加をしないので，必ずoverrideします。
overrideを書かないとコードから呼び出すと動くのにパッケージに入れるとエラーが表示されないのに動作しないという現象が起きます。

参考資料
[DocWiki コンストラクタのオーバーライド](https://docwiki.embarcadero.com/RADStudio/Alexandria/ja/%E3%82%B3%E3%83%B3%E3%82%B9%E3%83%88%E3%83%A9%E3%82%AF%E3%82%BF%E3%81%AE%E3%82%AA%E3%83%BC%E3%83%90%E3%83%BC%E3%83%A9%E3%82%A4%E3%83%89)
[DocWiki メソッドのオーバーライド](https://docwiki.embarcadero.com/RADStudio/Alexandria/ja/%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%AE%E3%82%AA%E3%83%BC%E3%83%90%E3%83%BC%E3%83%A9%E3%82%A4%E3%83%89)
[DocWiki メソッド]
(https://docwiki.embarcadero.com/RADStudio/Alexandria/ja/%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%EF%BC%88Delphi%EF%BC%89)

記述した行にカーソルを置いて， **CTRL-SHIFT-C** を押すと，以下のように自動的に実装部が書かれます。

```Image1.pas
・・・
constructor TImage1.Create(Sender: TObject);
begin
  inherited;

end;
・・・
```
#3. Bitmapメンバーの取り扱い
FMXのTImageのカスタムコンポーネントでは，Bitmapに対して直接描画をすることができます。
今回作ったTImage1だったら以下のように書くことができます。

```Image1.pas
・・・
procedure TImage1.DrawSomething;
begin
  Bitmap.SetSize(Trunc(Width),Trunc(Height));
  Bitmap.Canvas.BeginScene;
  ・・・Bitmap.Canvasへの描画手続き
  Bitmap.Canvas.EndScene;
end;
・・・
```
コードから以下のように生成すれば下記のコードは動きます。

```unit1.pas
・・・
uses
  Image1;

procedure TForm1.FormCreate(Sender:TObject);
begin
  ・・・
  Image11:=TImage1.Create(Self);
  Image11.Parent:=Self;
  Image11.Position:=PointF(10.0, 10.0);

  ・・・
end;
・・・
```

しかし，これをパッケージに入れてインストールして使おうとすると，

**BeginSceneがない** 旨のエラーが表示され ~~ます~~ ることがあります。

そこでImage1.pasを以下のように書き換えると動きます。

```Image1.pas
・・・
procedure TImage1.DrawSomething;
begin
  TThread.ForceQueue(nil,
    procedure
    begin
      Bitmap.SetSize(Trunc(Width),Trunc(Height));
      Bitmap.Canvas.BeginScene;
      ・・・Bitmap.Canvasへの描画手続き
      Bitmap.Canvas.EndScene;
    end
  );
end;
・・・
```
~~一旦 Bitmapと同じサイズのDBitmapを生成し，それに描画して，Bitmapにコピーします。~~
Bitmap.Canvasが使えるようになった後で処理するため，描画の手順を新しいThreadを作ってメインスレッドにSynchronizeします。

#4. まとめ
FMXでTImageを継承してカスタムコンポーネントを生成するときには，以下のようにVCLとは違った配慮が必要になることが分かりました。

1. **コンストラクターは必ずoverrideすること。** overrideしないとCreate手続きで記述された命令は ~~IDEから追加されたコンポーネントでは~~ 明示的に指定しないと実行されない。
2. **Bitmapメンバーに直接描画しない**。 ~~新たに別のBitmapを生成してそれに描画し，描画結果をBitmapにAssignする。~~ Bitmapの描画はBitmap.Canvasが描画可能になるまで待つ必要があるので，新しいThreadを作って メインスレッドにSynchronizeする。そうしないとBitmap.Canvasが描画できないタイミングだとBeginSceneのエラーが出ることがある。

FMXでカスタムコンポーネントを作っている時，コードから呼び出したら動くのにIDEにインストールしたら動かなくなった時にお役に立てたらうれしいです。
~~このアイデアは私の見当違いかもしれません，，問題点をご指摘いただけるとありがたいです。~~
@pik Jun HOSOKAWA様 いつもありがとうございます。加筆修正しました。