Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Delphi: Changing focus after deleting a node from a virtual string tree

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)?

like image 274
Chris S Avatar asked Jun 18 '17 18:06

Chris S


1 Answers

Is there a built-in way to always have a node selected?

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.


How to safely update foreign control's enabled state from VT event?

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.

like image 197
Victoria Avatar answered Nov 16 '22 00:11

Victoria