Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
2.1k views
in Technique[技术] by (71.8m points)

delphi - Storing interface pointer inside tree view nodes

I'm attempting to store interface pointers in a tree view under TTreeNode.Data properties. While I am able to store an interface pointer (Node.Data := Pointer(MyInterface);) it does not seem to work the other way around (MyInterface := ISomeInterface(Node.Data);). It always comes out nil.

I've also attempted to use manual reference counting, as I've seen required in another question. However, it is still coming out nil and now giving memory leaks.

//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;

What's the appropriate way to do this?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Essentially you are doing this correctly. Your casts are reasonable, and you understand the need to perform manual reference counting since you are holding the reference in a field of type Pointer which does not perform reference counting.

P := ISuperObject(Node.Data);

If P is assigned the value nil that means that Node.Data is equal to nil. There's nothing more to say. Presumably there is some rather mundane reason for Data being nil, but it's nothing to do with the way you are casting.

Looking at your code, I would criticise it for mixing all the different concerns up together. You will find this task much easier if you can maintain a degree of isolation between the various different aspects.

One way to make life much simpler is to avoid using the untyped pointer Data. Instead use a custom node type that can perform proper reference counting:

type
  TMyTreeNode = class(TTreeNode)
  private
    FIntf: IInterface;
  property
    Intf: IInterface read FIntf write FIntf;
  end;

You'll need to handle the OnCreateNodeClass event of the tree view to get the control to create your node class.

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

Now whenever the tree view control creates a node instance it creates one of type TMyTreeNode. Which happens to have a field to contain your interface. I've typed it as IInterface here, but you'd use the more specific interface that fits your needs. And of course you can add whatever capability you please to your custom node type.

The mild bind to this is that you need to cast node references from TTreeNode (as returned by the underlying tree view control) to TMyTreeNode in order to gain access to the interface property. However, this bind is well worth it in my view because you can rely on the compiler to managed lifetime correctly, and so forget all about that aspect of the code. This will allow you to concentrate on your program rather than tedious boilerplate. Continuing down the path you are currently on looks like a recipe for memory leaks and access violations. Get the compiler to manage things and you can be sure to avoid any such pitfalls.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...