I have a component of type TVirtualStringTree
(let us call it VST
). It has nodes in the form of a list, i.e. all the nodes are at the same level. I want to change a focus after deleting a node (with the DeleteNode
method) and I used OnFreeNode
event:
procedure TMyForm.VSTFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
var
NewFocus: PVirtualNode;
begin
NewFocus := VST.GetNext(Node);
if not Assigned(newFocus) then
NewFocus := VST.GetPrevious(Node);
if Assigned(NewFocus) then
begin
VST.FocusedNode := NewFocus;
VST.Selected[NewFocus] := True
end;
end;
I want also the change to cause some reactions, e.g. set the Enabled
property of a button:
procedure TMyForm.VSTFocusChanged(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex);
begin
btn1.Enabled := Assigned(Node);
end;
But there are some problems with the solution. For example, when I close the form with the Cancel button (the form is open with ShowModal
method), the nodes are freed, VSTFocusChanged
is triggered, and the later throws an exception because of nil button. Of course, I could check if the button is assigned, but is there more elegant solution of changing the focus after deleting a node without this undesirable effect (and without other undesirable effects)?
Yes, there is. Remove your code from those events and include the toAlwaysSelectNode option to the tree view TreeOptions.SelectionOptions option set (e.g. enable it in the IDE). The comment by this option says:
If this flag is set to true, the tree view tries to always have a node selected.
That includes node deletion as well.
The problem you were facing is that you were focusing node manually from the OnFreeNode event which in turn triggered OnFocusChanged event. And since nodes are freed also when the control is destroyed and that button was destroyed before, you were trying to access a destroyed control. To avoid this in the future you can use RTL actions because there's a lot of events which VT fires even when it has csDestroying state signalled (including event like OnStructureChange) and actions are one safe workaround.
Something like this should work safely (I'm not a fan of action OnUpdate event):
procedure TMyForm.VSTStructureChange(Sender: TBaseVirtualTree; Node: PVirtualNode;
Reason: TChangeReason);
begin
{ ActionDeleteNode is assigned to the button's Action property; SelectedCount
is a bit paranoic here because if you use the toAlwaysSelectNode option, at
least one node should be always selected, so RootNodeCount > 0 could do the
same here }
ActionDeleteNode.Enabled := Sender.SelectedCount > 0;
end;
Without RTL actions you can safely update that button state e.g. right after you perform an action, for example after you delete node:
VST.DeleteNode(VST.FocusedNode);
ButtonDeleteNode.Enabled := VST.SelectedCount > 0;
But with that you can get out of sync and write more code. Hence using RTL actions is what would I prefer.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With