How to stop duplicating child components (Delphi 10.1 Berlin Firemonkey)

  

I am creating a component with a published “VisibleItems” property. When the property of an item is checked/un-checked an item is either added or removed from the component and stored in the dfm (Which works), however; when the the component is streamed from the dfm into the IDE duplicate items are created. I’ve overridden the protected “Loaded” procedure of the item (TMyTreeViewItem) and added a “CreateDefaultItem” procedure to the parent component (TDesignTimeTreeView) which appears to work for Windows but not for Android.

I tried marking properties in the parent and child components with the “stored False” directive. I’ve also tried overriding the “Loaded” procedure in the main component.

Below is a minimal version of the component. If the TMyTreeViewItem.Loaded and TDesignTimeTreeView.CreateDefaultItem are bypassed the component duplication starts to happen in the IDE when toggling between “View as Text” and “View as Form” (Alt + F12).

unit DesignTimeTreeView;

interface

uses
System.SysUtils,
System.Classes,
System.Types,
System.UITypes,

FMX.Types,
FMX.Controls,
FMX.Layouts,
FMX.TreeView;

type
{$SCOPEDENUMS ON}
TItemId = (NONE, Default, Custom, One, Two);
{$SCOPEDENUMS OFF}

TMyTreeViewItem = class(TTreeViewItem)
private
FId : TItemId;

procedure SetFId(const Value: TItemId);
protected
procedure Loaded; override;
published
property Id: TItemId read FId write SetFid;
end;

TVisibleItemsChange = procedure(Id: TItemId; Visible: Boolean) of object;

TVisibleItems = class(TPersistent)
private
FItem1 : Boolean;
FItem2 : Boolean;

FOnChange : TVisibleItemsChange;

procedure SetVisibility(Id: TItemId; Visible: Boolean);
procedure SetFItem1(const Visible: Boolean);
procedure SetFItem2(const Visible: Boolean);

property OnChange: TVisibleItemsChange read FOnChange write FOnChange;
published
property Item1: Boolean read FItem1 write SetFItem1;
property Item2: Boolean read FItem2 write SetFItem2;
end;

TDesignTimeTreeView = class(TTreeView)
private
{ Private declarations }
FVisibleItems : TVisibleItems;
FDefaultMyTreeViewItem : TMyTreeViewItem;

function GetItemById(Id: TItemId): TMyTreeViewItem;
function GetItemName(Id: TItemId): string;
procedure AddItem(Id: TItemId);
procedure OnVisibilityChange(Id: TItemId; Visible: Boolean);
procedure OnItemClick(Sender: TObject);
procedure OnItemTap(Sender: TObject; const Point: TPointF);
procedure CreateDefaultItem;
const
PREFIX = ‘StandardItem_’;
protected
{ Protected declarations }
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
published
{ Published declarations }
property VisibleItems: TVisibleItems read FVisibleItems write FVisibleItems;
end;

procedure Register;

implementation

procedure Register;
begin
RegisterComponents(‘Samples’, [TMyTreeViewItem, TDesignTimeTreeView]);
end;

{ TDesignTimeTreeView }

procedure TDesignTimeTreeView.AddItem(Id: TItemId);
var
Item : TMyTreeViewItem;
ItemName : string;
i : Integer;
Sub : TTreeViewItem;
begin
ItemName := GetItemName(Id);
Item := GetItemById(Id);

if Item <> nil then
RemoveObject(Item);

Item := TMyTreeViewItem.Create(nil);
Item.FId := Id;
Item.Text := ItemName;
Item.Name := ItemName;
Item.HitTest := True;
Item.OnClick := OnItemClick;
Item.OnTap := OnItemTap;
Item.Parent := Self;

for i := 1 to 10 do
begin
Sub := TTreeViewItem.Create(nil);
Sub.Text := ‘Sub Item (‘ + i.ToString + ‘)’;
Sub.Parent := Item;
end;
end;

constructor TDesignTimeTreeView.Create(AOwner: TComponent);
begin
inherited Create(AOwner);

ItemHeight := 46;

CreateDefaultItem;

FVisibleItems := TVisibleItems.Create;
FVisibleItems.OnChange := OnVisibilityChange;

{ Set the default values }
FVisibleItems.FItem1 := False;
FVisibleItems.FItem2 := False;
end;

