【问题标题】:Storing interface pointer inside tree view nodes在树视图节点内存储接口指针
【发布时间】:2014-10-25 23:51:50
【问题描述】:

我正在尝试将接口指针存储在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;

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

【问题讨论】:

  • 你可以研究TInterfaceList的代码。
  • 或者我可以将每个接口及其内容转换为记录并存储记录指针而不是接口指针
  • 如果你放一个InterfaceList,你的TTreeNode.Data指向InterfaceItem?树视图中的每一项都是 interfacelist 中的一项。

标签: delphi pointers interface


【解决方案1】:

基本上你做对了。您的强制转换是合理的,并且您了解执行手动引用计数的必要性,因为您将引用保存在不执行引用计数的 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,以便访问接口属性。但是,在我看来,这种绑定非常值得,因为您可以依靠编译器来正确管理生命周期,因此忘记代码的这方面的所有内容。这将使您能够专注于您的程序,而不是繁琐的样板文件。继续沿着你当前的路径前进看起来像是内存泄漏和访问冲突的秘诀。让编译器来管理事物,您可以确保避免任何此类陷阱。

【讨论】:

  • 这很聪明。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-15
  • 1970-01-01
  • 2015-07-18
  • 2022-01-14
  • 2021-07-02
  • 1970-01-01
相关资源
最近更新 更多