如何防止TTreeView的自定义树节点数据丢失?

时间:2015-04-22 06:46:52

标签: delphi

我使用Zarko Gajic's Store More (Custom) Data Into The Tree Node Of A Tree View为每个节点项添加其他字符串,但我发现如果我的应用程序已经空闲了很长时间,那么我存储在自定义树节点中的值消失。

这就是我昨天离开时的自定义treenode

Before going home

这就是今天早上的样子(注意fMyProperty值现在为空)

Back the next day

我已确认我的电脑从未休眠或睡眠,但系统会在锁定1分钟后关闭显示器以节省电量。然而,计算机需要闲置一段时间才能发生此问题。它在一夜之间闲置时最引人注目,但如果只闲置30分钟则不太可能发生。

我能想到的唯一可能导致这种情况的是操作系统将应用程序内存交换到磁盘,当您重新激活计算机时,它会被换回内存。正如您所看到的,FItemId正在发生变化,因此它似乎正在重建"树视图,因此失去与自定义树节点的关联。

我用一个非常简单的应用程序重现了这个问题,其代码如下。

我知道我可以使用另一种方法来存储其他数据,使用Treenode中的数据字段,但能够做到这一点会很好,因为我不必担心释放额外的块删除节点时的内存。

如何防止数据丢失?

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.

在Windows 2012 R2上使用Delphi XE6,调试Win32版本,但Win64版本也会出现此问题。

1 个答案:

答案 0 :(得分:9)

这是因为VCL窗口重新创建而发生的。 VCL设计意味着在某些情况下,需要重新创建实现表单和控件的窗口。通常,当进行状态更改而无法应用于已存在的窗口时,会发生这种情况。因此窗口被重新创建。发生这种情况时,控件会尝试保存其状态,然后将其还原。

对于树视图,节点本身将被销毁,然后重新创建。这意味着在参考节点时需要格外小心。一旦窗口重新创建,该节点引用就无效。

您可以通过调用树视图控件的受保护RecreateWnd方法来强制执行此操作。你需要受保护的成员访问黑客来做到这一点。但是只要您调用RecreateWnd,就可以发现自定义节点的属性已被清除。确实将重写的构造函数和析构函数添加到节点类中,并观察它们在重新创建期间被调用。

你在这里很难过。存储然后恢复节点状态的树视图重新创建代码,没有任何钩子,据我所见。您的自定义节点将被销毁,并将重新创建。我认为在此过程中您没有直接的方法来保持节点的状态。 Data属性是持久保存的,因此您可以使用它来确保正确地重新创建自定义节点。但是,一旦你走上这条道路,自定义节点会带来哪些好处?

我的建议是您使用Data作为自定义数据,并避免使用自定义节点类型。

据我所知,这个问题使自定义节点类型功能接近无用。