سؤال

I have a frame with an TLMDDockPanel component as the parent, on the frame there is a TTreeView component:

unit devices;
...
  Tmaster = class(TObject)
    ...
    devTreeNode : ttreenode;
    ...
  end;
...
end.

unit deviceTree;
...
  TfrmDevTree = class(TFrame)
    JvTreeView1: TTreeView;
    ...
  end;

procedure TfrmDevTree.GetSlavesOnSelectedClick(Sender: TObject);
var
  Node: TTreeNode;
begin
  ...
  Node := self.JvTreeView1.Selected;
  ...
end;
...
end.

unit mainForm;
...
TfrmMain = class(TForm)
...
   LMDDockSite1: TLMDDockSite;
   LMDDockPanel_DevTree: TLMDDockPanel;
...
var
  frmDevTree  : TfrmDevTree;
...
procedure TfrmMain.FormCreate(Sender: TObject);
begin
  ...
  frmDevTree := TfrmDevTree.Create(self);
  frmDevTree.Parent := LMDDockPanel_DevTree;
  ...
end;
...
end.

At application start, i fill the 'Data' fields for all the nodes of JvTreeView1:

master := Tmaster.create;
Node.Data := master;
master.devtreenode := node;  //I also save the treenode that is representing the master in JvTreeView1 into a master field.

The LMDDockPanel_DevTree dock panel is docked at the left of the docksite by default and there is no any problem while the dock panel sits there, but after undocking it, the obj. references for the treenodes are changing so the references stored in the masters (master.devtreenode) are no longer valid. Can someone please explain why are the treenode references changing? How to avoid this? Should i refresh all the references stored in the masters every time i dock/undock the dock panel?

Thank You.

هل كانت مفيدة؟

المحلول

The reason it happens is because docking/undocking destroys and recreates the TreeView's HWND, which in turn destroys and recreates its node objects. A TreeView is designed to cache and restore the TTreeNode.Data values automatically during this recreation process, but it knows nothing about TMaster.DevTreeNode. As such, you need to detect when the nodes have been recreated so you can manually update their DevTreeNode values with the new TTreeNode pointers.

A TreeView has OnAddition and OnDeletion events that one would think would be ideal for this task. However, they are inconveniently NOT triggered during HWND recreation!

So you have two choices:

  1. subclass the TreeView's WindowProc property to catch the recreation messages.

    private
      { Private declarations }
      DefTreeViewWndProc: TWndMethod;
      procedure TreeViewWndProc(var Message: TMessage);
    

    procedure TfrmDevTree.FormCreate(Sender: TObject);
    begin
      DefTreeViewWndProc := JvTreeView1.WindowProc;
      JvTreeView1.WindowProc := TreeViewWndProc;
    end;
    
    procedure UpdateMasterDevNode(Node: TTreeNode; Destroying: Boolean);
    var
      Child: TTreeNode;
    begin
      if Node.Data <> nil then
      begin
        if Destroying then
          TMaster(Node.Data).DevTreeNode := nil
        else
          TMaster(Node.Data).DevTreeNode := Node;
      end;
      Child := Node.getFirstChild;
      while Child <> nil do
      begin
        UpdateMasterDevNode(Child, Destroying);
        Child := Child.getNextSibling;
      end;
    end;
    
    procedure UpdateMasterDevNodes(Nodes: TTreeNodes; Destroying: Boolean);
    var
      Node: TTreeNode;
    begin
      Node := Nodes.GetFirstNode;
      while Node <> nil do
      begin
        UpdateMasterDevNode(Node, Destroying);
        Node := Node.getNextSibling;
      end;
    end;
    
    procedure TfrmDevTree.TreeViewWndProc(var Message: TMessage);
    const
      WM_UPDATEMASTERDEVNODES = WM_APP + 1;
    begin
      if Message.Msg = CM_RECREATEWND then
        UpdateMasterDevNodes(JvTreeView1.Items, True);
    
      DefTreeViewWndProc(Message);
    
      if Message.Msg = WM_CREATE then
      begin
        // the cached nodes have not been recreated yet, so delay the DevTreeNode updates
        PostMessage(TreeView1.Handle, WM_UPDATEMASTERDEVNODES, 0, 0)
      end
      else if Message.Msg = WM_UPDATEMASTERDEVNODES then
        UpdateMasterDevNodes(JvTreeView1.Items, False);
    end;
    
  2. use an interceptor class to override the virtual CreateWnd() and DestroyWnd() methods.

    type
      TJvTreeView = class(JVCL.ListsAndTrees.Trees.TJvTreeView)
      protected
        procedure CreateWnd; override;
        procedure DestroyWnd; override;
     end;
    
     TfrmDevTree = class(TForm)
       JvTreeView1: TJvTreeView;
       ...
     end;
    

     procedure UpdateMasterDevNode(Node: TTreeNode; Destroying: Boolean);
     var
       Child: TTreeNode;
     begin
       if Node.Data <> nil then
       begin
         if Destroying then
           TMaster(Node.Data).DevTreeNode := nil
         else
           TMaster(Node.Data).DevTreeNode := Node;
       end;
       Child := Node.getFirstChild;
       while Child <> nil do
       begin
         UpdateMasterDevNode(Child, Destroying);
         Child := Child.getNextSibling;
       end;
     end;
    
     procedure UpdateMasterDevNodes(Nodes: TTreeNodes; Destroying: Boolean);
     var
       Node: TTreeNode;
     begin
       Node := Nodes.GetFirstNode;
       while Node <> nil do
       begin
         UpdateMasterDevNode(Node, Destroying);
         Node := Node.getNextSibling;
       end;
     end;
    
     procedure TJvTreeView.CreateWnd;
     begin
       inherited;
       UpdateMasterDevNodes(Items, False);
     end;
    
     procedure TTreeView.DestroyWnd;
     begin
       if csRecreating in ControlState then
         UpdateMasterDevNodes(Items, True);
       inherited;
     end;
    

Either way, be sure that any code which uses TMaster.DevTreeNode checks for nil first before using the TTreeNode.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top