views:

195

answers:

4

i'm writing a delphi 2009 app that uses a TTreeView on a docking panel.

i saw i could make big simplifications in my app if i subclassed the TTreeNode. the tree view it's on is placed on a docking panel.

TInfoTreeNode=class(TTreeNode)
private
  // remember some stuff
public
end;

procedure TfraInfoTree.tvInfoCreateNodeClass(Sender: TCustomTreeView;
  var NodeClass: TTreeNodeClass);
begin
  NodeClass:=TInfoTreeNode;
end;

i think i've hit a wall though...each "TInfoTreeNode" instance needs to remember things about itself. since the handles are freed when the panel containing the TTreeView auto-hides, the classes are destroyed.

that's a problem because then everything the classes knew is then forgotten.

is there a way around this (other than reloading every TInfoTreeNode from the database again)?

thank you!

+3  A: 

IIRC, the Tag Data property on each TTreeNode instance is preserved through the handle rebuild.

You could either use this as an index into a List containing objects with additional information, or use type-casting to store an object reference and access the objects directly.

Bevan
That class has no `Tag` property. Maybe you're thinking of the `Data` property instead.
Rob Kennedy
Ah yes, well, it's been a while since I did some Delphi stuff. Entirely probable my memory is failing me!
Bevan
+1, good answer. IMO the data structure should definitely exist elsewhere, and the tree should only reference it (doesn't matter whether via pointers or handles, like list indexes) to make object ownership clearer and properly decouple data from presentation.
mghie
A: 

the problem is caused by the wrong implementation of your custom TreeNode - it doesn't preserve its information when the TreeView's parent window gets recreated after it has been hodden. As a solution, create a TTreeView descendant and override its DestroyWnd method, to preserve your custom values. For example, take a look at how the TCustomTreeView.DestroyWnd method is implemented.

Joe Meyer
TTreeNode has no DestroyWnd method. That's a method of the TTreeView control that contains all the nodes.
Rob Kennedy
A: 

Having looked at TCustomTreeView.DestroyWnd like Joe Meyer proposes, I would suggest you revert to using the TreeNode.Data property, and store a reference to objects of a new class inheriting from TObject directly. The OnDeletion event of the TreeView offers a good spot to put the destruction code: "TMyObject(Node.Data).Free;"

Usage is pretty similar except you'll need to use "TMyObject(Node.Data)" instead of "TMyNode(Node)". A warning though: experience has taught me to pay close attention not to forget the ".Data" part, since "TMyObject(Node)" will not throw a compile error and raise access violations at run-time.

Stijn Sanders
A: 

thank you all for your replies!

i have for 10 years been using the tree view using TTreeNode's data property. i wanted to be free of:

  • setting the Data property
  • creating/destroying the "data" object in a manner so there are no memory leaks

i have used the Data property for an ID number in the past as well.

today, my nodes have GUIDs to find their data in the database so they don't "fit" into the Data property anymore.

using a descendant of TTreeNode seems to have addressed my wishes nicely but in order to make that work nicely i had to do a few things:

  • handle TTreeView.OnCreateNodeClass event
  • handle TTreeView.OnDeletion event to retrieve latest data from the nodes before they are destroyed
  • handle TTreeView.OnAddition event to: 1) maintain a simple list of the nodes 2) set the node's Data property so we can use it to find the place in the list allocated for storing it's data.

here's the code:

  TInfoTreeNodeMemory=record
    ...
  end;

  TInfoTreeNode=class(TTreeNode)
  private
    m_rInfoTreeNodeMemory:TInfoTreeNodeMemory;
  public
    property InfoTreeNodeMemory:TInfoTreeNodeMemory read m_rInfoTreeNodeMemory write m_rInfoTreeNodeMemory;
  end;

  TInfoTreeNodeMemoryItemList=class
  private
    m_List:TList<TInfoTreeNodeMemory>;
  public
    constructor Create;
    destructor Destroy; override;

    procedure HandleOnDeletion(Node: TInfoTreeNode);
    procedure HandleOnAddition(Node: TInfoTreeNode);
  end;

  TfraInfoTree = class(TFrame)
    tvInfo: TTreeView;
    procedure tvInfoCreateNodeClass(Sender: TCustomTreeView;
      var NodeClass: TTreeNodeClass);
    procedure tvInfoDeletion(Sender: TObject; Node: TTreeNode);
    procedure tvInfoAddition(Sender: TObject; Node: TTreeNode);
  private
    m_NodeMemory:TInfoTreeNodeMemoryItemList;
   ...

procedure TfraInfoTree.tvInfoCreateNodeClass(Sender: TCustomTreeView;
  var NodeClass: TTreeNodeClass);
begin
  // THIS IS VITAL!
  NodeClass:=TInfoTreeNode;
end;

procedure TfraInfoTree.tvInfoDeletion(Sender: TObject; Node: TTreeNode);
begin
  m_NodeMemory.HandleOnDeletion(TInfoTreeNode(Node));
end;

procedure TfraInfoTree.tvInfoAddition(Sender: TObject; Node: TTreeNode);
begin
  m_NodeMemory.HandleOnAddition(TInfoTreeNode(Node));
end;

g_icTreeNodeNotInList=MAXINT;

procedure TInfoTreeNodeMemoryItemList.HandleOnDeletion(Node: TInfoTreeNode);
var
  iPosition:integer;
begin
  iPosition:=integer(Node.Data);

  if iPosition=g_icTreeNodeNotInList then
    raise Exception.Create('Node memory not found!')
    else
    // we recognize this node; store his data so we can give it back to him later
    m_List[iPosition]:=Node.InfoTreeNodeMemory;
end;

procedure TInfoTreeNodeMemoryItemList.HandleOnAddition(Node: TInfoTreeNode);
var
  iPosition:integer;
begin
  // "coat check" for getting back node data later
  iPosition:=integer(Node.Data);

  if iPosition=g_icTreeNodeNotInList then
    begin
      // Node.Data = index of it's data
      // can't set Node.Data in OnDeletion so we must assign it in OnAddition instead
      Node.Data:=pointer(m_List.Count);
      // this data may very well be blank; it mostly occupies space; we harvest the real data in OnDeletion
      m_List.Add(Node.InfoTreeNodeMemory);
    end
    else
    // we recognize this node; give him his data back
    Node.InfoTreeNodeMemory:=m_List[iPosition];
end;

very cool...it meets all my objectives!

to add a node to the tree, all i need to do is:

// g_icTreeNodeNotInList important so the "coat check" (TInfoTreeNodeMemoryItemList)
// can recognize this as something that's not in it's list yet.
MyInfoTreeNode:=TInfoTreeNode(tvInfo.Items.AddChildObject(nParent, sText, pointer(g_icTreeNodeNotInList))));
X-Ray