Delphi 10.1 BerlinのFireMonekyアプリケーションで実行中のOSを(Windows/OSX/Android/iOS)を取得する

Delphi 10.1 BerlinのFireMonekyアプリケーションで実行中のOSを(Windows/OSX/Android/iOS)を取得する

実行中のプラットフォームはTOSVersionレコードのPlatformフィールドで取得できます。

戻り値は次のような定数になっています。

TPlatform = (pfWindows, pfMacOS, pfiOS, pfAndroid, pfWinRT, pfLinux);

次のコードは、アプリケーションを実行しているOSを表示します。

uses
  FMX.DialogService;

procedure TForm1.Button1Click(Sender: TObject);
const
  OSNames: array [0 .. 3] of String = ('Windows', 'OSX', 'iOS', 'Android');
var
  OSName: string;
begin
  OSName := OSNames[Integer(TOSVersion.Platform)];
  FMX.DialogService.TDialogService.ShowMessage(OSName);
end;

android

ios

osx

windows

Delphi 10.1 Berlinのスタイルを使い方の備忘録

Delphi 10.1 Berlinのスタイルを使い方の備忘録

登録されているスタイルを取得する

uses
  FMX.Styles;

  // 登録されているスタイルを列挙します。
  TStyleManager.EnumStyleResources(
    procedure(const AResourceName: string; const APlatform: TOSPlatform)
    begin
      ListBoxStyleNames.Items.Add(AResourceName)
    end);

設定されているスタイルオブジェクトを取得する

var
  StyleResource: TFmxObject;

StyleResource := TStyleManager.ActiveStyle(Self);

スタイル名からスタイルオブジェクトを取得する

var
  StyleResource: TFmxObject;

StyleResource := TStyleManager.GetStyleResource(ResourceName);

スタイルオブジェクトからスタイルの説明を取得する

  // スタイル説明を取得します
  StyleDescription := TStyleManager.FindStyleDescriptor(StyleResource);
  Memo1.Lines.Add('スタイルの作者=' + StyleDescription.Author);
  Memo1.Lines.Add('スタイルの作成者のeメールアドレス=' + StyleDescription.AuthorEMail);
  Memo1.Lines.Add('スタイルの作成者のWebサイトのURL =' + StyleDescription.AuthorURL);
  Memo1.Lines.Add('スタイルがモバイルプラットフォーム上で使用できるか=' +
    BoolToStr(StyleDescription.MobilePlatform, True));
  Memo1.Lines.Add('スタイルのタイトル=' + StyleDescription.Title);
  Memo1.Lines.Add('スタイルのバージョン=' + StyleDescription.Version);

デフォルトのスタイルを設定する

TStyleManager.SetStyle(nil);

スタイルをファイルから読み込んで適用する

AFileName := 'C:\~\Air.Style'
TStyleManager.SetStyleFromFile(AFileName);

サンプルアプリケーション

登録されているスタイルをリストボックスに一覧表示します。
リストボックスのスタイルを選択すると、スタイルの情報をメモに表示します。

ソースコードはこちら→StyleSample

Delphi 10.1 BerlinのFireMonkeyアプリケーションでTStringGridでカスタムエディタを使用する

TStringGridのセルに入力するエディタをカスタマイズする方法を紹介します。

TStringGridでカスタマイズしたエディタを使用するには

エディタをカスタマイズするには、TStringGridのOnCreateCustomEditorイベントを使用します。
OnCreateCustomEditorイベントで、カスタマイズしたエディタを返します。

type
  TForm1 = class(TForm)
    StringGrid1: TStringGrid;
    StringColumn1: TStringColumn;
    procedure StringGrid1CreateCustomEditor(Sender: TObject;
      const Column: TColumn; var Control: TStyledControl);
  private
    { private 宣言 }
  public
    { public 宣言 }
  end;

procedure TForm1.StringGrid1CreateCustomEditor(Sender: TObject;
  const Column: TColumn; var Control: TStyledControl);
begin
  // ここでカスタマイズしたエディタを返す
  Control := TMyEdit.Create(Sender as TComponent);
end;

カスタマイズしたエディタを作成する

