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.
Related
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.
My environment:
RadStudio 10.2 Tokyo (and also XE4)
I was implementing a copy property method to copy TShape properties.
Following is what I implemented:
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
// set Shape1 color to [clWhite]
Shape1->Brush->Color = clRed; // clWhite
Shape2->Brush->Color = clAqua;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::copyProperties(TControl *srcCtrl, TControl *dstCtrl)
{
// to Keep original names
String orgName_src = srcCtrl->Name;
String orgName_dst = dstCtrl->Name;
// copy properties
TMemoryStream *strm = new TMemoryStream;
Shape1->Name = L""; // to avoid source collision
try {
strm->WriteComponent(srcCtrl);
strm->Position = 0;
strm->ReadComponent(dstCtrl);
}
__finally
{
delete strm;
}
srcCtrl->Name = orgName_src;
dstCtrl->Name = orgName_dst;
}
void __fastcall TForm1::Button1Click(TObject *Sender)
{
copyProperties((TControl *)Shape1, (TControl *)Shape2);
// shift to avoid position-overlapping
Shape2->Left = Shape1->Left + 150;
}
//---------------------------------------------------------------------------
The code seems work fine.
But there is a single case, in which the code does not work.
i.e. when the Brush->Color = clWhite for Shape1.
This bug? can be reproduced also for XE4.
I wonder why only the clWhite has this kind of bug? Other colors does not have this kind of bug.
There is no bug in the streaming. It is operating as designed. You are simply using it in a way that it is not intended for.
clWhite is the declared default value of the TBrush.Color property. The DFM streaming system does not stream properties that are currently set to their default values, unless those properties are declared as nodefault or stored=true. TBrush.Color is neither. So the current Brush.Color value will not be streamed when it is set to clWhite.
Consider using the RTTI system directly instead of using the DFM system to copy properties from one object to another. Then you can copy property values regardless of defaults, if you choose to do so. And you can opt to ignore the Name property without having to (re)store it each time.
For example:
#include <System.TypInfo.hpp>
void __fastcall TForm1::copyProperties(TControl *srcCtrl, TControl *dstCtrl)
{
PTypeInfo pDstTypeInfo = static_cast<PTypeInfo>(dstCtrl->ClassInfo());
PPropList srcPropList;
int srcPropCount = GetPropList(srcCtrl, srcPropList);
try
{
for (int i = 0; i < srcPropCount; ++i)
{
PPropInfo pSrcPropInfo = (*srcPropList)[i];
if (pSrcPropInfo->Name == "Name") continue;
PTypeInfo pSrcPropTypeInfo = *(pSrcPropInfo->PropType);
if (pSrcPropTypeInfo->Kind == tkClass)
{
PPropInfo pDstPropInfo = GetPropInfo(pDstTypeInfo, pSrcPropInfo->Name, TTypeKinds() << tkClass);
if (pDstPropInfo)
{
TPersistent *pDstObj = static_cast<TPersistent*>(GetObjectProp(dstCtrl, pDstPropInfo, __classid(TPersistent)));
if (pDstObj)
{
TPersistent *pSrcObj = static_cast<TPersistent*>(GetObjectProp(srcCtrl, pSrcPropInfo, __classid(TPersistent)));
pDstObj->Assign(pSrcObj);
}
}
}
else
{
PPropInfo pDstPropInfo = GetPropInfo(pDstTypeInfo, pSrcPropInfo->Name);
if (pDstPropInfo)
{
Variant value = GetPropValue(srcCtrl, pSrcPropInfo);
SetPropValue(dstCtrl, pDstPropInfo, value);
}
}
}
}
__finally
{
FreeMem(srcPropList);
}
}
Alternatively:
#include <System.Rtti.hpp>
void __fastcall TForm1::copyProperties(TControl *srcCtrl, TControl *dstCtrl)
{
TRttiContext ctx;
TRttiType *pSrcType = ctx.GetType(srcCtrl->ClassInfo());
TRttiType *pDstType = ctx.GetType(dstCtrl->ClassInfo());
DynamicArray<TRttiProperty*> srcProps = pSrcType->GetProperties();
for (int i = 0; i < srcProps.Length; ++i)
{
TRttiProperty *pSrcProp = srcProps[i];
if (pSrcProp->Name == L"Name") continue;
if (pSrcProp->PropertyType->TypeKind == tkClass)
{
TRttiProperty *pDstProp = pDstType->GetProperty(pSrcPropInfo->Name);
if ((pDstProp) && (pDstProp->PropertyType->TypeKind == tkClass))
{
TPersistent *pDstObj = dynamic_cast<TPersistent*>(pDstProp->GetValue(dstCtrl).AsObject());
if (pDstObj)
{
TPersistent *pSrcObj = dynamic_cast<TPersistent*>(pSrcProp->GetValue(srcCtrl).AsObject());
pDstObj->Assign(pSrcObj);
}
}
}
else
{
TRttiProperty *pDstProp = pDstType->GetProperty(pSrcPropInfo->Name);
if (pDstProp)
{
TValue value = pSrcProp->GetValue(srcCtrl);
pDstProp->SetValue(dstCtrl, value);
}
}
}
}
I only have a pointer to a function, how to call it in js-ctypes?
Thanks.
If you got a function pointer from a C function then you need to make sure it's correctly interpreted as a pointer to FunctionType. Then you can simply call it as you would a JavaScript function. For example, GetProcAddress() returns a function pointer - in the following code I declare GetProcAddress() with a void pointer as return type, then I cast that pointer to a function type matching the signature of the MessageBox() function:
Components.utils.import("resource://gre/modules/ctypes.jsm");
var BOOL = ctypes.int32_t;
var HANDLE = ctypes.voidptr_t;
var HMODULE = HANDLE;
var HWND = HANDLE;
var FARPROC = ctypes.voidptr_t;
var LPCTSTR = ctypes.jschar.ptr;
var LPCSTR = ctypes.char.ptr;
var kernel = ctypes.open("kernel32.dll");
var LoadLibrary = kernel.declare(
"LoadLibraryW",
ctypes.winapi_abi,
HMODULE, // return type
LPCTSTR // parameters
);
var FreeLibrary = kernel.declare(
"FreeLibrary",
ctypes.winapi_abi,
BOOL, // return type
HMODULE // parameters
);
var GetProcAddress = kernel.declare(
"GetProcAddress",
ctypes.winapi_abi,
FARPROC, // return type
HMODULE, LPCSTR // parameters
);
// Load the library we're interested in.
var hUser = LoadLibrary("user32");
// Get the pointer to the function.
var MessageBox = GetProcAddress(hUser, "MessageBoxW");
// Now we have a pointer to a function, let's cast it to the right type.
var MessageBoxType = ctypes.FunctionType(
ctypes.winapi_abi,
ctypes.int32_t, // return type
[HWND, LPCTSTR, LPCTSTR, ctypes.uint32_t] // parameters
);
MessageBox = ctypes.cast(MessageBox, MessageBoxType.ptr);
// Actually call the function.
MessageBox(null, "Test1", "Test2", 0);
// Free the library again if no longer needed. Any imported function
// pointers should be considered invalid at this point.
FreeLibrary(hUser);
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
}
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();
}