在组件的持久化中实现 `Auto` 属性

Posted

技术标签:

【中文标题】在组件的持久化中实现 `Auto` 属性【英文标题】:Implementing `Auto` property in component's persistent 【发布时间】:2012-01-09 19:44:34 【问题描述】:

我有一个具有相应持久性的组件。这个持久化当然是作为组件的属性发布的,显示在对象检查器中。它有自己的几个不同的属性,主要是 4 个整数(左、上、右、下)。我在这个持久化中还有一个名为Auto 的属性,旨在根据组件的大小自动计算 4 个整数。

更具体地说,这个图形组件周围有一个边框,每条边都可以有不同大小的边框。这个持久性及其属性指定了每边边框的厚度。当Auto开启时,4个边框边是根据组件的大小来计算的。

现在它似乎在大多数情况下都可以正常工作,但不知何故,这个 Auto 属性会随机返回 False。此外,在设计时,在启用Auto 的情况下调整组件大小时,它实际上会完美地相应地进行计算。但是,在运行时,它会返回 False,不再计算它们。在启用Auto 保存后,关闭表单,然后重新打开它,这个Auto 属性又回到了False。

这 4 个整数属性具有设置器,如果设置了它们,它会将 Auto 属性设置为 false。我假设这是导致它再次变为 false 的原因,但我没有告诉它在任何地方设置这些属性。

这是持久的:

  TJDGlassBorder = class(TPersistent)
  private
    fOwner: TJDGlass; //This is the parent component
    fGlow: Integer;
    fBottom: Integer;
    fLeft: Integer;
    fTop: Integer;
    fRight: Integer;
    fColor: TColor;
    fOnEvent: TNotifyEvent;
    fAuto: Bool;
    procedure SetBottom(const Value: Integer);
    procedure SetColor(const Value: TColor);
    procedure SetGlow(const Value: Integer);
    procedure SetLeft(const Value: Integer);
    procedure SetRight(const Value: Integer);
    procedure SetTop(const Value: Integer);
    function GetBottom: Integer;
    function GetLeft: Integer;
    function GetRight: Integer;
    function GetTop: Integer;
    procedure SetAuto(const Value: Bool);
  public
    constructor Create(AOwner: TJDGlass);
    destructor Destroy; override;
    procedure Event;
    procedure Assign(Source: TPersistent); override;
  published
    property Auto: Bool read fAuto write SetAuto default True;
    property Left: Integer read GetLeft write SetLeft default 3;
    property Top: Integer read GetTop write SetTop default 2;
    property Right: Integer read GetRight write SetRight default 3;
    property Bottom: Integer read GetBottom write SetBottom default 4;
    property Color: TColor read fColor write SetColor;
    property Glow: Integer read fGlow write SetGlow default 1;
    property OnEvent: TNotifyEvent read fOnEvent write fOnEvent;
  end;

/////////////

 TJDGlassBorder 

procedure TJDGlassBorder.Assign(Source: TPersistent);
begin
  inherited Assign(Source);
  Event;
end;

constructor TJDGlassBorder.Create(AOwner: TJDGlass);
begin
  fOwner:= AOwner;
  fAuto:= True;
  fColor:= clBlack;
  fGlow:= 1;
  Event;
end;

destructor TJDGlassBorder.Destroy;
begin

  inherited;
end;

procedure TJDGlassBorder.Event;
begin
  if assigned(fOwner) then 
    if fOwner <> nil then
      fOwner.Invalidate;
  if assigned(fOnEvent) then
    fOnEvent(Self);
end;

function TJDGlassBorder.GetBottom: Integer;
begin
  if fAuto then begin
    if assigned(fOwner) then begin
      if fOwner <> nil then begin
        Result:= Max(2, fOwner.Height div 10);
        fBottom:= Result;
      end;
    end;
  end else begin
    Result:= fBottom;
  end;
end;

function TJDGlassBorder.GetLeft: Integer;
begin
  if fAuto then begin
    if assigned(fOwner) then begin
      if fOwner <> nil then begin
        Result:= (Top + Bottom) div 2;
        fLeft:= Result;
      end;
    end;
  end else begin
    Result:= fLeft;
  end;
end;

function TJDGlassBorder.GetRight: Integer;
begin
  if fAuto then begin
    if assigned(fOwner) then begin
      if fOwner <> nil then begin
        Result:= (Top + Bottom) div 2;
        fRight:= Result;
      end;
    end;
  end else begin
    Result:= fRight;
  end;
