Discussion:
Dynamic property-setting of TComponent descendants
(too old to reply)
p***@childcaremanager.com
2007-01-08 22:36:57 UTC
Permalink
I have been working to update the look and feel of my forms in Delphi 7
and I am looking for a way to automatically update the font (to, say,
"Tahoma" for XP or "Segoe UI" for Vista) for all controls on a form.
Since any change to a control's font sets all of the information
(including name and size), this prevents the controls' fonts being set
simply by changing the form's font.

What I would like to do is recurse through all of the controls on a
form and set the font. However, as in the case of the TLabel control,
while the Font property is defined in the TControl class, it is not
published until TLabel. Thus, I have to cast it to a TLabel to get
access to the font.

I would like to avoid writing a massive set of if statements including
every type of component that is on any of the forms in the program.
However, my attempts at dynamic casting aren't working. Here is the
first try at what I'm doing.

I should hope that most of the code below is pretty self-explanatory.
I have a class defined (TComponentUpdater) which has two overloaded
procedures (called SetFont). The hope is that, if SetFont is called
for a component other than a TLabel, it will call the empty SetFont.
Otherwise, it will cast it to the appropriate type (i.e. TLabel) and
call that version of SetFont. TSetFontFlags is a set of enumerations
to indicate what font properties should be set.

If I can get this to work, I will probably break out the code where the
font properties are being set into a different procedure to reduce code
duplication. That said, here's the code:

procedure SetChildrenFonts(const formToUpdate : TCustomForm);
var
cmpChild : TComponent;
nComponent : Integer;
begin
for nComponent := 0 to (formToUpdate.ComponentCount - 1) do
begin
if (formToUpdate.Components[nComponent] is TCustomForm) then
...
else begin
cmpChild := formToUpdate.Components[nComponent];
TComponentUpdater.SetFont(cmpChild as cmpChild.ClassType,
formToUpdate.Font, [sfName, sfSize]);
end;
end;
end;

class procedure TComponentUpdater.SetFont(cmpToUpdate : TComponent;
fntInfo : TFont; sfFlags : TSetFontFlags);
begin
// Do nothing. We can't set the font for a TComponent. This
prevents errors.
end;

class procedure TComponentUpdater.SetFont(lblToUpdate : TLabel; fntInfo
: TFont; sfFlags : TSetFontFlags);
begin
if (sfCharset in sfFlags) then
lblToUpdate.Font.Charset := fntInfo.Charset;
if (sfColor in sfFlags) then
lblToUpdate.Font.Color := fntInfo.Color;
if (sfName in sfFlags) then
lblToUpdate.Font.Name := fntInfo.Name;
if (sfPitch in sfFlags) then
lblToUpdate.Font.Pitch := fntInfo.Pitch;
if (sfSize in sfFlags) then
lblToUpdate.Font.Size := fntInfo.Size;
if (sfStyle in sfFlags) then
lblToUpdate.Font.Style := fntInfo.Style;
end;
Rudy Velthuis
2007-01-08 23:58:07 UTC
Permalink
Post by p***@childcaremanager.com
I have been working to update the look and feel of my forms in Delphi
7 and I am looking for a way to automatically update the font (to,
say, "Tahoma" for XP or "Segoe UI" for Vista) for all controls on a
form. Since any change to a control's font sets all of the
information (including name and size), this prevents the controls'
fonts being set simply by changing the form's font.
Why not set ParentFont to True?
--
Rudy Velthuis http://rvelthuis.de