入力した文字を大文字に変換するカスタムエディタのサンプルです。
TMyEditは入力された値を大文字に変換します。

TEditクラスを継承したTMyEditクラスを作成します

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

constructor TMyEdit.Create(AOwner: TComponent);
begin
  inherited;
end;

ICellControlインターフェースも継承します。

セルの編集が終わった後、列オブジェクト(TColumn)に対してセルの値の変更を通知する必要があります。
ICellControlインターフェースを継承すると、編集する列のオブジェクトを取得できるようになります。

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

インターフェースが要求するメソッドを追加します。

type
  TMyEdit = class(TEdit, ICellControl)
  private
    [Weak]
    FColumn: TColumn;
    { ICellControl }
    function GetColumn: TColumn;
    procedure SetColumn(const Value: TColumn);
  end;

function TMyEdit.GetColumn: TColumn;
begin
  // 編集対象の列
  Result := FColumn;
end;

procedure TMyEdit.SetColumn(const Value: TColumn);
begin
  // 編集対象の列
  FColumn := Value;
end;

セルの値が変更されたら、列オブジェクトに変更を通知します。

type
  TMyEdit = class(TEdit, ICellControl)
  private
    procedure ChangeProc(Sender: TObject);
    ...
  end;

procedure TMyEdit.ChangeProc(Sender: TObject);
begin
  // 列に対して変更を通知する。これがないと編集した値が登録されない
  if FColumn <> nil then
    FColumn.EditorModified;
end;

constructor TMyEdit.Create(AOwner: TComponent);
begin
  inherited;
  // 入力されたらChangeProc処理を行う
  OnChangeTracking := ChangeProc;
end;

入力された値を大文字に変換する処理を追加します。

type
  TMyEdit = class(TEdit, ICellControl)
  private
    procedure ValidateText(Sender: TObject; var Text: string);
    ...
  end;

constructor TMyEdit.Create(AOwner: TComponent);
begin
  inherited;
  // 入力されたらChangeProc処理を行う
  OnChangeTracking := ChangeProc;
  // 入力が終わったときにValidateText処理を行う
  OnValidate := ValidateText;
end;

procedure TMyEdit.ValidateText(Sender: TObject; var Text: string);
begin
  // 入力が終わったら大文字に変換する
  Text := UpperCase(Text);
end;

以上で完成です。

全体のコードは次のようになります。

type
  TMyEdit = class(TEdit, ICellControl)
  private
    [Weak]
    FColumn: TColumn;
    procedure ChangeProc(Sender: TObject);
    { ICellControl }
    function GetColumn: TColumn;
    procedure SetColumn(const Value: TColumn);
    procedure ValidateText(Sender: TObject; var Text: string);
  public
    constructor Create(AOwner: TComponent); override;
  end;

{ TMyEdit }

procedure TMyEdit.ChangeProc(Sender: TObject);
begin
  // 列に対して変更を通知する。これがないと編集した値が登録されない
  if FColumn <> nil then
    FColumn.EditorModified;
end;

constructor TMyEdit.Create(AOwner: TComponent);
begin
  inherited;
  // 入力されたらChangeProc処理を行う
  OnChangeTracking := ChangeProc;
  // 入力が終わったときにValidateText処理を行う
  OnValidate := ValidateText;
end;

function TMyEdit.GetColumn: TColumn;
begin
  // 編集対象の列
  Result := FColumn;
end;

procedure TMyEdit.SetColumn(const Value: TColumn);
begin
  // 編集対象の列
  FColumn := Value;
end;

procedure TMyEdit.ValidateText(Sender: TObject; var Text: string);
begin
  // 入力が終わったら大文字に変換する
  Text := UpperCase(Text);
end;

サンプルアプリケーション

フォームにTStringGridを配置し、TStringColumnを追加します。

グリッドのOnCreateCustomEditorイベントを追加します。

procedure TForm1.StringGrid1CreateCustomEditor(Sender: TObject;
  const Column: TColumn; var Control: TStyledControl);
begin
  Control := TMyEdit.Create(Sender as TComponent);
end;

アプリケーションを実行します。

セルに入力します。

001

入力を完了すると、セルに入力した値が大文字に変換されます。

