FMX framework automatically maintains style loading for Controls which implement StyleLookup property. What would you do, to quickly implement compound control, with automatic styling support, without actually write a "true" component class for it?
The quick decision is to create compound "control" by dropping, say, FMX primitives, like TText, TRectangle, etc., on true styleable control, like TPanel.
I usually create reusable compound "control", by dropping TPanel onto TFrame, then, adding neccessary shapes, texts, images, as children of TPanel. TPanel have StyleLookup and OnApplyStyleLookup properties, so the last thing to do, is to create OnApplyStyleLookup handler, which allow access to the nested named style resource stream, if any, loaded for the TPanel.
To make use of those nested style streams, each FMX primitive, which is dropped onto TPanel, should have its unique StyleName assigned. The parent TPanel component should have its StyleLookup property set.
Then, custom style resource should have created for TPanel, and all nested style elements sould be added to it, with StyleName set to the same strings as we have used in TPanel nested primitives.
In TPanel's OnApplyStyleLookup handler, we have to iterate all primtives nested in TPanel, search for the corresponding named style resource, and apply it to the primitive.
To augment this, the following code was written (working, but needs refactoring, IMO):
void styleApply(TObject* dest, TObject* container, const TStyledSettings& ss = AllStyledSettings);
static void propAssignVal(TRttiProperty* p, void* dest, const TValue& val)
{
if( p )
p->SetValue( dest, val );
}
//---------------------------------------------------------------------------
static void propAssign(TRttiType* type, const UnicodeString& prop, void* dest, void* src)
{
ES_ASSERT(type);
TRttiProperty* p = type->GetProperty(prop);
propAssignVal(p, dest, p->GetValue(src));
}
//---------------------------------------------------------------------------
void styleApply(TObject* dest, TObject* container, const TStyledSettings& ss /*= AllStyledSettings*/)
{
TFmxObject* sctl = dynamic_cast<TFmxObject*>(container);
TFmxObject* dobj = dynamic_cast<TFmxObject*>(dest);
const UnicodeString& sname = dobj->StyleName;
if(dobj && sctl && !sname.IsEmpty())
{
TFmxObject* sres = sctl->FindStyleResource(sname);
if( sres )
{
TRttiContext ctx;
// Handle common shape styles
TShape* sshape = dynamic_cast<TShape*>(sres);
if(sshape)
{
TShape* dshape = dynamic_cast<TShape*>(dobj);
if( dshape )
{
dshape->Stroke->Assign(sshape->Stroke);
dshape->Fill->Assign(sshape->Fill);
// Handle some common, as well as distinct shape family properties using rtti
TRttiType* srtti = ctx.GetType( sshape->ClassType() );
TRttiType* drtti = ctx.GetType( dshape->ClassType() );
// Assign common shape properties
propAssign(drtti, esT("StrokeCap"), dshape, sshape);
propAssign(drtti, esT("StrokeDash"), dshape, sshape);
propAssign(drtti, esT("StrokeJoin"), dshape, sshape);
propAssign(drtti, esT("StrokeThickness"), dshape, sshape);
if( srtti == drtti )
{
if( srtti->Name == esT("TRectangle") ||
srtti->Name == esT("TCalloutRectangle") )
{
propAssign(drtti, esT("Sides"), dshape, sshape);
propAssign(drtti, esT("Corners"), dshape, sshape);
propAssign(drtti, esT("CornerType"), dshape, sshape);
propAssign(drtti, esT("XRadius"), dshape, sshape);
propAssign(drtti, esT("YRadius"), dshape, sshape);
}
else if( srtti->Name == esT("TRoundRect") )
propAssign(drtti, esT("Corners"), dshape, sshape);
}
}
}
else
{
// Handle text styles
TText* stxt = dynamic_cast<TText*>(sres);
if( stxt )
{
TText* dtxt = dynamic_cast<TText*>(dobj);
if( dtxt )
{
if( ss.Contains(TStyledSetting::Family) )
dtxt->TextSettings->Font->Family = stxt->TextSettings->Font->Family;
if( ss.Contains(TStyledSetting::Size) )
dtxt->TextSettings->Font->Size = stxt->TextSettings->Font->Size;
if( ss.Contains(TStyledSetting::Style) )
dtxt->TextSettings->Font->Style = stxt->TextSettings->Font->Style;
if( ss.Contains(TStyledSetting::FontColor) )
{
dtxt->TextSettings->FontColor = stxt->TextSettings->FontColor;
// Handle some extended text objects, like TMS HTML one
TRttiType* drtti = ctx.GetType( dtxt->ClassType() );
TRttiProperty* p = drtti->GetProperty(esT("Fill"));
if( p )
{
TValue fill = p->GetValue(dtxt);
TBrush* br = dynamic_cast<TBrush*>( fill.AsObject() );
if( br )
br->Color = stxt->TextSettings->FontColor;
}
p = drtti->GetProperty(esT("Stroke"));
if( p )
{
TValue stroke = p->GetValue(dtxt);
TBrush* br = dynamic_cast<TBrush*>( stroke.AsObject() );
if( br )
br->Color = stxt->TextSettings->FontColor;
}
if( dtxt->ClassName() == stxt->ClassName() )
{
p = drtti->GetProperty(esT("URLColor"));
if( p )
p->SetValue( dtxt, p->GetValue(stxt) );
}
}
if( ss.Contains(TStyledSetting::Other) )
{
dtxt->TextSettings->HorzAlign = stxt->TextSettings->HorzAlign;
dtxt->TextSettings->VertAlign = stxt->TextSettings->VertAlign;
dtxt->TextSettings->Trimming = stxt->TextSettings->Trimming;
dtxt->TextSettings->WordWrap = stxt->TextSettings->WordWrap;
}
}
}
else
{
// Handle image styling
TImage* simg = dynamic_cast<TImage*>(sres);
if( simg )
{
TImage* dimg = dynamic_cast<TImage*>(dobj);
if( dimg )
dimg->MultiResBitmap = simg->MultiResBitmap;
}
}
}
}
}
}
For each TPanel child, that should be styled, we have to call:
styleApply(TObject* dest, TObject* container, const TStyledSettings& ss /*= AllStyledSettings*/);
where dest is an object to be styled, container is parent TPanel instance, and ss is optional paramter for selective text style application.