Home > Enterprise >  Create component to update global properties of controls
Create component to update global properties of controls

Time:03-17

I have a set of components, that share some global variables to control common properties, e.g. style features.

These are currently accessed at run-time via a global class, e.g. MyCompsSettings().SomeProperty.

I thought it might be useful to allow users to configure some of these properties at design-time, so I converted the global class to a component, and because these properties need to be shared between MyCompsSettings() and instances of my TMyCompsSettings component(s), I used global vars to store the state, e.g.

type
  TMyCompsSettings = class(TComponent)
  private
    function GetBackgroundColor(): TColor;
    procedure SetBackgroundColor(const v: TColor);
    function GetTitleText(): string;
    procedure SetTitleText(const v: string);
  published
    property BackgroundColor: TColor read GetBackgroundColor write SetBackgroundColor;
    property TitleText: string read GetTitleText write SetTitleText;
  end;

implementation

var
  gBackgroundColor: TColor;
  gTitleText: string;

function TIEGlobalSettings.GetBackgroundColor(): TColor;
begin
  Result := gBackgroundColor;
end;

procedure TIEGlobalSettings.SetBackgroundColor(const v: TColor);
begin
  gBackgroundColor := v;
end;

function TIEGlobalSettings.GetTitleText(): string;
begin
  Result := gTitleText;
end;

procedure TIEGlobalSettings.SetTitleText(const v: string);
begin
  gTitleText := v;
end;

However, I overlooked that the IDE will also maintain the var states, so when I:

  1. Add a TMyCompsSettings component to a form
  2. Set MyCompsSettings1.TitleText to 'ABC' in the object inspector
  3. Open a different project
  4. Add a TMyCompsSettings component to a form

-> MyCompsSettings1.TitleText is already 'ABC'!

Obvious of course, but I didn't consider that, and it breaks my whole model.

Is there a correct way to do this? e.g. Fields at design-time, vars at run-time, e.g.

type
  TMyCompsSettings = class(TComponent)
  private
    FAuthoritative: Boolean;     // Set to true for first instance, which will be MyCompsSettings()
    FBackgroundColor: TColor;
    FTitleText: string;

    function GetBackgroundColor(): TColor;
    procedure SetBackgroundColor(const v: TColor);
    function GetTitleText(): string;
    procedure SetTitleText(const v: string);
  published
    property BackgroundColor: TColor read GetBackgroundColor write SetBackgroundColor;
    property TitleText: string read GetTitleText write SetTitleText;
  end;

implementation

function TIEGlobalSettings.GetBackgroundColor(): TColor;
begin
  if FAuthoritative or ( csDesigning in ComponentState ) then
    Result := FBackgroundColor
  else
    Result := MyCompsSettings().BackgroundColor;
end;

procedure TIEGlobalSettings.SetBackgroundColor(const v: TColor);
begin
  if FAuthoritative or ( csDesigning in ComponentState ) then
    FBackgroundColor := v
  else
    MyCompsSettings().BackgroundColor := v;
end;

function TIEGlobalSettings.GetTitleText(): string;
begin
  if FAuthoritative or ( csDesigning in ComponentState ) then
    Result := FTitleText
  else
    Result := MyCompsSettings().TitleText;
end;

procedure TIEGlobalSettings.SetTitleText(const v: string);
begin
  if FAuthoritative or ( csDesigning in ComponentState ) then
    FTitleText := v
  else
    MyCompsSettings().TitleText := v;
end;

CodePudding user response:

As the IDE is a process, global variables in the process will remain in the process.

If you want to be able to track the settings between different projects in the IDE (which, if they're in a project group, could both have forms open at the same time) then you will need to find a way of tracking them.

Probably the simplest way is to have the settings held in an object - there can be a global object loaded in an initialization section and freed in a finalization section. Your form based TComponents can check if they are in design mode or not and if they are in design mode then they create a new separate copy of the object, if not they connect to the global instance of the object.

Other components that then access those settings will all use the global object - to ensure that the contents of the object match the design time version you would need to overwrite the global object with any form loaded version. You can do this in the TComponent's Loaded routine.

This code is unchecked, but should give you an outline of how it might work.

implementation

  type
  TMySettings = class(TPersistent)     // so you can .Assign
  protected
    FOwner: TPersistent;
    function GetOwner(): TPersistent; override;
  public
    constructor Create(AOwner: TPersistent); reintroduce;
  property 
    Owner: TPersistent read GetOwner();
  end;

  TMySettingsComponent = class(TComponent)
  protected
    procedure Loaded(); override;

  public
    destructor Destroy(); override;
    procedure AfterConstruction(); override;
  end;

implementation
  var
    gpMySettings: TMySettings;

  constructor TMySettings.Create(AOwner: TPersistent);
  begin
    Self.FOwner:=AOwner;
    inherited Create();
  end;

  function TMySettins.GetOwner(): TPersistent;
  begin
    Result:=Self.FOwner;
  end;

  destructor TMySettingsComponent.Destroy;
  begin
    if(Self.FSettings.Owner = Self) then
      FreeAndNIl(Self.FSettings);
    inherited;
  end;

  procedure TMySettingsComponent.AfterConstruction();
  begin
    // our ComponentState will not yet be set
    if( (Self.Owner <> nil) And
        (csDesigning in Self.Owner.ComponentState) ) then 
      Self.FSettings:=TMySettings.Create(Self) 
    else
      Self.FSettings:=gpMySettings;
    inherited;
  end;

  procedure TMySettingsComponent.Loaded;
  begin
    if( (Self.FMySettings.Owner=Self) And
        (gpMySettings<>nil) ) then
      gpMySettings.Assign(Self.FMySettings);
  end;

initialization
  gpMySettings:=TMySettings.Create(nil);

finalization
  FreeAndNIl(gpMySettings);

You would also want to ensure that in your TMySettingsComponent you update the global object when the user is changing the properties. This could be as simple as:

  procedure TMyComponentSettings.SetBackgroundColour(FNewValue: TColor);
  begin
    if(Self.FSettings.FBkColour<>FNewValue) then
    begin
      Self.FSettings.FBkColour:=FNewValue;
      if( (Self.FSettings.Owner=Self) And
          (gpMySettings<>nil) ) then
        gpMySettings.Assign(Self.FSettings);
        // -- or use gpMySettings.FBkColour:=FNewValue;
    end;
  end;
  • Related