创建组件以更新控件的全局属性

Create component to update global properties of controls

我有一组组件,它们共享一些全局变量来控制公共属性,例如风格特点。

这些当前在 运行 时间通过全局 class 访问,例如MyCompsSettings().SomeProperty.

我认为允许用户在设计时配置其中一些属性可能会有用,所以我将全局 class 转换为一个组件,并且因为这些属性需要在 MyCompsSettings( ) 和我的 TMyCompsSettings 组件的实例,我使用全局变量来存储状态,例如

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;

但是,我忽略了 IDE 也会保持 var 状态,所以当我:

  1. 将 TMyCompsSettings 组件添加到表单
  2. 在对象检查器中将 MyCompsSettings1.TitleText 设置为 'ABC'
  3. 打开一个不同的项目
  4. 将 TMyCompsSettings 组件添加到表单

-> MyCompsSettings1.TitleText 已经是 'ABC'!

当然很明显,但我没有考虑到这一点,它破坏了我的整个模型。

有正确的方法吗?例如设计时的字段,运行 时的变量,例如

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;

由于IDE是一个进程,进程中的全局变量会保留在进程中。

如果您希望能够跟踪 IDE 中不同项目之间的设置(如果它们在一个项目组中,则可以同时打开表单)那么您将需要找到一种跟踪它们的方法。

可能最简单的方法是将设置保存在对象中 - 可以在 initialization 部分加载全局对象并在 finalization 部分释放。基于表单的 TComponents 可以检查它们是否处于设计模式,如果它们处于设计模式,那么它们会创建对象的新的单独副本,如果不是,它们会连接到对象的全局实例。

然后访问这些设置的其他组件将全部使用全局对象 - 为确保对象的内容与设计时版本匹配,您需要用任何表单加载版本覆盖全局对象。您可以在 TComponentLoaded 例程中执行此操作。

此代码未经检查,但应该向您简要说明其工作原理。

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);

您还希望确保在您的 TMySettingsComponent 中当用户更改属性时更新全局对象。这可能很简单:

  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;