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:
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;
use an interceptor class to override the virtual
CreateWnd()
andDestroyWnd()
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
.