Jump to content
MikeMon

Programmatically Change Properties of a Custom style

Recommended Posts

How can I programmatically change properties of a custom style?

 

Basically, I have created a custom style for TLabel and have added a rectangle to be able to control its background. How can I programmatically change its color so that all TLabels that have that style are automatically updated with the new color?

Share this post


Link to post

Something in this direction ?

 

procedure ColorLabel(const pColor : TAlphaColor; const pString:String; mylabel:Tlabel);
var
  prect:Trectangle;
  ptext:TText;
  
begin  
1. creation

   if mylabel.ChildrenCount=1 then begin // creating trrectangle on fly
     prect:=Trectangle.Create(mylabel);
     prect.Parent:=mylabel;
     prect.Align:=TAlignLayout.Client;
     prect.HitTest:=False;
     prect.Margins.Left:=-1;
     prect.Margins.Right:=-1;
     prect.Margins.Top:=-1;
     prect.Margins.Bottom:=-1;
     prect.Sides:=[]; // no borders
     prect.Fill.Color:=pColor;

     ptext:=TText.Create(mylabel);
     ptext.Parent:=prect;
     ptext.Align:=TAlignLayout.Client;
     ptext.Text:=pString;
     ptext.TextSettings:= mylabel.TextSettings;
     ptext.HitTest:=False;
     mylabel.Text:='';
   end

     
2. changing color
     
     if mylabel.Children[1].ClassName=Trectangle.ClassName then Begin // change color
        (mylabel.Children[1] as Trectangle).Fill.Color:=pColor; // pCOlor is parameter for custom color
        if (mylabel.Children[1] as Trectangle).Children[0].classname=TText.classname then Begin // chanhe text
           ((mylabel.Children[1] as Trectangle).Children[0] as TTExt).Text:=pString; // pString is parameter for TEXT 
           ((mylabel.Children[1] as Trectangle).Children[0] as TTExt).TextSettings.Fontcolor:=mylabel.TextSettings.Fontcolor;
        End;
     End;

end;

 

br,

m

Share this post


Link to post
25 minutes ago, mausmb said:

Something in this direction ?

Hi. Thank you for your answer.

 

Your approach is irrelevant to what I am trying to do. What I want to do is change properties of a style in a Stylebook and expect all objects having that style in their stylelookup field to be updated automatically. Currently, as a workaround, I'm recursively checking all objects and their children and looking for TLabels with stylelookup the style that I want, and changing the fill color of the rectangle in the style. There should be a better way, i.e. just change the fill color of the style in the stylebook. This is the code I'm currently using as a workaround:

procedure ChangeAllLabelStyles(ChangeToColor: Cardinal);

  procedure ChangeObjectRecursive(CurrentComponent: TComponent);
  var
     i: Integer;
  begin
       if CurrentComponent.ClassNameIs('TLabel') and (TLabel(CurrentComponent).StyleLookup = 'RedButtonLabelStyle') then 
          TLabel(CurrentComponent).StylesData['Rectangle1Style.Fill.Color']:= ChangeToColor;
          
       for i:= 0 To CurrentComponent.ComponentCount - 1 do ChangeObjectRecursive(CurrentComponent.Components[i]);
  end;

begin
     ChangeObjectRecursive(Self);
end;

 

Share this post


Link to post

The code above works for the children of the "RedButtonLabelStyle" resource. How do I change for the resource itself. E.g.: when you use a "Custom Style" for a TPanel, it creates a custom style e.g. Panel1Style1. Panel1Style1 doesn't have any children. How can I change its properties (e.g. Fill.Color) by code? The above code's approach (e.g. StylesData['Panel1Style1.Fill.Color'] or StylesData['Fill.Color']) doesn't work.

Share this post


Link to post

As I understand what is in your mind, I think it's your  ChangeObjectRecursive function the problem.

A component (your panel) as style but style is not the "container" of the panel's childs.

And therefore, you change wrong "styledata" property for a label

 

See this :

 

