Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I prevent custom Tree Node Data loss for a TTreeView?

Tags:

delphi

I'm using Zarko Gajic's Store More (Custom) Data Into The Tree Node Of A Tree View to add additional strings for each node item, but I have found that if my application has been idle for a long time, the values which I have stored in the customized tree nodes disappear.

This is what my custom treenode looked like when I left yesterday

Before going home

This is what it looks like this morning (note the fMyProperty value is now empty)

Back the next day

I have confirmed that my computer never hibernates nor sleeps, but the system does turn off the monitor for power saving after 1 minute of being locked. The computer however needs to be idle for some time for this problem to occur. It's most noticeable when it's been idle overnight, but less likely to occur if only idle for 30 minutes.

The only thing I can think of which can be causing this is that the operating system is swapping the applications memory to disk, and when you reactivate your computer, it's swapped back to memory. As you can see, the FItemId is changing, so it seems to be "rebuilding" the Treeview, and thus losing the association with the custom Tree node.

I have reproduced this issue with a very simple application, code of which is below.

I know I can use another method to store additional data, by using the Data field in the Treenode, but it would be nice to be able to do this since I don't have to worry about freeing additional blocks of memory when nodes are deleted.

What can I do to prevent this data loss from occurring?

unit Test04Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ComCtrls, Vcl.StdCtrls;

type
  TMyTreeNode = class(TTreeNode)
    private
      fMyProperty : string;
    public
    property MyProperty : string read fMyProperty write fMyProperty;
  end;
  TForm1 = class(TForm)
    TreeView1: TTreeView;
    StatusBar1: TStatusBar;
    procedure FormCreate(Sender: TObject);
    procedure TreeView1CreateNodeClass(Sender: TCustomTreeView; var NodeClass: TTreeNodeClass);
    procedure TreeView1Change(Sender: TObject; Node: TTreeNode);
  private
    fTreeView1_Selected: TMyTreeNode;
    property TreeView1_Selected : TMyTreeNode read fTreeView1_Selected;
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
var
  tn : TTreeNode;
  cnt : integer;
begin
  //fill some items
  TreeView1.Items.Clear;

  for cnt := 0 to 9 do
  begin
    tn := TreeView1.Items.AddChild(nil, IntToStr(cnt));
    //add default MyProperty values
    TMyTreeNode(tn).MyProperty := 'this is node ' + IntToStr(cnt);
  end;
end;

procedure TForm1.TreeView1Change(Sender: TObject; Node: TTreeNode);
begin
  fTreeView1_Selected := TMyTreeNode(Node);
  StatusBar1.Panels[0].Text := TreeView1_Selected.MyProperty;
end;

procedure TForm1.TreeView1CreateNodeClass(Sender: TCustomTreeView; var NodeClass: TTreeNodeClass);
begin
  NodeClass := TMyTreeNode;
end;

end.

Using Delphi XE6 on Windows 2012 R2, Debug Win32 build, but this problem occurs also with Win64 build.

like image 379
KeyszerS Avatar asked Apr 22 '15 06:04

KeyszerS


1 Answers

This is happening because of VCL window re-creation. The VCL design means that in some situations, the windows that implement forms and controls need to be re-created. Typically this happens when a state change is made that cannot be applied to a window that already exists. So the windows are re-created. When this happens the controls attempt to save away their state, and then restore it.

For tree views, the nodes themselves are destroyed and then re-created. Which means that you need to take extreme care when taking a reference to a node. As soon as window re-creation happens, that node reference is invalid.

You can force this to happen yourself by calling the protected RecreateWnd method of the tree view control. You'll need the protected member access hack to do that. But as soon as you call RecreateWnd you can observe that your custom node's properties are cleared. Indeed add overridden constructor and a destructor to your node class and observe them being called during re-creation.

You are pretty much stumped here. The tree view re-creation code that stores and then restores the node state, does not have any hooks, so far as I can see. Your custom nodes will be destroyed, and will be re-created. I see no straightforward way for you to persist the state of your nodes during that process. The Data property is persisted so you could use that to ensure correct re-creation of your custom node. But once you go down that path, what benefit does the custom node offer?

My recommendation is that you use Data for your custom data and avoid using custom node types.

So far as I can tell, this issue renders the custom node type feature close to useless.

like image 147
David Heffernan Avatar answered Nov 03 '22 01:11

David Heffernan