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.
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();
}
Related
TComboBox in TStringGrid does not work when used from the keyboard. It does not update the Cells value.
I expected it to work from the keyboard when it works when I use the mouse. How should I change the code to make it work from the keyboard? Or is it a dead case?
Thanks Mika
void __fastcall TForm1::ChangeStringGridComboBox(TObject* Sender)
{
TComboBox* combobox = dynamic_cast<TComboBox*>(Sender);
if (combobox && combobox->ItemIndex > -1) {
StringGrid1->Cells[StringGrid1->Col][StringGrid1->Row] =
combobox->Items->Strings[combobox->ItemIndex];
}
}
//---------------------------------------------------------------------------
void __fastcall TForm1::StringGrid1CreateCustomEditor(
TObject* Sender, TColumn* const Column, TStyledControl*&Control)
{
TComboBox* combobox = new TComboBox(this);
if (Column == Column1) {
Control = combobox;
combobox->Items->Assign(Memo1->Lines);
combobox->ItemIndex = combobox->Items->IndexOf(
StringGrid1->Cells[StringGrid1->Col][StringGrid1->Row]);
if (combobox->ItemIndex > -1) {
StringGrid1->Cells[StringGrid1->Col][StringGrid1->Row] =
combobox->Items->Strings[combobox->ItemIndex];
}
combobox->OnChange = &ChangeStringGridComboBox;
}
}
From Firemonkey: How do I use a TComboBox in a TStringGrid to make it work from the keyboard?:
To use a TComboBox in a TStringGrid and make it work from the keyboard, you can follow these steps:
Place a TComboBox component on your form and set its Parent property to the TStringGrid.
Set the TComboBox's Visible property to False.
Handle the TStringGrid's OnSelectCell event. In the event handler, check if the current column of the selected cell is the column that the TComboBox is associated with. If it is, set the TComboBox's Left and Top properties to the coordinates of the selected cell, and set its Visible property to True.
Handle the TStringGrid's OnKeyPress event. In the event handler, check if the key pressed is the down arrow key. If it is, and the TComboBox is visible, set the focus to the TComboBox.
Handle the TComboBox's OnExit event. In the event handler, set the TComboBox's Visible property to False, and set the focus back to the TStringGrid.
In the TComboBox's OnSelect event, you can get the selected value and assign it to the selected cell of the TStringGrid.
You can also create a custom component that inherits from TStringGrid and add the TComboBox and the necessary events to it.
Since you are creating the TComboBox as a custom InplaceEditor, most of that should already be handled for you by the TStringGrid. All you are really missing is the Key Press handler, eg:
// Unlike VCL, FMX does not expose access to TStringGrid's
// active Editor, so keep track of it manually...
protected:
void __fastcall Notification(TComponent* AComponent, TOperation Operation) override;
private:
TComboBox *myComboBox;
...
void __fastcall TForm1::StringGrid1CreateCustomEditor(
TObject* Sender, TColumn* const Column, TStyledControl* &Control)
{
if (Column == Column1) {
myComboBox = new TComboBox(this);
myComboBox->Items->Assign(Memo1->Lines);
myComboBox->ItemIndex = myComboBox->Items->IndexOf(
StringGrid1->Cells[StringGrid1->Col][StringGrid1->Row]);
myComboBox->OnChange = &ChangeStringGridComboBox;
Control = myComboBox;
}
}
void __fastcall TForm1::ChangeStringGridComboBox(TObject* Sender)
{
if (myComboBox->ItemIndex > -1) {
StringGrid1->Cells[StringGrid1->Col][StringGrid1->Row] =
myComboBox->Items->Strings[myComboBox->ItemIndex];
}
}
void __fastcall TForm1::StringGrid1KeyPress(
TObject* Sender, Word &Key, WideChar &KeyChar, TShiftState Shift)
{
if ((Key == vkDown) && StringGrid1->EditorMode)
{
Key = 0;
myComboBox->SetFocus();
}
}
void __fastcall TForm1::Notification(TComponent* AComponent, TOperation Operation)
{
TForm::Notification(AComponent, Operation);
if ((Operation == TOperation::opRemove) && (AComponent == myComboBox)) {
myComboBox = nullptr;
}
}
With the following changes, TComboBox works fine:
ChangeStringGridComboBox
{
...
temp = myComboBox->Items->Strings[myComboBox->ItemIndex];
}
StringGridEditingDone
{
if (Column1 == StringGrid1->ColumnByIndex(ACol) && temp.Length()) {
StringGrid1->Cells[StringGrid1->Col][StringGrid1->Row] = temp;
temp = "";}
}
Components often have long lists of properties with usable default values:
class PACKAGE TMySpecialComboBox : public TCustomComboBox
{
public:
__fastcall TMySpecialComboBox(TComponent *Owner);
// ...
private:
// ...
bool fetch_all_;
bool rename_;
TColor background1_, background2_, background3_;
// ...
__published:
// ...
__property bool FetchAll = {read = fetch_all_, write = fetch_all_,
default = false};
__property bool Rename = {read = rename_, write = rename_,
default = false};
__property TColor Background1 = {read = background1_, write = background1_,
default = clWindow};
__property TColor Background2 = {read = background2_, write = background2_,
default = clWindow};
__property TColor Background3 = {read = background3_, write = background3_,
default = clWindow};
// ...
};
Storing all this information in the form file wastes space and reading it back takes time, which is not desirable, considering that in most cases, few of the defaults change.
To minimize the volume of data in the form file, you can specify a default value for each property (when writing to the form file, the form editor skips any properties whose values haven't been changed).
Note that doing so doesn't set the default value:
Note: Property values are not automatically initialized to the default value. That is, the default directive controls only when property values are saved to the form file, but not the initial value of the property on a newly created instance.
the constructor is responsible for doing that:
__fastcall TMySpecialComboBox::TMySpecialComboBox(TComponent* Owner)
: TCustomComboBox(Owner), // ...
{
FetchAll = false; // how to get the default value ?
Rename = false; // how to get the default value ?
Background1 = clWindow // how to get the default value ?
// ...
}
but writing initialization in this way is very error prone.
How can I get the default value of a __property?
It can be done via the TRttiContext structure.
#include <Rtti.hpp>
int get_property_default(const String &name)
{
TRttiContext ctx;
auto *p(ctx.GetType(__classid(TMySpecialComboBox))->GetProperty(name));
assert(dynamic_cast<TRttiInstanceProperty *>(p));
return static_cast<TRttiInstanceProperty *>(p)->Default;
}
__fastcall TMySpecialComboBox::TMySpecialComboBox(TComponent* Owner)
: TCustomComboBox(Owner), // ...
{
FetchAll = get_property_default("FetchAll");
// ...
}
References:
Enhanced RTTI in C++Builder 2010, Part I: Introduction by Remy Lebeau (BCBJ Volume 13, Number 8, August 2009);
__classid. You can use it to obtain the metaclass of a TObject-based class (a Delphi-style class);
Delphi RTTI and C++Builder. Delphi RTTI is different and separate from standard C++ RTTI.
I downloaded the VirtualTreeView component for Borland C ++ Builder 6.0 and I'm trying to learn how to use it. Unfortunately I can not find any code for this compiler that serves as an example and I am based on a Delphi code that I have given.
I have created a project with a single form that contains only one TVirtualStringTree. I am using this code to try to understand how it works.
In the CPP:
void __fastcall TForm1 :: FormCreate (TObject * Sender)
{
AnsiString cLiteral;
PTrecBase pRecData;
PVirtualNode Node;
VST-> BeginUpdate ();
VST-> Clear ();
VST-> NodeDataSize = sizeof (TRecBase);
for (int nItem = 0; nItem <10; nItem ++)
{
cLiteral = "Node" + IntToStr (nItem);
Node = VST-> AddChild (NULL);
pRecData = (PTrecBase) VST-> GetNodeData (Node);
pRecData-> Literal = cLiteral;
for (int nSub = 0; nSub <5; nSub ++)
{
Node = getNodeDondeInsert (cLiteral);
Node = VST-> AddChild (Node);
pRecData = (PTrecBase) VST-> GetNodeData (Node);
pRecData-> Literal = cLiteral + "Sub" + IntToStr (nSub);
}
}
VST-> EndUpdate ();
}
In the H:
class TForm1: public TForm
{
__published: // IDE-managed Components
TVirtualStringTree * VST;
void __fastcall FormCreate (TObject * Sender);
private: // User declarations
public: // User declarations
__fastcall TForm1 (TComponent * Owner);
};
struct TRecBase
{
AnsiString Literal;
};
typedef TRecBase * PTrecBase;
However I can not get the literal that I want to show. Only this is shown:
And I do not know what I'm doing wrong so that you do not see the literals that I'm defining. Does anyone have a code for C ++ Builder that can guide me? Thank you.
You are storing string data for each node, but you have no event handlers assigned to the TreeView, particularly OnGetText, to supply those strings to the TreeView when it is being rendered. This is a virtual control, you have to supply it with data when it asks you for it.
There is a C++Builder demo available on VirtualTreeView's GitHub repository
Thanks for your reply.
The code that you have indicated to me I have already tried it and I am not able to understand it because I can not find in which part of it it is necessary to indicate the literal of the node. The attached image is the result I get with that example code.
Form CBMininal
As you can see the result is practically the same as in the image that I went up yesterday. What I'm most sorry about is that even though I searched, I did not find any sample code for this component for Borland C ++.
What's more: If I run the example with the debugger, the same thing happens to me as with my code.
In my code when I execute the line pRecData->Literal = cLiteral; I can see in the debugger that it has the calculated values (Node 0, Node 1, etc.) and in the example, when this one is executed (Text = Data->Caption;) I can see that both Text and Data->Caption contain the value, for example, Level 0, Index 5 but this literal is not shown in the TVirtualStringTree.
I want to implement a collection or list using TOwnedCollection / TCollectionItem. I need a persistent list (to load and create from a FileStream) of classes with polymorphism.
Here is (part of) my code so far, but I didn't succeed to create the derived class TGenerator instead of its parent TPowerComponent and add it to the Collection.
//-------------------------------------------------------------------------------------
class TPCCollection : public TOwnedCollection
{
typedef TOwnedCollection inherited;
private:
TPowerComponent* __fastcall GetPowerComponent(int Index);
void __fastcall SetPowerComponent(int Index, TPowerComponent *Value);
public:
__fastcall TPCCollection(TPersistent *Owner);
HIDESBASE TPowerComponent* __fastcall Add(void);
HIDESBASE TPowerComponent* __fastcall Insert(int Index);
__property TPowerComponent* PCCollection[int Index] = {read=GetPowerComponent, write=SetPowerComponent};
};
//-------------------------------------------------------------------------------------
class TPowerComponent : public TCollectionItem
{
typedef TCollectionItem inherited;
public :
int X, Y, Rotation;
PowSymbType HisType;
__fastcall TPowerComponent(TCollection *Collection, PowSymbType AType );
void __fastcall Assign(TPersistent *Source);
virtual void __fastcall Paint(TCanvas * Canvas);
};
//-------------------------------------------------------------------------------------
class TGenerator : public TPowerComponent
{
typedef TPowerComponent inherited;
public :
double PG, Qgmin, Qgmax, Vsch;
__fastcall TGenerator(TCollection *Collection, PowSymbType AType );
void __fastcall Assign(TPersistent *Source);
virtual void __fastcall Paint(TCanvas * Canvas);
};
//-------------------------------------------------------------------------------------
// implementation
//-------------------------------------------------------------------------------------
// TPCCOllection
//-------------------------------------------------------------------------------------
__fastcall TPCCollection::TPCCollection(TPersistent *Owner)
: TOwnedCollection(Owner, __classid(TPowerComponent))
{
}
//-------------------------------------------------------------------------------------
TPowerComponent* __fastcall TPCCollection::Add()
{
return static_cast<TPowerComponent>(inherited::Add());
}
//-------------------------------------------------------------------------------------
TPowerComponent* __fastcall TPCCollection::Insert(int Index)
{
return static_cast<TPowerComponent>(inherited::Insert(Index));
}
//-------------------------------------------------------------------------------------
TPowerComponent* __fastcall TPCCollection::GetPowerComponent(int Index)
{
return static_cast<TPowerComponent>(inherited::GetItem(Index));
}
//-------------------------------------------------------------------------------------
void __fastcall TPCCollection::SetPowerComponent(int Index, TPowerComponent *Value)
{
inherited::SetItem(Index, Value);
}
//-------------------------------------------------------------------------------------
// TPowerComponent
//-------------------------------------------------------------------------------------
__fastcall TPowerComponent::TPowerComponent(TCollection *Collection, PowSymbType AType )
: TCollectionItem(Collection)
{
HisType=AType;
Rotation=0;
}
//-------------------------------------------------------------------------------------
void __fastcall TPowerComponent::Assign(TPersistent *Source)
{
TPowerComponent *Src = dynamic_cast<TPowerComponent>(Source);
if( Src )
{
// copy members from Src...
}
else inherited::Assign(Source);
}
//-------------------------------------------------------------------------------------
// se dessine
void __fastcall TPowerComponent::Paint(TCanvas * Canvas)
{
...
}
//-------------------------------------------------------------------------------------
// TGenerator
//-------------------------------------------------------------------------------------
__fastcall TGenerator::TGenerator(TCollection *Collection, PowSymbType AType )
:TPowerComponent( Collection, AType )
{
PG=0; Qgmin=0; Qgmax=0; Vsch=1.0; Con=-1;
}
//-------------------------------------------------------------------------------------
void __fastcall TGenerator::Assign(TPersistent *Source)
{
TGenerator *Src = dynamic_cast<TGenerator>(Source);
if( Src )
{
// copy members from Src...
}
else inherited::Assign(Source);
}
//-------------------------------------------------------------------------------------
// Usage
TPCCollection * NetWork = new TPCCollection(this);
// Usage to Access all the collection
for( int i=0; i< NetWork->Count; i++)
{
((TPowerComponent*)(NetWork->Items[i]))->Paint(Canvas);
}
To add a TGenerator and not a TPowerComponent, I use:
TGenerator * Gen=new TGenerator( NetWork, Generator);
The creation of the TCollectionItem child automatically add itself to the TCollection
The problem here is that we can't separate the process of item creation from adding it to the collection.
When I need another list that can have some of the items of the first collection list, for example, SelectedComponents can have one or some of the Items of the NetWork Collection, without recreating them.
This can be done with
std::list<TPowerComponent*> SelectedComponents;
but I can't write/read them using FileStream / persistent list. I need to put them in a TCollection but without recreating them.
How?
The RTL's native DFM streaming for TCollection objects only partially supports polymorphic TCollectionItem classes.
You can add polymorphic TCollectionItem objects to a TCollection in code (at runtime, as well as at design-time with the aid of a custom editor), as long as they all derive from a common base class that is passed to the TCollection constructor. And such a collection can even be saved as-is to a DFM.
However, when loading back a DFM, native streaming will force all collection items read from the DFM to use whatever TCollectionItem class type you pass to the TCollection constructor. So, polymorphic classes can't be loaded natively.
The only way to override that behavior is to disable native streaming for the collection (make your TCollection property be non-published, or at least mark it as stored=false), and then stream the collection items manually.
Have your main component (or whatever TPersistent class owns the collection) override the virtual DefineProperties() method to call TFiler.DefineProperty() to register custom reading/writing methods for streaming the collection items. To support polymorphic classes, you will have to write each item's ClassName to the DFM before writing its property values, then read the name back so you know which class to instantiate before then reading property values.
How do I check through all the components of a form and verify that components are of type TEdit?
You can use the dynamic_cast operator.
Excuse me if I'm wrong, but won't embarcadero automatically add all form-component object pointers to the class definition ( in the header file )..
Such as:
class TFormSomeForm : public TForm
{
__published:
TEdit *SomeEditBox;
TEdit *AnotherEditBox;
...
}
Meaning that you can tell from the header which components are of type TEdit.
Or you can click on the components in the Design View and the Object Inspector will show the type.
My function sets Text property of all edits in a TWinControl and its children.
void __fastcall SetEditsText(TWinControl* winControl, UnicodeString editsText)
{
for (int c = 0; c < winControl->ControlCount; c++)
{
TControl* ctrl = winControl->Controls[c];
TWinControl* wc = dynamic_cast<TWinControl*>(ctrl);
// Check if it's grouping component
if (wc != NULL)
{
// Set edits of children
SetEditsText(wc, editsText);
}
else
{
if (ctrl->ClassType() == __classid(TEdit))
{
TEdit* ecomp = (TEdit*) ctrl;
ecomp->Text = editsText;
}
}
}
}
Using:
void __fastcall TForm1::Button1Click(TObject *Sender)
{
SetEditsText(form1, ""); // Clear all edits
}