end;

function TJDGlassBorder.GetTop: Integer;
begin
  if fAuto then begin
    if assigned(fOwner) then begin
      if fOwner <> nil then begin
        Result:= Max(1, fOwner.Height div 30);
        fTop:= Result;
      end;
    end;
  end else begin
    Result:= fTop;
  end;
end;

procedure TJDGlassBorder.SetAuto(const Value: Bool);
begin
  fAuto := Value;
  Event;
end;

procedure TJDGlassBorder.SetBottom(const Value: Integer);
begin          
  fAuto:= False;
  fBottom := Value;  
  Event;
end;

procedure TJDGlassBorder.SetColor(const Value: TColor);
begin
  fColor := Value;  
  Event;
end;

procedure TJDGlassBorder.SetGlow(const Value: Integer);
begin
  fGlow := Value;  
  Event;
end;

procedure TJDGlassBorder.SetLeft(const Value: Integer);
begin         
  fAuto:= False;
  fLeft := Value;  
  Event;
end;

procedure TJDGlassBorder.SetRight(const Value: Integer);
begin       
  fAuto:= False;
  fRight := Value; 
  Event;
end;

procedure TJDGlassBorder.SetTop(const Value: Integer);
begin           
  fAuto:= False;
  fTop := Value;
  Event;
end;

编辑:

我在上面的代码中尝试了另外 3 件事,但仍然有问题。这就是我所做的:

1:在其他 4 个属性之后发布 Auto 属性,考虑这些属性的检索顺序。

published
  property Auto: Bool read fAuto write SetAuto default True;
  property Left: Integer read GetLeft write SetLeft default 3;
  property Top: Integer read GetTop write SetTop default 2;
  property Right: Integer read GetRight write SetRight default 3;
  property Bottom: Integer read GetBottom write SetBottom default 4;

改为:

published
  property Left: Integer read GetLeft write SetLeft default 3;
  property Top: Integer read GetTop write SetTop default 2;
  property Right: Integer read GetRight write SetRight default 3;
  property Bottom: Integer read GetBottom write SetBottom default 4;
  property Auto: Bool read fAuto write SetAuto default True;

2:在这些整数的属性设置器中,我正在检查新值是否与现有值不同...

procedure TJDGlassBorder.SetTop(const Value: Integer);
begin         
  if Value <> fTop then begin  
    fAuto:= False;
    fTop := Value;
    Event;
  end;
end;

3:在这些整数的属性 getter 中,我更改了它检查现有值的方式...

function TJDGlassBorder.GetTop: Integer;
begin
  Result:= fTop;
  if fAuto then begin
    if assigned(fOwner) then begin
      if fOwner <> nil then begin
        Result:= Max(1, fOwner.Height div 30);
        fTop:= Result;
      end;
    end;
  end;
end;

同样,这些尝试都没有奏效,我仍然有这个问题。

【问题讨论】:

阅读我对您发布的答案所做的评论。此外,您已经了解到 default 会阻止存储该属性,除非该值与默认值不同,但它不会将该属性设置为默认值。为什么你会认为整数属性的处理方式与布尔值不同? @KenWhite:我从来没有提到治疗上有什么不同。但我确实知道发布属性的顺序很重要。保存到 DFM 时,我相信它会按照它们在类中发布的顺序保存它们,同时,它们会按照它们在 DFM 中保存的顺序加载。如果先加载Auto,然后加载4个整数,那么整数设置会触发Auto再次切换回false,即使它被保存为True。移动 Auto 在整数之后发布,使其在整数之后从 DFM 加载,这是部分原因 顺序没有区别。它们是根据 RTTI 加载的(这就是为什么存储在 DFM 中的所有属性都必须是 published,并且会自动添加 $M+ 编译器选项。您的逻辑是基于对流式传输工作原理的误解。您可能有setter 过程搞砸了,但流式机制不可能这样做。 我在标准 VCL 库中看到了很多地方(不知道具体示例),在代码中明确注释了某些属性必须在某些其他属性之前/之后发布,因为某些原因.我不完全确定保存/加载东西的顺序,但我确信从 DFM 加载/设置属性的顺序确实有很大的不同。成像 1) 将 Auto 设置为 True,2) 将 Left 设置为 20,然后 3) 将 Left 设置为 20(上面是我的代码)将 Auto 设置回 false。 (也就是说,即使不考虑 DFM,也可以考虑按不同的顺序设置这些属性) 【参考方案1】:

