在树视图节点内存储接口指针

时间:2014-09-01 18:14:51

标签: delphi pointers interface

我试图在TTreeNode.Data属性下的树视图中存储接口指针。虽然我能够存储一个接口指针(Node.Data := Pointer(MyInterface);),但它似乎没有相反的方式(MyInterface := ISomeInterface(Node.Data);)。它始终出现nil

我还试图使用手动引用计数,正如我在another question中看到的那样。但是,它仍然会出现nil,现在会造成内存泄漏。

//Clears tree view and adds drive letters
procedure TfrmMain.cmdRefreshBrowseClick(Sender: TObject);
var
  Arr, O: ISuperObject;
  X: Integer;
  N, C: TTreeNode;
begin
  //First clear all items and release their interface refs
  for N in tvBrowse.Items do begin
    O:= ISuperObject(N.Data);
    O._Release;
  end;
  tvBrowse.Items.Clear;
  Arr:= ListDirectory(''); //Returns ISuperObject array listing drives
  for X := 0 to Arr.AsArray.Length-1 do begin
    O:= Arr.AsArray.O[X];
    N:= tvBrowse.Items.Add(nil, O.S['drive']+':\ ['+O.S['type']+']'); //Add root node
    N.Data:= Pointer(O); // Assign interface pointer to node data
    O._AddRef; //Manually increment interface reference count
    C:= tvBrowse.Items.AddChild(N, ''); //Add a fake child node
  end;
end;

procedure TfrmMain.tvBrowseExpanding(Sender: TObject; Node: TTreeNode;
  var AllowExpansion: Boolean);
var
  N, C: TTreeNode;
  P, A, O: ISuperObject;
  X: Integer;
begin
  //Check first node if it's a fake node
  N:= Node.getFirstChild;
  if N.Text = '' then begin //if first node is a fake node...
    P:= ISuperObject(Node.Data); // <-- P always comes out nil here???
    N.Delete; //Delete first "fake" node
    //Get child files/folders
    if Node.Parent = nil then //If root (drive) node...
      A:= ListDirectory(P.S['drive']+':\') //Returns ISuperObject array listing files/folders
    else
      A:= ListDirectory(P.S['name']); //Returns ISuperObject array listing files/folders
    for X := 0 to A.AsArray.Length-1 do begin
      O:= A.AsArray.O[X];
      C:= tvBrowse.Items.AddChild(N, O.S['name']); //Add child node
      C.Data:= Pointer(O); //Assign interface pointer to node data
      O._AddRef; //Manually increment reference count
    end;
  end;
end;

这样做的恰当方法是什么?

1 个答案:

答案 0 :(得分:7)

基本上你正确地这样做了。您的演员表是合理的,并且您理解需要执行手动引用计数,因为您在Pointer类型的字段中保留了引用,该字段不执行引用计数。

P := ISuperObject(Node.Data);

如果为P分配了值nil,则表示Node.Data等于nil。没有什么可说的了。据推测Data nil Data有一些相当普通的原因,但这与你的投射方式无关。

看看你的代码,我会批评它将所有不同的问题混合在一起。如果您可以在各个不同方面之间保持一定程度的隔离,您会发现这项任务非常简单。

使生活更简单的一种方法是避免使用无类型指针type TMyTreeNode = class(TTreeNode) private FIntf: IInterface; property Intf: IInterface read FIntf write FIntf; end; 。而是使用可以执行正确引用计数的自定义节点类型:

OnCreateNodeClass

您需要处理树视图的procedure TForm1.TreeView1CreateNodeClass(Sender: TCustomTreeView; var NodeClass: TTreeNodeClass); begin NodeClass := TMyTreeNode; end; 事件以获取控件以创建节点类。

TMyTreeNode

现在每当树视图控件创建一个节点实例时,它就会创建一个IInterface类型的节点实例。这恰好有一个字段来包含你的界面。我在这里输入TTreeNode,但您可以使用更符合您需求的更具体的界面。当然,您可以将任何您喜欢的功能添加到自定义节点类型中。

对此的轻度绑定是您需要将TMyTreeNode(由基础树视图控件返回)的节点引用转换为{{1}},以便获得对interface属性的访问权限。但是,在我看来,这个绑定非常值得,因为您可以正确地依赖编译器来管理生命周期,因此忘记了代码的所有方面。这将使您专注于您的程序而不是繁琐的样板。继续沿着您当前所处的路径看起来像内存泄漏和访问冲突的配方。让编译器管理事物,你可以确保避免任何这样的陷阱。