"The cynics are right nine times out of ten."
-- Henry Louis Mencken (1880-1956)
Rob Kennedy
2007-01-09 01:41:08 UTC
Permalink
Post by p***@childcaremanager.com
I have been working to update the look and feel of my forms in Delphi 7
and I am looking for a way to automatically update the font (to, say,
"Tahoma" for XP or "Segoe UI" for Vista) for all controls on a form.
Have you tried setting the font to MS ShellDlg2 and letting the OS map
the name to the true font? Also take a look at the DesktopFont property.
Post by p***@childcaremanager.com
procedure SetChildrenFonts(const formToUpdate : TCustomForm);
This procedure's name suggests it will work with the form's children,
but that's not really what it does. It works with the components the
form owns.
Post by p***@childcaremanager.com
var
cmpChild : TComponent;
nComponent : Integer;
begin
for nComponent := 0 to (formToUpdate.ComponentCount - 1) do
begin
if (formToUpdate.Components[nComponent] is TCustomForm) then
...
else begin
cmpChild := formToUpdate.Components[nComponent];
TComponentUpdater.SetFont(cmpChild as cmpChild.ClassType,
formToUpdate.Font, [sfName, sfSize]);
end;
end;
end;
The Font property is introduced in TControl. All visible components
descend from TControl. Therefore, the only components you need to worry
about are the TControls.

All the children of a control are available in the Controls array
property. The Components array isn't appropriate. It will give you only
the things that the form _owns_, which is neither a subset nor a
superset of the form's children. (A form can own something that is not
its child, and it can have children it doesn't own.)

Here is a procedure that recursively sets the fonts of all a form's
children. It skips calling SetFont on controls that have ParentFont set
to True; their fonts should already have changed. The recursion occurs
on any children that are TWinControl descendants. TWinControl is the
class that introduces child controls. The procedure's parameter is of
type TWinControl. TCustomForm descends from that class, so you can call
the procedure on all your forms as well.

The TProtectedControl declaration is there to allow access to the Font
and ParentFont properties in TControl and TWinControl. The properties
are protected in the base classes, so they're normally inaccessible.
Declaring TProtectedControl here and type-casting to it within the
procedure invokes Delphi's same-unit scoping rule to grant access to the
protected properties. You'll need to use the same technique to implement
your SetFont procedure, too. Make the procedure accept a TControl as its
first parameter, and then type-cast when you need to get its Font property.

type
TProtectedControl = class(TControl);

procedure SetChildrenFont(Parent: TWinControl);
var
i: Integer;
Child: TControl;
begin
for i := 0 to Pred(Parent.ControlCount) do begin
Child := Parent.Controls[i];
if not TProtectedControl(Child).ParentFont then
TComponentUpdated.SetFont(
Child, TProtectedControl(Parent).Font, [sfName, sfSize]);
if Child is TWinControl then
SetChildrenFont(TWinControl(Child));
end;
end;
--
Rob
p***@childcaremanager.com
2007-01-10 21:16:17 UTC
Permalink
Post by Rob Kennedy
The TProtectedControl declaration is there to allow access to the Font
and ParentFont properties in TControl and TWinControl. The properties
are protected in the base classes, so they're normally inaccessible.
Declaring TProtectedControl here and type-casting to it within the
procedure invokes Delphi's same-unit scoping rule to grant access to the
protected properties. You'll need to use the same technique to implement
your SetFont procedure, too. Make the procedure accept a TControl as its
first parameter, and then type-cast when you need to get its Font property.
Due to how I was taught O.O.P., I've never been too fond of the
"friends" idea (if I may use the C++ terminology). Usually, things are
protected for a reason and creating a class as a friend of another is
generally poor design. However, in this case, I think it was bad
design on Borland's part when they defined the control classes.
Post by Rob Kennedy
type
TProtectedControl = class(TControl);
procedure SetChildrenFont(Parent: TWinControl);
var
i: Integer;
Child: TControl;
begin
for i := 0 to Pred(Parent.ControlCount) do begin
Child := Parent.Controls[i];
if not TProtectedControl(Child).ParentFont then
TComponentUpdated.SetFont(
Child, TProtectedControl(Parent).Font, [sfName, sfSize]);
if Child is TWinControl then
SetChildrenFont(TWinControl(Child));
end;
end;
This worked wonderfully! Thank you for your help.

Loading...