已修复!

我在上面的编辑中进行的 3 次尝试是部分问题,但实际修复是删除 Auto 属性的 default。问题是,我将此默认设置为 True,在这种情况下,该属性未保存在 DFM 文件中。因此它甚至没有尝试设置这个Auto 属性。去掉 Default 固定,因为现在无论是真还是假,它总是保存在 DFM 文件中,因此总是设置这个值。发布属性的顺序也是问题的一半。


这是我上面发布的最终代码:

  TJDGlassBorder = class(TPersistent)
  private
    fOwner: TJDGlass; //This is the parent component
    fGlow: Integer;
    fBottom: Integer;
    fLeft: Integer;
    fTop: Integer;
    fRight: Integer;
    fColor: TColor;
    fOnEvent: TNotifyEvent;
    fAuto: Bool;
    procedure SetBottom(const Value: Integer);
    procedure SetColor(const Value: TColor);
    procedure SetGlow(const Value: Integer);
    procedure SetLeft(const Value: Integer);
    procedure SetRight(const Value: Integer);
    procedure SetTop(const Value: Integer);
    function GetBottom: Integer;
    function GetLeft: Integer;
    function GetRight: Integer;
    function GetTop: Integer;
    procedure SetAuto(const Value: Bool);
  public
    constructor Create(AOwner: TJDGlass);
    destructor Destroy; override;
    procedure Event;
    procedure Assign(Source: TPersistent); override;
  published
    property Left: Integer read GetLeft write SetLeft default 3;
    property Top: Integer read GetTop write SetTop default 2;
    property Right: Integer read GetRight write SetRight default 3;
    property Bottom: Integer read GetBottom write SetBottom default 4;
    property Color: TColor read fColor write SetColor;
    property Glow: Integer read fGlow write SetGlow default 1;
    property OnEvent: TNotifyEvent read fOnEvent write fOnEvent;
    property Auto: Bool read fAuto write SetAuto;
  end;

/////////////

 TJDGlassBorder 

procedure TJDGlassBorder.Assign(Source: TPersistent);
begin
  inherited Assign(Source);
  Event;
end;

constructor TJDGlassBorder.Create(AOwner: TJDGlass);
begin
  fOwner:= AOwner;
  fAuto:= True;
  fColor:= clBlack;
  fGlow:= 1;
  Event;
end;

destructor TJDGlassBorder.Destroy;
begin

  inherited;
end;

procedure TJDGlassBorder.Event;
begin
  if assigned(fOwner) then 
    if fOwner <> nil then
      fOwner.Invalidate;
  if assigned(fOnEvent) then
    fOnEvent(Self);
end;

function TJDGlassBorder.GetBottom: Integer;
begin
  Result:= fBottom;
  if fAuto then begin
    if assigned(fOwner) then begin
      if fOwner <> nil then begin
        Result:= Max(2, fOwner.Height div 10);
        fBottom:= Result;
      end;
    end;
  end;
end;

function TJDGlassBorder.GetLeft: Integer;
begin
  Result:= fLeft;
  if fAuto then begin
    if assigned(fOwner) then begin
      if fOwner <> nil then begin
        Result:= (Top + Bottom) div 2;
        fLeft:= Result;
      end;
    end;
  end;
end;

function TJDGlassBorder.GetRight: Integer;
begin
  Result:= fRight;
  if fAuto then begin
    if assigned(fOwner) then begin
      if fOwner <> nil then begin
        Result:= (Top + Bottom) div 2;
        fRight:= Result;
      end;
    end;
  end;
end;

function TJDGlassBorder.GetTop: Integer;
begin
  Result:= fTop;
  if fAuto then begin
    if assigned(fOwner) then begin
      if fOwner <> nil then begin
        Result:= Max(1, fOwner.Height div 30);
        fTop:= Result;
      end;
    end;
  end;
end;

procedure TJDGlassBorder.SetAuto(const Value: Bool);
begin
  fAuto := Value;
  Event;
end;

procedure TJDGlassBorder.SetBottom(const Value: Integer);
begin
  if Value <> fBottom then begin
    fAuto:= False;
    fBottom := Value;  
    Event;
  end;
end;

procedure TJDGlassBorder.SetColor(const Value: TColor);
begin
  fColor := Value;  
  Event;
end;