002

ソースコードのダウンロードはこちら→GridCustomEditor

Delphi iOSアプリケーションでネイティブコントロールの上に画像を表示する

FireMonkeyのコントロールはネイティブコントロールの上に表示することができません。

Delphi iOSアプリケーションでネイティブコントロールの上に画像を表示するために
ネイティブコントロールのUIImageViewをラップしたクラスを作成しました。

UIImageViewもネイティブコントロールのため、ネイティブコントロールの上に画像を表示できます。

使用例

FUIImageView := FMX.iOS.UIImageView.TUIImageView.Create(Layout1);
FUIImageView.ImageWrapMode := TImageWrapMode.Fit;
FUIImageView.SetBitmap(Bitmap);

ソースコード

unit FMX.iOS.UIImageView;

interface

uses
  iOSapi.UIKit, iOSapi.CoreGraphics,
  System.Classes, System.Types, FMX.Graphics,
  FMX.Layouts, FMX.Forms, FMX.Objects;

type
  TUIImageView = class
  private
    FUIImageView: iOSapi.UIKit.UIImageView;
    FLayout: TLayout;
    FImageWrapMode: TImageWrapMode;
    procedure SetImageWrapMode(const Value: TImageWrapMode);
    function GetForm: TCustomForm;
    function GetLayoutRect: TRect;
    function getFrame: CGRect;
  public
    constructor Create(const ALayout: TLayout);
    destructor Destroy; override;
    procedure SetBitmap(const ABitmap: TBitmap);
    procedure Resize;
    property ImageWrapMode: TImageWrapMode read FImageWrapMode
      write SetImageWrapMode;
    property frame: CGRect read getFrame;
    property ImageView: iOSapi.UIKit.UIImageView read FUIImageView;

  end;

implementation

uses

  Macapi.Helpers, FMX.Helpers.iOS, iOSapi.Foundation, FMX.Platform.iOS,

  System.SysUtils, FMX.Types;

{ TUIImageView }

constructor TUIImageView.Create(const ALayout: TLayout);
var
  AForm: TCustomForm;
begin
  FLayout := ALayout;

  FUIImageView := iOSapi.UIKit.TUIImageView.Wrap
    (iOSapi.UIKit.TUIImageView.Alloc.init);
  FUIImageView.retain;
  Resize;

  AForm := GetForm;
  if AForm <> nil then
    WindowHandleToPlatform(AForm.Handle).View.addSubview(FUIImageView);

  Self.ImageWrapMode := TImageWrapMode.Stretch;
end;

destructor TUIImageView.Destroy;
begin
  if FUIImageView <> nil then
  begin
    FUIImageView.removeFromSuperview;
    FUIImageView.release;
    FUIImageView := nil;
  end;

  inherited;
end;

function TUIImageView.GetForm: TCustomForm;
var
  Parent: TFmxObject;
begin
  Parent := FLayout.Parent;

  while True do
  begin
    if Parent = nil then
      Exit(nil);
    if Parent is TForm then
      Exit(Parent as TForm);
    Parent := Parent.Parent;
  end;
end;

function TUIImageView.getFrame: CGRect;
begin
  Result := FUIImageView.frame;
end;

function TUIImageView.GetLayoutRect: TRect;
var
  ATopLeft, ABottomRight: TPoint;
begin
  if FLayout = nil then
  begin
    Log.d('Layout = nil');
    Exit(Rect(0, 0, 0, 0));
  end;

  ATopLeft := FLayout.LocalToAbsolute(PointF(0, 0)).Truncate;
  ABottomRight := ATopLeft.Add(PointF(FLayout.width, FLayout.height).Truncate);
  Result := System.Classes.Rect(ATopLeft, ABottomRight);
end;

procedure TUIImageView.Resize;
begin
  FUIImageView.setFrame(Macapi.Helpers.RectToNSRect(GetLayoutRect));
end;

procedure TUIImageView.SetBitmap(const ABitmap: TBitmap);
var
  Image: UIImage;
  AutoreleasePool: NSAutoreleasePool;
