对象的 VirtualTreeView 内存泄漏
VirtualTreeView Memory Leak with Objects
我正在使用 Cosmin Prund in this post 提供的代码,因为它符合我的需要,但是我经常遇到内存泄漏,我不知道如何释放 TNode
包含的对象的节点TObjectList
反过来这最后一个也可以包含 TNode
也包含 TObjectList
等等......不过我是某种递归,
据我所知,要根据此 link 释放 VirtualTreeView
中的节点,需要验证节点并在 OnFreeNode
事件中完成此代码 return 无效的指针操作,当然还有内存泄漏报告:
procedure TfrmFichePermission.VSTFreeNode(Sender: TBaseVirtualTree;
Node: PVirtualNode);
var
AObject:TObject;
ANode: TNode;
begin
AObject := TObject(VST.GetNodeData(Node)^);
ANode := TNode(AObject);
ANode.Free;
end;
这是重现内存泄漏的完整示例
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, VirtualTrees, System.Generics.Collections,
Vcl.StdCtrls;
type
TNode = class;
TForm1 = class(TForm)
VST: TVirtualStringTree;
Button1: TButton;
procedure VSTGetNodeDataSize(Sender: TBaseVirtualTree;
var NodeDataSize: Integer);
procedure VSTGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
procedure Button1Click(Sender: TObject);
private
procedure AddNodestoTree(ParentNode: PVirtualNode; Node: TNode);
public
{ Public declarations }
end;
TNode = class
public
Name: string;
VTNode: PVirtualNode;
Sub: TObjectList<TNode>;
constructor Create(aName: string);
destructor Destroy; override;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
constructor TNode.Create(aName:string);
begin
Name := aName;
Sub := TObjectList<TNode>.Create;
end;
destructor TNode.Destroy;
begin
Sub.Free;
end;
procedure TForm1.AddNodestoTree(ParentNode: PVirtualNode; Node: TNode);
var SubNode: TNode;
ThisNode: PVirtualNode;
begin
ThisNode := VST.AddChild(ParentNode, Node); // This call adds a new TVirtualNode to the VT, and saves "Node" as the payload
Node.VTNode := ThisNode; // Save the PVirtualNode for future reference. This is only an example,
// the same TNode might be registered multiple times in the same VT,
// so it would be associated with multiple PVirtualNode's.
for SubNode in Node.Sub do
AddNodestoTree(ThisNode, SubNode);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
Root: TNode;
begin
ReportMemoryLeaksOnShutdown := True;
VST.Clear;
//
Root := TNode.Create('Test1');
Root.Sub.Add(TNode.Create('Test2'));
Root.Sub.Add(TNode.Create('Test3'));
Root.Sub[1].Sub.Add(TNode.Create('Test4'));
Root.Sub[1].Sub.Add(TNode.Create('Test5'));
AddNodesToTree(nil, Root);
//
Root := TNode.Create('Test1');
Root.Sub.Add(TNode.Create('Test2'));
Root.Sub.Add(TNode.Create('Test3'));
Root.Sub[1].Sub.Add(TNode.Create('Test4'));
Root.Sub[1].Sub.Add(TNode.Create('Test5'));
AddNodesToTree(nil, Root);
//
end;
procedure TForm1.VSTGetNodeDataSize(Sender: TBaseVirtualTree;
var NodeDataSize: Integer);
begin
NodeDataSize := SizeOf(Pointer);
end;
procedure TForm1.VSTGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
var
AObject:TObject;
ANode: TNode;
begin
AObject := TObject(VST.GetNodeData(Node)^);
ANode := TNode(AObject);
CellText := ANode.Name;
end;
end.
内存泄漏报告:
Cosmin 的代码并不打算让树视图节点拥有 TNode
对象。我认为在他的 post 中你应该抓住 Root
对象并在树被摧毁后将其摧毁。
在 Cosmin 的代码中,TNode
个对象属于包含它们的对象列表。一直到创建它的任何人所拥有的根节点。你也可以这样做。您必须记住根对象,并在树视图节点被销毁时停止销毁 TNode
对象。
如果您想让树视图拥有 TNode
个对象,那么您可以这样做。但是你需要清楚所有权。您不能像现在那样使用树视图 和 对象列表来拥有对象。如果树视图将成为所有者,则需要在对象列表中将 OwnsObjects
设置为 False
。或者甚至更好地切换到 TList<TNode>
。
因此,总而言之,您当前的代码为每个 TNode
对象提供了两个所有者。树视图节点和所属对象列表。对象需要只有一个所有者。您需要在这两个选项之间做出选择。
我正在使用 Cosmin Prund in this post 提供的代码,因为它符合我的需要,但是我经常遇到内存泄漏,我不知道如何释放 TNode
包含的对象的节点TObjectList
反过来这最后一个也可以包含 TNode
也包含 TObjectList
等等......不过我是某种递归,
据我所知,要根据此 link 释放 VirtualTreeView
中的节点,需要验证节点并在 OnFreeNode
事件中完成此代码 return 无效的指针操作,当然还有内存泄漏报告:
procedure TfrmFichePermission.VSTFreeNode(Sender: TBaseVirtualTree;
Node: PVirtualNode);
var
AObject:TObject;
ANode: TNode;
begin
AObject := TObject(VST.GetNodeData(Node)^);
ANode := TNode(AObject);
ANode.Free;
end;
这是重现内存泄漏的完整示例
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, VirtualTrees, System.Generics.Collections,
Vcl.StdCtrls;
type
TNode = class;
TForm1 = class(TForm)
VST: TVirtualStringTree;
Button1: TButton;
procedure VSTGetNodeDataSize(Sender: TBaseVirtualTree;
var NodeDataSize: Integer);
procedure VSTGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
procedure Button1Click(Sender: TObject);
private
procedure AddNodestoTree(ParentNode: PVirtualNode; Node: TNode);
public
{ Public declarations }
end;
TNode = class
public
Name: string;
VTNode: PVirtualNode;
Sub: TObjectList<TNode>;
constructor Create(aName: string);
destructor Destroy; override;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
constructor TNode.Create(aName:string);
begin
Name := aName;
Sub := TObjectList<TNode>.Create;
end;
destructor TNode.Destroy;
begin
Sub.Free;
end;
procedure TForm1.AddNodestoTree(ParentNode: PVirtualNode; Node: TNode);
var SubNode: TNode;
ThisNode: PVirtualNode;
begin
ThisNode := VST.AddChild(ParentNode, Node); // This call adds a new TVirtualNode to the VT, and saves "Node" as the payload
Node.VTNode := ThisNode; // Save the PVirtualNode for future reference. This is only an example,
// the same TNode might be registered multiple times in the same VT,
// so it would be associated with multiple PVirtualNode's.
for SubNode in Node.Sub do
AddNodestoTree(ThisNode, SubNode);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
Root: TNode;
begin
ReportMemoryLeaksOnShutdown := True;
VST.Clear;
//
Root := TNode.Create('Test1');
Root.Sub.Add(TNode.Create('Test2'));
Root.Sub.Add(TNode.Create('Test3'));
Root.Sub[1].Sub.Add(TNode.Create('Test4'));
Root.Sub[1].Sub.Add(TNode.Create('Test5'));
AddNodesToTree(nil, Root);
//
Root := TNode.Create('Test1');
Root.Sub.Add(TNode.Create('Test2'));
Root.Sub.Add(TNode.Create('Test3'));
Root.Sub[1].Sub.Add(TNode.Create('Test4'));
Root.Sub[1].Sub.Add(TNode.Create('Test5'));
AddNodesToTree(nil, Root);
//
end;
procedure TForm1.VSTGetNodeDataSize(Sender: TBaseVirtualTree;
var NodeDataSize: Integer);
begin
NodeDataSize := SizeOf(Pointer);
end;
procedure TForm1.VSTGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
var
AObject:TObject;
ANode: TNode;
begin
AObject := TObject(VST.GetNodeData(Node)^);
ANode := TNode(AObject);
CellText := ANode.Name;
end;
end.
内存泄漏报告:
Cosmin 的代码并不打算让树视图节点拥有 TNode
对象。我认为在他的 post 中你应该抓住 Root
对象并在树被摧毁后将其摧毁。
在 Cosmin 的代码中,TNode
个对象属于包含它们的对象列表。一直到创建它的任何人所拥有的根节点。你也可以这样做。您必须记住根对象,并在树视图节点被销毁时停止销毁 TNode
对象。
如果您想让树视图拥有 TNode
个对象,那么您可以这样做。但是你需要清楚所有权。您不能像现在那样使用树视图 和 对象列表来拥有对象。如果树视图将成为所有者,则需要在对象列表中将 OwnsObjects
设置为 False
。或者甚至更好地切换到 TList<TNode>
。
因此,总而言之,您当前的代码为每个 TNode
对象提供了两个所有者。树视图节点和所属对象列表。对象需要只有一个所有者。您需要在这两个选项之间做出选择。