5

I want to add a TList with a TTreeViewItem and a custom class (TRoom)'s object with another. In delphi 2007 there was a field 'Data' of Pointer type which has been replaced with a TValue here which I don't know as to how to use. I have searched the internet with some stating that it can't handle custom types for the time being.

Can somebody devise a way to achieve this, except for making a hack class?

For example, the following form code should run properly:-

unit Unit1;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Rtti, System.Classes,
  System.Variants, FMX.Types, FMX.Controls, FMX.Forms, FMX.Dialogs,
  FMX.TreeView, FMX.Layouts, FMX.Edit;

type
  TRoom = class

    ID : WORD;
    Name : String;

  end;

  TForm1 = class(TForm)
    TreeView1: TTreeView;
    TreeViewItem1: TTreeViewItem;
    Button1: TButton;
    Edit1: TEdit;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}

procedure TForm1.Button1Click(Sender: TObject);
var
  List : TList;
begin

  // Get The List From TreeViewItem1
  // pani's Solution  - List := TList ( TreeViewItem1.TagObject );

  Edit1.Text := TRoom ( List.First ).Name;

end;

procedure TForm1.FormCreate(Sender: TObject);
var
  Room : TRoom;
  List : TList;

begin

  List := TList.Create;
  Room := TRoom.Create;
  Room.ID := 5;
  Room.Name := IntToStr ( 5 );
  List.Add ( Room );

  // Add The List To TreeViewItem1    
  // pani's Solution  - TreeViewItem1.TagObject := List;

end;

end.
Umair Ahmed
  • 2,420
  • 1
  • 21
  • 40
  • `TValue` is a variant type, use `Data.AsObject` – Free Consulting Nov 22 '13 at 18:56
  • doesn't work, compiler errors 'incompatible types...' without type casting and 'invalid typecast' with. – Umair Ahmed Nov 22 '13 at 19:04
  • @JerryDodge A hack class disables styling on the control, necessitating manual styling assignment and calls for update. – Umair Ahmed Nov 22 '13 at 19:08
  • 2
    You can attach it to the .TagObject property. – pani Nov 23 '13 at 08:54
  • @pani Your answer does the job splendidly for both the custom class and TList. If you post it as an answer, I'll accept it. – Umair Ahmed Nov 24 '13 at 10:12
  • 1
    @UmairAhmed `Data` is not what you or I thought it is. `TagObject` will do what you want. Sorry for misleading you. – David Heffernan Nov 24 '13 at 11:04
  • @DavidHefferman Can you please give an insight on the conclusion you came to? – Umair Ahmed Nov 24 '13 at 11:23
  • The `Data` property is a `TValue` with a virtual getter and setter. There is no `TValue` field behind it. So assigning to it ends up in `TTextControl.SetData` as it happens. And that is actually implemented as `Text := Value.ToString`. So your value is lost. From what I can glean from the so-called documentation, `Data` is used for data binding. I think you can override the getter and setter and do your own interpretation. But since FMX is not documented, I'm not sure. `TagObject` certainly works but I've no idea how the FMX design intends you to solve the problem. – David Heffernan Nov 24 '13 at 12:55
  • Once again I'm really sorry for mis-leading you and wasting your time. I'm not even certain that I've learnt anything from it all. Only a deeper realisation of how lacking FMX is in terms of docs. It beggars belief that Emba would invest so much into this framework and not make any effort to write documentation. – David Heffernan Nov 24 '13 at 12:56
  • Some other comments. Use as for a type checked cast. And don't use TList these days. Use generic TList. – David Heffernan Nov 24 '13 at 13:57

2 Answers2

4

If you want to "attach" an object to TControl, TControl's parent class TFmxObject introduces the .TagObject property that stores any object value.

Besides using this property you can also use the .Tag property with typecasting into NativeInt and your wanted class type, for example: TreeViewItem1.Tag := NativeInt(List); and List := TList(TreeViewItem1.Tag);

pani
  • 1,075
  • 6
  • 13
1

In in a 'small "g"' generic fashion, the Data property of a FMX control should get or set the control's core value. In a TImage case this will be the bitmap displayed, for a TEdit the text and so on. As such, its purpose is completely different to the Data property of a VCL tree view item, which is to hang an arbitrary piece of data off of the object.

As pani rightly answers, if you want to hang an arbitrary object reference to a FMX tree view item, then you can use TagObject. That said, and notwithstanding irritations concerning FMX's bodging of proper OOP behaviour (see here), if you are creating tree view items dynamically then a better way might be to derive a custom TTreeViewItem descendant:

uses System.Generics.Collections;

type
  TRoomTreeViewItem = class(TTreeViewItem)
    RoomList: TList<TRoom>; //better use a generic than non-generic list as mentioned above
  end;

Or, if the lifetime of a room list is the same as the lifetime of the tree view item it is associated with, you could actually encapsulate the list in the item:

type
  TRoomTreeViewItem = class(TTreeViewItem)
  strict private
    FRoomList: TObjectList<TRoom>;
    function GetRoom(Index: Integer): TRoom;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    function GetEnumerator: TEnumerator<TRoom>;
    function AddRoom: TRoom;
    property Rooms[Index: Integer] read GetRoom;
  end;

constructor TRoomTreeViewItem.Create(AOwner: TComponent);
begin
  inherited;
  FRoomList := TObjectList<TRoom>.Create;
end;

destructor TRoomTreeViewItem.Destroy;
begin
  FRoomList.Free;
  inherited;
end;

function TRoomTreeViewItem.GetEnumerator: TEnumerator<TRoom>;
begin
  Result := FRoomList.GetEnumerator;
end;

function TRoomTreeViewItem.GetRoom(Index: Integer): TRoom;
begin
  Result := FRoomList[Index];
end;

function TRoomTreeViewItem.AddRoom: TRoom;
begin
  Result := TRoom.Create;
  FRoomList.Add(Result);
end;

Some people may consider the second variant a terrible conflation of non-UI with UI code however - personally I don't oppose it (indeed, that's why I've suggested it), though YMMV.

Chris Rolliston
  • 4,788
  • 1
  • 16
  • 20
  • I have tried that and got into the same mind boggling styling issues, the linked article does prove insightful for their mitigation though. I solved them another way, still I think the greater useability isn't worth the extra mile, if only a single object needs to be attached, otherwise I can't refute your argument. – Umair Ahmed Nov 25 '13 at 09:38
  • @UmairAhmed - you got styling issues when deriving directly from TTreeViewItem...? – Chris Rolliston Nov 25 '13 at 10:17
  • Yes, I did, which I resolved by passing and setting the parent in the new constructor, it worked where the creation was being done in a BeginUpdate/EndUpdate block otherwise it took a manual set to the default treeviewitemstyle to get them to work. I didn't look for the protected FStyleLookup. – Umair Ahmed Nov 25 '13 at 10:24
  • @UmairAhmed - ah, OK. That's another bug indeed (and affects more than TTreeViewItem I've found...) – Chris Rolliston Nov 25 '13 at 10:42