【问题标题】:How to override an inherited property?如何覆盖继承的属性?
【发布时间】:2012-07-11 02:06:52
【问题描述】:

我有一个类 (TMyClass),它有一个属性 (Items: TItems)

TItems = class;    

TMyClass = class(TComponent)
private
   FItems: TItems;
   procedure SetItems(const Value: TItems);
protected

public

protected
  property Items: TItems read FItems write SetItems;
end.

TExItems = class(TItems)
private
  FNewProb: Integer;
protected

public

published
  property NewProp: Integer read FNewProb write FNewProb;
end.

TExMyClass = class(TMyClass)
private
   FItems: TExItems;
   procedure SetItems(const Value: TItems);
protected

public

published
  property Items: TExItems read FItems write SetItems;
end.

新的“Items”属性是从 TItems 继承的,但是当我安装组件时,TExItems 的新属性“NewProb”没有出现,看起来“Items”属性仍然是 TItems 而不是 TExItems...如何覆盖它?

谢谢

修改: 这是真实的代码

类型 TKHAdvSmoothDock = 类;

TKHAdvSmoothDockItem = class(TAdvSmoothDockItem)
private
  FImageIndex: TImageIndex;
  procedure SetImageIndex(const Value: TImageIndex);
protected

public

published
  property ImageIndex: TImageIndex read FImageIndex write SetImageIndex default -1;
end;

TKHAdvSmoothDockItems = class(TAdvSmoothDockItems)
private
  FOwner: TKHAdvSmoothDock;
  FOnChange: TNotifyEvent;
  function GetItem(Index: Integer): TKHAdvSmoothDockItem;
  procedure SetItem(Index: Integer; const Value: TKHAdvSmoothDockItem);
protected
  function GetOwner: TPersistent; override;
public
  constructor Create(AOwner: TKHAdvSmoothDock);
  function Add: TKHAdvSmoothDockItem;
  function Insert(Index: Integer): TKHAdvSmoothDockItem;
  property Items[Index: Integer]: TKHAdvSmoothDockItem read GetItem write SetItem; default;
  procedure Delete(Index: Integer);
published
  property OnChange: TNotifyEvent read FOnChange write FOnChange;
end;

TKHAdvSmoothDock = class(TAdvSmoothDock)
private
  FImageChangeLink: TChangeLink;
  FImages: TCustomImageList;
  FItems: TKHAdvSmoothDockItems;
  procedure ImageListChange(Sender: TObject);
  procedure SetImages(const Value: TCustomImageList);
  procedure SetItems(const Value: TKHAdvSmoothDockItems);
  function GetItems: TKHAdvSmoothDockItems;
  { Private declarations }
protected
  procedure UpdateImagesFromImageList;
public
  constructor Create(AOwner: TComponent); override;
  destructor Destroy; override;
published
  property Images: TCustomImageList read FImages write SetImages;
  property Items: TKHAdvSmoothDockItems read GetItems write SetItems;
end;

问候。

【问题讨论】:

  • 另外,为了在 Delphi IDE 中显示某些内容,您应该从 TPersistent 类开始 -
  • 您包含的新代码不包含任何相互关联的类,那么它们与“覆盖”任何东西有什么关系?它们还包含许多可能与您的问题无关的内容。请注意发布最小示例,以免分散实际问题的注意力。
  • 欢迎来到 Stack Overflow。请注意,您不必成为 Stack Overview 的专家就可以在这里提出好的问题。提出好问题的技巧是通用的。如果你给朋友发电子邮件,你也会面临同样的问题。当你犯错时,你的朋友可能会更温和一些,但他仍然无法仅凭你在此处提供的信息来回答你的问题。

标签: delphi inheritance properties overriding


【解决方案1】:

这与尝试拥有多个使用相同 Lines 对象的 TMemo 是相同的问题。

由于TCustomMemo 在其private 部分FLines: TStrings; 中删除,因此不可能有多个使用相同FLines 的TMemo。