// Changing all labels of a panel randomly
procedure TForm112.Button1Click(Sender: TObject);
var aColor : TAlphaColor;
 procedure ChangePanelLabels(const Panel : TPanel; const ChangeToColor : TAlphaColor);
 var i : integer;
 begin
  if SameText(Panel.StyleLookup,'panel1style1') then
  begin
   for i:=0 to Panel.Children.Count - 1 do
    begin
    if Panel.Children[i].ClassNameIs('TLabel') then
          TLabel(Panel.Children[i]).StylesData['Text.TextSettings.FontColor']:= ChangeToColor;
   end;
  end;
 end;
begin
 randomize;
 aColor:=Random($FFFFFF)
         OR $FF000000;    // add alpha layer
 ChangePanelLabels(Panel1,aColor);
end;

 

 
 

 

Share this post


Link to post
1 hour ago, Serge_G said:

As I understand what is in your mind, I think it's your  ChangeObjectRecursive function the problem.

A component (your panel) as style but style is not the "container" of the panel's childs.

And therefore, you change wrong "styledata" property for a label

 

Hi Serge

 

Thank you for your answer.

 

The custom-styled TPanel has no children. This is what I'm doing:

 

1.) Drop a TPanel on the form. Call it Panel1

2.) Right-click on the TPanel and choose "Edit Custom Style..."

3.) Close the designer

4.) Make sure the "StyleLookup" field of Panel1 is "Panel1Style1"

 

What I need to do is to programmatically change the "Fill.Color" of "Panel1Style1". I can't seem to find a way how.

 

However, if I drop a TLabel and do the same, "Label1Style1" will be created. Label1Style1 has a child stylename "Text". I could easily access and change its properties by my method above, i.e. Label1.StylesData['text.Scale.X']:= 2, or Label1.StylesData['text.Text']:= 'OK'

 

Share this post


Link to post

Panel is no more than a TRectangle (depending style file I think)

I don't know why but if you try to change TPanel fill color this does not work with 'windows 10 desktop style' ,  works during design time with 'default' Stylecollection but not at runtime 🐛something to do with the fact that this TRectangle is at the root and not in a TLayout ?     

Best suggestion I can do is : use a TRectangle. I do not use anymore  TPanel  now, preferring Layouts and TRectangle 😉

  • Like 1

Share this post


Link to post
3 minutes ago, Serge_G said:

Panel is no more than a TRectangle (depending style file I think)

I don't know why but if you try to change TPanel fill color this does not work with 'windows 10 desktop style' ,  works during design time with 'default' Stylecollection but not at runtime 🐛something to do with the fact that this TRectangle is at the root and not in a TLayout ?     

Best suggestion I can do is : use a TRectangle. I do not use anymore  TPanel  now, preferring Layouts and TRectangle 😉

Yes, I can change the color of TPanel during design time, but not at runtime. To be honest I use TRectangle, too. The TPanel in my question was just for reference because it is simple to demonstrate. Generally, I can't access and change the properties of the first-level style. Another example which I still haven't found a workaround for is TSwitch. I use a TSwitchObject for a TSwitch style. Again, because it is the first-level style, I can't access and change its properties, e.g. "ThumbOn.Color".

Share this post


Link to post

The implementations can also vary between the different platforms.

You cannot rely that one element is accessible in same way under all platforms.

This is one of the biggest drawbacks of FMX styles IMHO.

  • Like 1

Share this post


Link to post
2 hours ago, Rollo62 said:

The implementations can also vary between the different platforms.

You cannot rely that one element is accessible in same way under all platforms.

This is one of the biggest drawbacks of FMX styles IMHO.

Let me find a solution for 1 platform and I'll do the rest!! lol

Share this post


Link to post

You could try to simply add another rectangle on top of the CustomStyle Panel background in the StyleDesigner,

with giving a new StyleName it should be accessible.

Not tested in your case, but Ido similar things with other controls.

As proposed above, try to use TRectangle instead of TPanel, or better even TLayout as base container for your other components.

Edited by Rollo62

Share this post


Link to post
1 hour ago, Rollo62 said:

You could try to simply add another rectangle on top of the CustomStyle Panel background in the StyleDesigner,

