3

I use this code to fill VirtualStringTree and allow renaming items:

//---------------------------------------------------------------------------
// Structure for the tree
//---------------------------------------------------------------------------
struct TVSTdata
{
UnicodeString Name;
};
//---------------------------------------------------------------------------
// Initialization of the tree
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
VirtualStringTree1->NodeDataSize = sizeof(TVSTdata);
// Fill all nodes with initial data
InitializeTree();
}
//---------------------------------------------------------------------------
// Fill all nodes with data and assign FocusedNode
//---------------------------------------------------------------------------
void TForm1::InitializeTree()
{
TVirtualNode* pNode;
TVirtualNode* pActiveNode;
TVSTdata*     pData;

VirtualStringTree1->BeginUpdate();
VirtualStringTree1->Clear();

pNode = VirtualStringTree1->AddChild(NULL); pData = static_cast<TVSTdata*>(VirtualStringTree1->GetNodeData(pNode)); pData->Name = "This is name 1";
pNode = VirtualStringTree1->AddChild(NULL); pData = static_cast<TVSTdata*>(VirtualStringTree1->GetNodeData(pNode)); pData->Name = "This is name 2";
pNode = VirtualStringTree1->AddChild(NULL); pData = static_cast<TVSTdata*>(VirtualStringTree1->GetNodeData(pNode)); pData->Name = "This is name 3"; pActiveNode = pNode;
pNode = VirtualStringTree1->AddChild(NULL); pData = static_cast<TVSTdata*>(VirtualStringTree1->GetNodeData(pNode)); pData->Name = "This is name 4";
pNode = VirtualStringTree1->AddChild(NULL); pData = static_cast<TVSTdata*>(VirtualStringTree1->GetNodeData(pNode)); pData->Name = "This is name 5";

VirtualStringTree1->Selected[pActiveNode] = true;
VirtualStringTree1->FocusedNode = pActiveNode; // PROBLEM -> if assigned from within OnNewText will still remain NULL and won't be set to pActiveNode!
VirtualStringTree1->EndUpdate();
}
//---------------------------------------------------------------------------
// Just display the text
//---------------------------------------------------------------------------
void __fastcall TForm1::VirtualStringTree1GetText(TBaseVirtualTree *Sender, PVirtualNode Node, TColumnIndex Column, TVSTTextType TextType, UnicodeString &CellText)
{
TVSTdata* pData = static_cast<TVSTdata*>(Sender->GetNodeData(Node));
CellText = pData->Name;
}
//---------------------------------------------------------------------------
// Allow editing
//---------------------------------------------------------------------------
void __fastcall TForm1::VirtualStringTree1Editing(TBaseVirtualTree *Sender, PVirtualNode Node, TColumnIndex Column, bool &Allowed)
{
Allowed = true;
}
//---------------------------------------------------------------------------
// Now this is where ideally I would reload the tree with new data - after rename
//---------------------------------------------------------------------------
void __fastcall TForm1::VirtualStringTree1NewText(TBaseVirtualTree *Sender, PVirtualNode Node, TColumnIndex Column, UnicodeString NewText)
{
NewText = "not important for this example as tree is reloaded anyway";
InitializeTree();  // ERROR is here - after assigning FocusedNode it is still NULL
//Timer1->Enabled = true; // If delayed call FocusedNode is correctly assigned and not NULL
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
//InitializeTree();
//Timer1->Enabled = false;
}
//---------------------------------------------------------------------------

The problem - when InitializeTree() is called initially and VirtualStringTree1->FocusedNode is assigned it is correctly assigned (not NULL).

However, if this InitializeTree() function is called within OnNewText to actually reload the tree from database after rename event - after assigning FocusedNode it is remains NULL. So obviously tree cannot be reloaded and assigned FocusedNode from within the OnNewText event.

I implemented delayed call to reload new tree and reassign FocusedNode - by implementing a quick and dirty timer (could have used PostMessage for delayed function call but this is just a dumb example) - after assigning within a timer it no longer NULL and works as expected.

Can anyone point me what is the optimal way to implement reloading of the tree - like a particular event to use in which it is safe to set new FocusedNode and it won't be reassigned back to NULL? Is delayed function call the only way to achieve this or is there a better event to trap (for example if one occurs after OnNewText if this one doesn't allow setting focused node). Of course this works but I am interested if there is a better way to do this.

Coder12345
  • 3,431
  • 3
  • 33
  • 73
  • 2
    If you're talking about database, do you really need to reload the whole tree when you edit just a single node ? Don't you rather do an update of a particular row when you edit the node and reload just that single row (reload just that node) from a database ? – TLama Oct 12 '12 at 23:47
  • I need to re-sort the tree as edited node might change alphabetical order. It is easier to reload the tree from sorted database rather than sorting the tree separately. As loading function also sorts and creates multi-level tree it is easier to do that way. Besides, sorting done in VT might behave differently than database one. Also, if I reload only that one node I still need to make certain node Focused right? – Coder12345 Oct 13 '12 at 10:58
  • 1
    It might be easier to reload sorted data from a database, instead of sorting the tree, but it's an overkill. If you need to sort the tree, simply call `SortTree` after node edit and handle the `OnCompareNodes` event. To your problem, first you have to remember which node has been selected before you clear the tree. But you can't do this by storing `PVirtualNode` since when you clear and refill the tree, the node pointer you stored is no more valid. You have to store the index of the selected node. Then after you refill the tree, find the new node by that stored index and select the found node. – TLama Oct 13 '12 at 11:36
  • Effectively having sort in database and tree is like having 2 functions to accomplish one thing. But it is matter of preference. Nobody said that I am storing `PVirtualNode` - I am using index from database to find relevant node to focus on redraw. Again, the problem is not finding the relevant node and selecting it (the code above does all that) but the fact if you use `VirtualStringTree1->FocusedNode = pActiveNode;` in code above it will remain NULL and won't change to `pActiveNode` if called directly from `OnNewText` event. Please look at the last paragraph of the question above. – Coder12345 Oct 13 '12 at 11:48
  • 1
    I should also note that it is a small tree so loading from database and sorting again is quickest way and doesn't make a huge impact neither on database neither on the tree. And `BeginUpdate` and `EndUpdate` makes the clearing and redrawing of the tree transition invisible. – Coder12345 Oct 13 '12 at 11:53

1 Answers1

7

You can't change the FocusedNode when you're in the tsEditing tree state and until you leave the OnNewText event, you're in that state. The OnNewText itself is more for edit validation; it is the event, where you can modify the edited value. Instead you should use the OnEdited event which is fired after the edit is actually done. So move your database update and tree reloading stuff there like shown in the following C++ Builder pseudocode:

void __fastcall TForm1::VirtualStringTree1Edited(TBaseVirtualTree *Sender, 
  PVirtualNode Node, TColumnIndex Column)
{
  // update your database here; with VirtualStringTree1.Text[Node, Column] you
  // can access the current node text after edit; when you update your DB, call
  InitializeTree();
} 
TLama
  • 75,147
  • 17
  • 214
  • 392