唯一的工作方法(我仍然知道)是将整个TCustomMemo 类完全复制为TLinkedCustomMemo,但在公共部分定义FLines: TStrings;;还需要克隆“TMemo”但引用TLinkedCustomMemo,而不是TCustomMemo

然后使用删除TMemo=class(TLinkedMemo) 的技巧,您将可以公开访问FLines,因此您可以在所有其余链接的备忘录上将该对象替换为主TMemo 的对象。

为什么要在TMemos的内容上做这样的Link? 简单的答案可能是:拥有多个显示相同文本的 TMemo,因此用户可以一次查看(和编辑)两个(或更多)不同的部分,就像某些 SpreadSheets 所做的那样......它通常还涉及它们,同步水平滚动条等。

有时候做一些看起来很容易的事情,其实是很复杂的! 只是由于 VCL 设计错误造成的。

对于 TMemos 的示例...为什么他们还没有根据 TStringList 而不是 TStrings 定义其 Lines 属性?这样我们就可以同时为多个 TMemo 使用同一个字符串列表。

如果想看看,内部是如何依赖于这样的......搜索类TMemoStrings(它出现在StdCtrls的实现部分)。为什么它必须只有一个 TMemo?,为什么它必须有?为什么不使用 TStringList 而不是所有的地狱?

当一些类如此封闭时......它来了声明类的黑客方式......但是如何只更改控件的属性而不需要完全复制一些类(只是为了改变这么少的东西)?

哦,是的,内容链接备忘录的真实示例可能是:

  • 让用户(同时)查看文本文件的两个不同部分,而无需双倍的内存存储
  • 或更好的示例:用户希望在第 3 行编辑一个单词,在千万行上编辑另一个单词,而无需滚动

因此,您放置了两个 TMemo 并链接 Lines 属性,因此用户可以控制从两个备忘录中编辑此类 Lines 属性(在不同点上),而无需一直向下和向上滚动。

另一个示例:

  • 用户想要编辑 100 到 100 万加 20 行,但需要同时查看 10 到 20 行。
  • 不可能使用copy 来创建备忘录,在 32 位 Windows 上超过 3GiB 的 RAM(不允许),每个备忘录都需要 >1.6GiB 的内存...等等。
  • 复制此类数据不是一种选择,用户在第二个备忘录上进行编辑,但首先必须同步...所以在松散焦点之后(或者如果想要好看,则在编辑之后)您必须将所有数据从一个复制到其他备忘录...计算时间? -等

样本太多了……最重要的是我想不出来的。

所以以一般形式回答您的问题,“如何破解一个类来稍微修改它”:

  1. 克隆在新单元中定义类的代码,调用该单元以明确它是为了克隆该类并进行一些修改
  2. 仅当您的修改将向后兼容时才使用相同的类名(例如,在将 Align 属性添加到 TE 时,使用将其声明为 TTheClass=class(TheUnit.TTheClass) 的技巧),否则使用不同的名称
  3. interface 部分的uses 末尾添加这样的新单元

就是这样......说起来很简单,在某些情况下需要努力工作。

我总是推荐使用 nes 类名称,除了将Alignment 属性添加到TEdit 的示例。

什么时候使用声明的技巧?当您有一个完整的应用程序已经编码并想要添加到所有TEdit 上时,对齐...首先要创建一个新组件,将其添加到组件工具中,重新定义所有表单以不使用:TEdit; 并使用@987654344 @...您可以简单地添加到使用您的单元,瞧...所有TEdit 现在都有这样的属性,IDE 管理员也可以看到它,等等。

【讨论】:

  • 欢迎来到 SO!。您的回复看起来不像是对这个问题的好回答。一旦你有足够的reputation,你就可以在任何帖子上comment。还要检查这个what can I do instead
【解决方案2】:

从技术上讲,您不能覆盖属性,但可以通过多种方式模拟覆盖。例如,请参阅this answer 了解最基本的礼仪。