with giving a new StyleName it should be accessible.

Not tested in your case, but Ido similar things with other controls.

As proposed above, try to use TRectangle instead of TPanel, or better even TLayout as base container for your other components.

Hi @Rollo62

As I said earlier, I do use TRectangle instead of TPanel. But my example above was just for illustration to show that first-level stylename's properties of a style can not be modified whereas its children's can. E.g. with a TSwitchObject, there is nothing I can do. The TSwitchObject stylename is at the first-level and I can't access any of its properties programmatically. I tried adding a TLayout in the style at the first-level and have the TSwitchObject as its child, but it is not working properly. E.g., the isChecked:= True is not changing the TSwitch's state.

Share this post


Link to post
On 5/12/2020 at 10:13 PM, MikeMon said:

The above code's approach (e.g. StylesData['Panel1Style1.Fill.Color'] or StylesData['Fill.Color']) doesn't work.

I found out why and I post the test code without talking too much:

unit FrmMain;

interface

uses
  System.SysUtils,
  System.Classes,
  System.UITypes,
  System.Rtti,
  FMX.Types,
  FMX.Forms,
  FMX.StdCtrls,
  FMX.Objects,
  FMX.Controls,
  FMX.Controls.Presentation;

type
  TMyPanel = class(TPanel)
  public
    function FindStyleResource(const AStyleLookup: string;
      const AClone: Boolean = False): TFmxObject; override;
  end;

  TFormMain = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    Button: TButton;
    Panel: TPanel;
    procedure Button1Click(Sender: TObject);
  end;

var
  FormMain: TFormMain;

implementation

{$R *.fmx}

procedure TFormMain.FormCreate(Sender: TObject);
begin
  Panel := TMyPanel.Create(Self);
  Panel.Parent := Self;
  Panel.Position.X := 100;
  Panel.Position.Y := 100;

  Button := TButton.Create(Self);
  Button.Parent := Self;
  Button.Text := 'Test';
  Button.OnClick := Button1Click;
end;

procedure TFormMain.Button1Click(Sender: TObject);
begin
  Panel.StylesData['Something.Fill.Color']:= TAlphaColors.Dodgerblue;
//  Panel.StylesData['.Fill.Color']:= TAlphaColors.Dodgerblue;
end;

{ TMyPanel }

function TMyPanel.FindStyleResource(const AStyleLookup: string;
  const AClone: Boolean): TFmxObject;

  function FindOnTopLevel(const Name: string): TFmxObject;
  var
    Child: TFmxObject;
  begin
    for Child in Children do
    begin
      if SameText(Child.StyleName, Name) then
      begin
        Exit(Child);
      end;
    end;
    Result := nil;
  end;

begin
  result := nil;

  { New: try something even if ResourceLink has no children ! }
  if ResourceLink.ChildrenCount = 0 then
  begin
    result := FindOnTopLevel('');
    if result is TRectangle then
      Exit;
  end;

  inherited;
end;

(*
There is a problem when FindStyleResource is called on a FResourceLink instance which has no children.

function TFmxObject.FindStyleResource(const AStyleLookup: string; const Clone: Boolean = False): TFmxObject;
begin
  if (AStyleLookup <> '') and (FChildren <> nil) and (FChildren.Count > 0) then
  begin
    // not executed because TPanel.ResourceLink has no children
  end;
end;

FMX.Types.TFmxObject.FindStyleResource('',False)
FMX.Controls.TStyledControl.FindStyleResource(???,???)
FMX.Controls.Presentation.TPresentedControl.FindStyleResource('',False)
FMX.Controls.TStyledControl.StyleDataChanged(???)
FMX.Controls.Presentation.TPresentedControl.StyleDataChanged('.Fill.Color')
FMX.Controls.TStyledControl.SetStyleData('.Fill.Color')
FrmMain.TFormMain.Button1Click($3F30880)
*)

end.

 

Share this post


Link to post

The styles data is not always accessible, better if you touch them only via the OnApplyStyleLookup event.

At that time should be guaranteed that all presentation layers are created.

Edited by Rollo62

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×