procedure TJDGlassBorder.SetGlow(const Value: Integer);
begin
  fGlow := Value;  
  Event;
end;

procedure TJDGlassBorder.SetLeft(const Value: Integer);
begin         
  if Value <> fLeft then begin
    fAuto:= False;
    fLeft := Value;  
    Event;
  end;
end;

procedure TJDGlassBorder.SetRight(const Value: Integer);
begin       
  if Value <> fRight then begin
    fAuto:= False;
    fRight := Value; 
    Event;
  end;
end;

procedure TJDGlassBorder.SetTop(const Value: Integer);
begin        
  if Value <> fTop then begin   
    fAuto:= False;
    fTop := Value;
    Event;
  end;
end;

【讨论】:

如果您希望它基本上为 True 除非另有特别设置,请在构造函数中将其设置为 True。然后当 .DFM 流入时,其中的任何存储值都将替换您在构造函数中设置的值。【参考方案2】:

首先,正如您已经注意到的,删除default。如果您希望属性为True,除非专门设置为另一个值,请在构造函数中设置它。当DFM 从您的可执行文件中流入时,其中存储的任何值都将替换构造函数中的集合。

其次,您的问题部分是基于错误的逻辑。 :) 如果您希望 Auto 属性控制其他属性(意味着如果 Auto = True 则忽略为其他属性设置的任何值),然后在设置器中进行测试:

procedure TJDGlassBorder.SetTop(const Value: Integer);
begin
  // Only change the value if Auto is not True         
  if (not FAuto) and (Value <> fTop) then 
  begin  
    fTop := Value;
    Event;
  end;
end;

procedure TJDGlassBorder.SetAuto(const Value: Boolean);
begin
  if (Value <> FAuto) then
  begin
    FAuto := Value;
    if FAuto then
    begin
      FTop := 0;     // Or whatever. Set field and not property to 
      FLeft := 0;    // avoid the setter's side effects
      FWidth := 0;
      FHeight := 0;
    end;
    // Whatever you need to do now.
  end;
end;

这样做意味着您可以通过调用 setter 来避免自动更改。

如果可以的话,我建议您在运行时使用Loaded 推迟对Event 的调用;这在组件从 .dfm 完全流式传输后调用,您可以在设置所有属性后执行任何计算或重绘。您可以通过检查ComponentState 属性来确定您是在IDE 中还是在运行时;如果它包含csDesigning,那么您在设计时就在IDE 中,如果设置了csLoading,那么您就在运行时。 (ComponentState 是一个集合,所以你检查使用if csDesigning in ComponentState。)

【讨论】:

实际上,它应该在设置值时更改 fAuto,无论是在设计时还是运行时。这就是它的全部目的。与设置(例如,其他一些 VCL 控件的“ParentColor”)的工作方式相同。它一开始并没有按预期工作,但我已经弄清楚了。谢谢。 我的意思是,如果用户(在设计或运行时)决定分配 4 个整数值之一,那么应该将 Auto 属性设置为 false。 另外,如果启用自动,我没有 FTop、FLeft 等 - 它需要根据控件的当前大小在属性 getter 中动态计算它。如果我像这样使用这些字段,那么当控件调整大小时,它将保持不变。这样做的重点是当场计算这些值,而不是存储字段。 如果自动启用,你从哪里得到值(如果你没有内部存储)?例如,假设我设置了四个值(TopLeftWidthHeight),然后不小心Auto 设置为True,实现错误,并将其设置回False。然后我必须返回并重置其他四个属性吗?如果没有,您必须在内部某个地方为它们存储空间。正确的?哦,是的 - 你确实fLeftfTop 等 - 我只是看看。 :) 是的,我有它们,但如果启用了自动,我就不会使用它们。属性获取器检查 Auto 属性,如果启用了 Auto,那么它会查看父控件的尺寸并当场计算值。如果您不小心在例如面板中启用了“ParentColor”,并将其切换回 False,同样的处理。

以上是关于在组件的持久化中实现 `Auto` 属性的主要内容,如果未能解决你的问题,请参考以下文章

有啥方法可以单独在 html 和 javascript 中实现持久的、可共享的存储?

如何在 C++ 中实现强大的数据持久层?

在 Rails 应用程序中实现“记住我”

vue中keepalive怎么理解?

即使在跨多个组件路由之后,如何在 Electron 驱动的 React 应用程序中处理持久数据、函数和任务?

C# WinForms。在组件中实现属性“名称”