现在我没有TAdvSmoothDock 的代码,所以剩下的只是猜测。当TAdvSmoothDock.Items 的属性getter 和setter 是虚拟的时,您可以覆盖它们。但是在更高级的组件中,我想来自 TMS 的组件是,TAdvSmoothDock 很有可能拥有一个受保护的 CreateItem 方法,只要需要可以覆盖的新项目,就会调用该方法。如果是这种情况,那么您应该像这样实现它:

function TKHAdvSmoothDock.CreateItem: TAdvSmoothDockItem;
begin
  Result := TKHAdvSmoothDockItem.Create;
end;

并像这样使用它:

TKHAdvSmoothDockItem(AKHAdvSmoothDock.Items[I]).ImageIndex := ...

【讨论】:

  • 非常感谢您提供的示例!在使用属性之前阅读这些示例非常重要......我正在搜索,我发现了一些可能对我有帮助但我需要更多解释的问题:我不能使用“reintroduce”而不是“Override” ?对于 setter 和 getter?
  • 可以,但这是个坏主意,因为它会破坏继承:即当祖先调用 setter 或 getter 时,不会调用你的。
【解决方案3】:

您可以实现和覆盖方法 getItem 和 setItem;

仅为 TMyClass 实现属性 Item

property Items: TItems read getItems write setItemps;

对于 TMyClass:

public:  
function getItems : TItems; virtual;
procedure setItems(items: TItems); virtual;

对于 TExMyClass:

public:
function getItems : TItems; override;
procedure setItems(items: TItems); override;

function TExMyClass.getItems : TItems;
begin
  result := fItems;
end;

procedure TExMyClass.setItems(items : TItems);
begin
  self.itmes := items;
end;

所以,TExMyClass.items.className = TExItems !

【讨论】:

    【解决方案4】:

    属性 getter 和 setter 可以是虚拟的,然后被继承类覆盖,请参阅下面的更新示例。对于示例代码有一个警告,那就是您正在尝试更改属性的类型,这是不允许的。我建议您在TExMyClass.SetItems 中检查Value is TExItems,但使用继承的Items 属性并在TExMyClass 和其他继承者的所有方法中转换为TExItems。

    TItems = class;
    
    TMyClass = class(TComponent)
    private
       FItems: TItems;
       procedure SetItems(const Value: TItems); virtual;
    protected
      property Items: TItems read FItems write SetItems;
    end;
    
    TExItems = class(TItems)
    private
      FNewProb: Integer;
    protected
    
    public
    
    published
      property NewProp: Integer read FNewProb write FNewProb;
    end;
    
    TExMyClass = class(TMyClass)
    private
       procedure SetItems(const Value: TItems); override;
    end;
    

    【讨论】:

    • 感谢您的出色回复,但在 TExMyClass 类中,Setter 过程“SetItems”从 TItems 获取值...我希望它获取 TExItems 吗?当我将其(值参数)类型从 TItems 更改为 TExItems 时,编译器给了我以下错误:[DCC Error] xxxxxx.pas(34): E2037 Declaration of 'SetItems' different from previous declaration 如何解决此错误?跨度>
    • 就像我说的,在继承时更改属性类型是不允许的。在设置和使用Items as TExItems 时检查类型应该可以工作。
    • 一个很好的解决方案,但是如果父类在子类以外的单元中,则虚拟属性和覆盖必须处于保护状态。
    【解决方案5】:

    属性不能是虚拟的,因此不能被覆盖。它们可以隐藏,因为在TExMyClass 的上下文中,对Items 的引用将解析为该类中声明的属性,而不是祖先中声明的属性。

    如果您有静态(声明的,编译时)类型为 TMyClass 的东西,Items 将始终引用该类中的那个,即使它的运行时类型是 TExMyClass

    您可以在基类中将 SetItems 声明为 protected 和 virtual,然后在后代中覆盖它,而不是声明一个恰好具有相同名称的全新属性。

    【讨论】:

      最近更新 更多