begin
  AutoreleasePool := TNSAutoreleasePool.Create;
  try
    Image := FMX.Helpers.iOS.BitmapToUIImage(ABitmap);
    FUIImageView.setImage(Image);
  finally
    AutoreleasePool.release
  end;
end;

procedure TUIImageView.SetImageWrapMode(const Value: TImageWrapMode);
begin
  FImageWrapMode := Value;

  case Value of
    TImageWrapMode.Fit: // コントロールの四角形にちょうど合うように調整します(画像の比率は保たれます)。
      FUIImageView.setContentMode(UIViewContentModeScaleAspectFit);
    TImageWrapMode.Stretch: // 画像を引き伸ばして、コントロールの四角形全体を埋めます。
      FUIImageView.setContentMode(UIViewContentModeScaleToFill);
    TImageWrapMode.Original: // 画像を元のサイズで表示します。
      FUIImageView.setContentMode(UIViewContentModeTopLeft);
    TImageWrapMode.Center: // 画像をコントロールの四角形の中央に表示します。
      FUIImageView.setContentMode(UIViewContentModeCenter);
    TImageWrapMode.Tile: // 画像を敷き詰めて(画像の数を増やして)、コントロールの四角形全体を覆います。
      raise System.SysUtils.ENotImplemented.Create('TImageWrapMode.Tile');
  end;
end;

end.

サンプルプログラム

TWebBrowserコントロールはネイティブコントロールのため、その上にコンポーネントを配置できません。

このサンプルプログラムでは、TWebBrowserコントロールの上にTUIImageViewを配置して、画像を表示します。

実行例は次のようになります。

Simulator Screen Shot 2016.06.20 0.30.50

フォームにTWebBrowserコントロールとTLayoutコントロールを配置します。
TLayoutコントロールはTUIImageViewの配置場所になります。

TUIImageView01

TUIImageView02

メニューの「プロジェクト」→「リソースと画像」を選択し、表示する画像を追加します。

TUIImageView03

今回使用した画像は次の画像です。

sample

画像を読み込む関数を作成します。

function GetImage: TBitmap;
var
  RS: TResourceStream;
begin
  Result := TBitmap.Create;
  RS := TResourceStream.Create(HInstance, 'PngImage_1', RT_RCDATA);
  try
    Result.LoadFromStream(RS);
  finally
    RS.Free;
  end;
end;

フォームにprivate変数にTUIImageViewを追加します。

type
  TForm1 = class(TForm)
    WebBrowser1: TWebBrowser;
    Layout1: TLayout;
    procedure FormCreate(Sender: TObject);
  private
    { private 宣言 }
    FUIImageView: TUIImageView;
  public
    { public 宣言 }
  end;

フォームのOnCreateイベントで、TWebBrowserコントロールにURLを指定してアクセスします。

procedure TForm1.FormCreate(Sender: TObject);
begin
  WebBrowser1.Navigate('http://www.gesource.jp/weblog/');
end;

フォームのOnCreateイベントにTUIImageViewを作成する処理を追加します。

uses
  FMX.Objects;

procedure TForm1.FormCreate(Sender: TObject);
var
  Bitmap: TBitmap;
begin
  WebBrowser1.Navigate('http://www.gesource.jp/weblog/');

  FUIImageView := FMX.iOS.UIImageView.TUIImageView.Create(Layout1);
  FUIImageView.ImageWrapMode := TImageWrapMode.Fit;

  Bitmap := GetImage;
  try
    FUIImageView.SetBitmap(Bitmap);
  finally
    Bitmap.Free;
  end;
end;

アプリケーションを実行すると、ブラウザの上に画像が表示されます。

Simulator Screen Shot 2016.06.20 0.30.50

DelphiのiOSアプリで、URLスキームでアプリを起動する、アプリがなければウェブサイトを開く

sharedApplicationのcanOpenURLメソッドで、アプリをURLスキームで起動できるか調べます。
起動できるときはsharedApplicationのopenURLメソッドでアプリを起動します。
アプリを起動できないときは、ウェブサイトのURLをopenURLメソッドで開きます。

uses iOSapi.Helpers, Macapi.Helpers;

