LoginSignup
4
1

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-01-05

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

1. コンポーネントの新規作成

VCLのカスタムコンポーネントと作り方は同じです。
コンポーネントメニューからコンポーネントの新規作成を選択
Snap0001.png
継承元コンポーネント__ダイアログでTImageを選択し, __次へボタン をクリック
Snap0002.png
新規コンポーネントの名前とユニットの名前を選択し, 次へボタン をクリック
Snap0003.png
ユニットの作成ダイアログで, ユニットの作成 を選択し, 完了 ボタンをクリック
Snap0004.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 コンストラクタのオーバーライド
DocWiki メソッドのオーバーライド
DocWiki メソッド

記述した行にカーソルを置いて, 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様 いつもありがとうございます。加筆修正しました。

4
1
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1