Delphi 11.0でFMXのTImageからカスタムコンポーネントを作ったのですが,その際に引っ掛かったことを記録しておきたいと思います。
私の不勉強から間違った理解をしているのかもしれません。より良い方法をご存知の方,ぜひ教えてください!
@pik Jun HOSOKAWA様 いつもありがとうございます。加筆修正しました。
1. コンポーネントの新規作成
VCLのカスタムコンポーネントと作り方は同じです。
__コンポーネント__メニューから__コンポーネントの新規作成__を選択
__継承元コンポーネント__ダイアログでTImageを選択し, 次へボタン をクリック
新規コンポーネントの名前とユニットの名前を選択し, 次へボタン をクリック
ユニットの作成ダイアログで, ユニットの作成 を選択し, 完了 ボタンをクリック
これで以下のようなユニットが生成されます。
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宣言に,コンストラクターを記述します。
・・・
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 コンストラクタのオーバーライド
DocWiki メソッドのオーバーライド
[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 を押すと,以下のように自動的に実装部が書かれます。
・・・
constructor TImage1.Create(Sender: TObject);
begin
inherited;
end;
・・・
3. Bitmapメンバーの取り扱い
FMXのTImageのカスタムコンポーネントでは,Bitmapに対して直接描画をすることができます。
今回作ったTImage1だったら以下のように書くことができます。
・・・
procedure TImage1.DrawSomething;
begin
Bitmap.SetSize(Trunc(Width),Trunc(Height));
Bitmap.Canvas.BeginScene;
・・・Bitmap.Canvasへの描画手続き
Bitmap.Canvas.EndScene;
end;
・・・
コードから以下のように生成すれば下記のコードは動きます。
・・・
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を以下のように書き換えると動きます。
・・・
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とは違った配慮が必要になることが分かりました。
-
コンストラクターは必ずoverrideすること。 overrideしないとCreate手続きで記述された命令は
IDEから追加されたコンポーネントでは明示的に指定しないと実行されない。 -
Bitmapメンバーに直接描画しない。
新たに別のBitmapを生成してそれに描画し,描画結果をBitmapにAssignする。Bitmapの描画はBitmap.Canvasが描画可能になるまで待つ必要があるので,新しいThreadを作って メインスレッドにSynchronizeする。そうしないとBitmap.Canvasが描画できないタイミングだとBeginSceneのエラーが出ることがある。
FMXでカスタムコンポーネントを作っている時,コードから呼び出したら動くのにIDEにインストールしたら動かなくなった時にお役に立てたらうれしいです。
このアイデアは私の見当違いかもしれません,,問題点をご指摘いただけるとありがたいです。
@pik Jun HOSOKAWA様 いつもありがとうございます。加筆修正しました。