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:
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()
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
.