/// <summary>
/// URLスキームを呼び出す、なければURLを開く
/// </summary>
procedure OpenURLSchemaOrURL(const URLSchema, URL: string);
begin
  if TiOSHelper.SharedApplication.canOpenURL(StrToNSUrl(URLSchema)) then
    TiOSHelper.SharedApplication.openURL(StrToNSUrl(URLSchema))
  else
    TiOSHelper.SharedApplication.openURL(StrToNSUrl(URL));
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  OpenURLSchemaOrURL(
    // Google Maps for iOS アプリのURLスキーム
    'comgooglemapsurl://',
    // Google Mapのウェブサイト
    'https://www.google.co.jp/maps');
end;

関連

DelphiでiOSの端末の情報を取得する

実行中の端末がiPhoneとiPadのどちらであるかを取得する

FMX.Helpers.iOSユニットにあるIsPhone関数とIsPadを使用します。

uses
  FMX.Helpers.iOS;

procedure TForm1.Button1Click(Sender: TObject);
begin
  if IsPhone then
    ShowMessage('iPhoneです');
  if IsPad then
    ShowMessage('iPadです');
end;

Simulator Screen Shot 2016.06.18 23.13.25

Simulator Screen Shot 2016.06.18 23.17.50

モデル(機種名)や名前、バージョンを取得する

TUIDeviceで端末の情報を取得できます。

uses
  iOSapi.UIKit;

procedure TForm1.Button1Click(Sender: TObject);
var
  Model, Name, LocalizedModel, SystemName, SystemVersion: string;
begin
  // デバイスのモデル
  Model := UTF8ToString(TUIDevice.Wrap(TUIDevice.OCClass.currentDevice).model.UTF8String);
  Memo1.Lines.Add('Model=' + Model);

  // デバイスの名前
  Name := UTF8ToString(TUIDevice.Wrap(TUIDevice.OCClass.currentDevice).name.UTF8String);
  Memo1.Lines.Add('Name=' + Name);

  // デバイスのローカルバージョン
  LocalizedModel := UTF8ToString(TUIDevice.Wrap(TUIDevice.OCClass.currentDevice).localizedModel.UTF8String);
  Memo1.Lines.Add('LocalizedModel=' + LocalizedModel);

  // OS名
  SystemName := UTF8ToString(TUIDevice.Wrap(TUIDevice.OCClass.currentDevice).systemName.UTF8String);
  Memo1.Lines.Add('SystemName=' + SystemName);

  // OS名
  SystemVersion := UTF8ToString(TUIDevice.Wrap(TUIDevice.OCClass.currentDevice).systemVersion.UTF8String);
  Memo1.Lines.Add('SystemVersion=' + SystemVersion);
end;

取得出来るモデルは次のようになります。

  • iPhone
  • iPod touch
  • iPad
  • iPhone Simulator

Simulator Screen Shot 2016.06.19 11.30.05

Delphi 10.1 BelrinのFireMonkeyアプリケーションで文字列を選択するリストピッカーを表示するには

リストピッカーはIFMXPickerServiceのCreateListPickerメソッドで作成できます。

uses
  FMX.Platform;

  if TPlatformServices.Current.SupportsPlatformService(IFMXPickerService, PickerService) then
  begin
    FListPicker := PickerService.CreateListPicker;
    …

作成したリストピッカーのイベントや選択項目を設定します。

FListPicker.Parent := Button1;

// イベントの設定
FListPicker.OnValueChanged := DoListPickerValueChanged;
FListPicker.OnHide := DoListPickerHide;
FListPicker.OnShow := DoListPickerShow;

// 選択項目の設定
FListPicker.Values.Add('Delphi');
FListPicker.Values.Add('C++Builder');
FListPicker.Values.Add('RAD Studio');

// 選択されている項目の設定
FListPicker.ItemIndex := 1;

Showメソッドを呼び出すと、リストピッカーを表示できます。

FListPicker.Show;

サンプルアプリケーション

フォームにボタン(TButton)とメモ(TMemo)を配置します。

interfaceのusesにFMX.Pickersを追加します。

interface

uses
…, FMX.Pickers; //追加

フォームにprivate変数を追加します。

type
TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
private
    { private 宣言 }
    FListPicker: TCustomListPicker; //追加