procedure TDesignTimeTreeView.CreateDefaultItem;
var
Item : TMyTreeViewItem;
const
DEFAULT_ITEM_NAME = ‘DEFAULT_MYTREEVIEWITEM’;
begin
if Owner <> nil then
if Owner.FindComponent(DEFAULT_ITEM_NAME) <> nil then
begin
Item := TMyTreeViewItem(Owner.FindComponent(DEFAULT_ITEM_NAME));
if Item.Id <> TItemId.Default then
Item.Free
else
Exit;
end;

if Assigned(FDefaultMyTreeViewItem) then
FreeAndNil(FDefaultMyTreeViewItem);

FDefaultMyTreeViewItem := TMyTreeViewItem.Create(Owner);
FDefaultMyTreeViewItem.FId := TItemId.Default;
FDefaultMyTreeViewItem.Name := DEFAULT_ITEM_NAME;
FDefaultMyTreeViewItem.Text := ‘Created by TDesignTimeTreeView. This makes it so you do not have ‘ +
‘to add a TMyTreeViewItem on the form just so this component will ‘ +
‘work.’;
FDefaultMyTreeViewItem.Parent := TFmxObject(Owner);
FDefaultMyTreeViewItem.Visible := False;
end;

function TDesignTimeTreeView.GetItemById(Id: TItemId): TMyTreeViewItem;
var
i : Integer;
ItemName : string;
begin
Result := nil;
ItemName := GetItemName(Id);

for i := 0 to Count – 1 do
if Items[i].Name = ItemName then
Result := TMyTreeViewItem(Items[i]);
end;

function TDesignTimeTreeView.GetItemName(Id: TItemId): string;
begin
Result := PREFIX + Ord(Id).ToString;
end;

procedure TDesignTimeTreeView.OnItemClick(Sender: TObject);
var
Item : TMyTreeViewItem;
begin
Item := TMyTreeViewItem(Sender);

Item.Text := Ord(Item.Id).ToString
+ ‘ Clicked (‘ + FormatDateTime(‘h:nn:ss.zzzAM/PM’, Now) + ‘)’;
end;

procedure TDesignTimeTreeView.OnItemTap(Sender: TObject; const Point: TPointF);
begin
OnItemClick(Sender);
end;

procedure TDesignTimeTreeView.OnVisibilityChange(Id: TItemId; Visible: Boolean);
var
Item : TMyTreeViewItem;
begin
Item := GetItemById(Id);

if Item <> nil then
RemoveObject(Item);

if Visible then
AddItem(Id);
end;

{ TVisibleItems }

procedure TVisibleItems.SetFItem1(const Visible: Boolean);
begin
SetVisibility(TItemId.One, Visible);
end;

procedure TVisibleItems.SetFItem2(const Visible: Boolean);
begin
SetVisibility(TItemId.Two, Visible);
end;

procedure TVisibleItems.SetVisibility(Id: TItemId; Visible: Boolean);
begin
case Id of
TItemId.Custom: { Do Nothing };
TItemId.One:
FItem1 := Visible;
TItemId.Two:
FItem2 := Visible;
end;

if Assigned(FOnChange) then
FOnChange(Id, Visible);
end;

{ TMyTreeViewItem }

procedure TMyTreeViewItem.Loaded;
begin
if Id = TItemId.NONE then
begin
Self.Destroy;
Exit;
end;

if Id = TItemId.Default then
Exit;

if not (Parent is TDesignTimeTreeView) then
Self.Destroy;

inherited;
end;

procedure TMyTreeViewItem.SetFId(const Value: TItemId);
begin
{ Read Only: But we want to see it in the .fmx file of the form }
end;

end.

I expect that when VisibleItems.Item1 is checked a TMyTreeViewItem will be added to the component and stored in the dfm (Which is working). The component also appears to be streaming from the dfm to the IDE correctly. I also expect to see Item1 and Item2 on the form (If they are checked) when the program runs (Which is working for Windows).

When I drop this component onto a multi-device-applicaiton form, set VisibleItems.Item1 and Item2 to true, and finally deploy it to an Android device I get the following errors:

*.apk raised ececption class EComponentError with message ‘A component named DEFAULT_MYTREEVIEWITEM already exists’.

*.apk raised exception class Segmentation fault (11).

*.apk raised exception class Illegal instruction (4).

Comments are closed.