フォームのOnCreateイベントを追加します。

リストピッカーを作成し、イベントや選択項目を設定します。

uses
  FMX.Platform;

procedure TForm1.FormCreate(Sender: TObject);
var
PickerService: IFMXPickerService;
begin
  if TPlatformServices.Current.SupportsPlatformService(IFMXPickerService, PickerService) then
  begin
    FListPicker := PickerService.CreateListPicker;
    FListPicker.Parent := Button1;

    // イベントの設定
    FListPicker.OnValueChanged := DoListPickerValueChanged;
    FListPicker.OnHide := DoListPickerHide;
    FListPicker.OnShow := DoListPickerShow;

    // 選択項目の設定
    FListPicker.Values.Add('Delphi');
    FListPicker.Values.Add('C++Builder');
    FListPicker.Values.Add('RAD Studio');

    // 選択されている項目の設定
    FListPicker.ItemIndex := 1;
  end;
end;

リストピッカーのイベントを追加します。
イベントが発生するとMemo1に出力します。

type
TForm1 = class(TForm)
  Button1: TButton;
  Memo1: TMemo;
  procedure FormCreate(Sender: TObject);
private
  { private 宣言 }
  FListPicker: TCustomListPicker;
  procedure DoListPickerValueChanged(Sender: TObject; const AValueIndex: Integer); // 追加
  procedure DoListPickerHide(Sender: TObject); // 追加
  procedure DoListPickerShow(Sender: TObject); // 追加
public
  { public 宣言 }
end;

procedure TForm1.DoListPickerHide(Sender: TObject);
begin
  Memo1.Lines.Add('DoListPickerHide');
end;

procedure TForm1.DoListPickerShow(Sender: TObject);
begin
  Memo1.Lines.Add('DoListPickerShow');
end;

procedure TForm1.DoListPickerValueChanged(Sender: TObject; const AValueIndex: Integer);
begin
  Memo1.Lines.Add('DoListPickerValueChanged AValueIndex=' + IntToStr(AValueIndex));
end;

ボタン(Button1)のOnClickイベントを追加します。
ボタンを押すとリストピッカーを表示します。

procedure TForm1.Button1Click(Sender: TObject);
begin
  FListPicker.Show;
end;

カスタム属性をつけたフィールドの値を取得するサンプル

カスタム属性をつけたフィールドの値を取得するサンプルを作り、カスタム属性の使い方を試してみます。

まず、次のようなカスタム属性を作成します。

type
  LogAttribute = class(TCustomAttribute)
  end;

このLogAttributeをつけたフィールドだけを出力するプログラムを作成します。

type
  TSample = class
  private
    [Log]
    FVal1: string;
    [Log]
    FVal2: string;
    FVal3: string;
  public
    constructor Create(const AVal1, AVal2, AVal3: string);
  end;

constructor TSample.Create(const AVal1, AVal2, AVal3: string);
begin
  FVal1 := AVal1;
  FVal2 := AVal2;
  FVal3 := AVal3;
end;

FVal1とFVal2に属性をつけました。
FVal3には属性はつけていません。

var
  Sample: TSample;

begin
  Sample := TSample.Create('aa', 'bbb', 'cccc');
  PrintLogAttr(Sample);
  Sample.Free;
end.

このコードを実行すると、PrintLogAttr関数でLogAttributeの属性がついているフィールドの値を出力します。

PrintLogAttr関数の実装は次のようになります。

RTTIを使用してオブジェクトの情報を取得します。
フィールドにつけた属性はTRttiFieldのGetAttributesメソッドで取得できます。

procedure PrintLogAttr(const Arg: TSample);
var
  LContext: TRttiContext;
  LType: TRttiType;
  LField: TRttiField;
  LAttr: TCustomAttribute;
  LFieldValue: string;
begin
  LContext := TRttiContext.Create;
  LType := LContext.GetType(TSample);

  // すべてのフィールドを取得する
  for LField in LType.GetFields do
  begin
    // フィールドの属性を取得する
    for LAttr in LField.GetAttributes do
    begin
      // 属性がLogAttributeであるか調べる
      if LAttr is LogAttribute then
      begin
        // 属性がLogAttributeのときはフィールドの値を取得する
        LFieldValue := LField.GetValue(Arg).AsString;
        // フィールドの値を出力する
        WriteLn(LField.Name + '=' + LFieldValue);
      end;
    end;
  end;
end;

実行結果は次のようになります。

FVal1=aa
FVal2=bbb

インターフェース参照 vs ジェネリックインターフェース制約

インターフェース参照を使用すると参照カウンターが使用されるので、意図しないタイミングでオブジェクトが破棄されてしまうことがあります。

たとえば、TPersonクラスはIPersonインターフェースを継承しています。

type
  IPerson = interface
    procedure Say;
  end;

  TPerson = class(TInterfacedObject, IPerson)
  private
    FName: string;
  public
    constructor Create(const AName: string);
    destructor Destroy; override;
    procedure Say;
  end;

{ TPerson }

constructor TPerson.Create(const AName: string);
begin
  FName := AName;
end;

destructor TPerson.Destroy;
begin
  WriteLn('Destroy');
  inherited;
end;

procedure TPerson.Say;
begin
  WriteLn('Hello ' + FName);
end;

TTestクラスのSay関数はインターフェースを引数に取ります。

type
  TTest = class
    class procedure Say(Arg: IPerson); overload; static;
  end;

class procedure TTest.Say(Arg: IPerson);
begin
  Arg.Say;
end;

次のようにTPersonクラスのオブジェクトを、インターフェースを引数に取る関数に渡すと、関数が終わったときにオブジェクトが破棄されてしまいます。

var
  Person: TPerson;
begin
  Person := TPerson.Create('Alice');
  TTest.Say(Person); // ここでPersonオブジェクトが破棄される
  Person.Say;
end.

ジェネリックインターフェース制約は参照カウンターを使用しません。

そのため、上のような問題は発生しません。

type
  TTest = class
    class procedure Say<T: IPerson>(Arg: T); overload; static;
  end;

class procedure TTest.Say<T>(Arg: T);
begin
  Arg.Say;
end;

このようにジェネリックインターフェース制約を使用します。

そうすると、参照カウンターは使用されず、オブジェクトは破棄されません。

var
  Person: TPerson;

begin
  Person := TPerson.Create('Alice');
  TTest.Say(Person); // ここでPersonオブジェクトが破棄されない
  Person.Say;
end.

Delphi 10.1 BerlinでLive Bindingsを使ってデータベースとRadioButtonを連結するサンプル

Delphi 10.1 BerlinのFireMonkeyアプリケーションでLive Bindingsを使ってデータベースの1つのフィールドと複数のRadioButtonを連結するサンプルプログラムです。

プロジェクトはこちらからダウンロードできます。

このサンプルプログラムのフォームはこのようになっています。

LiveBindings03

データベースのフィールドとラジオボタンはLive Bindingsで連結しています。

フィールドの値が「1」のときはRadioButton1をチェック
フィールドの値が「2」のときはRadioButton2をチェック
フィールドの値が「3」のときはRadioButton3をチェック
します。

Edit1に入力するとデータベースの値が更新され、ラジオボタンのチェックが更新されます。

LiveBindings02

データベースのフィールドは「Value」はInteger型のフィールドで連結する値を持ちます。
データベースのフィールドは「RadioButton1」「RadioButton2」「RadioButton3」はBoolean型のフィールドで、計算項目です。
「Value」フィールドの値が「1」のときは「RadioButton1」フィールドはTrue、
「Value」フィールドの値が「2」のときは「RadioButton2」フィールドはTrue、
「Value」フィールドの値が「3」のときは「RadioButton3」フィールドはTrue、
になります。

LiveBindings01

procedure TForm1.FDMemTable1CalcFields(DataSet: TDataSet);
begin
  DataSet.FieldByName('RadioButton1').AsBoolean := (DataSet.FieldByName('Value').AsInteger = 1);
  DataSet.FieldByName('RadioButton2').AsBoolean := (DataSet.FieldByName('Value').AsInteger = 2);
  DataSet.FieldByName('RadioButton3').AsBoolean := (DataSet.FieldByName('Value').AsInteger = 3);
end;