delphi - 如何在复合组件中发布子组件的属性?

标签 delphi components

在派生自 TPanel 的复合组件中,我试图发布一个属性,其唯一目的是设置和获取子组件的链接属性。每次我将复合组件添加到表单时,都会引发访问冲突:

Access violation at address 12612D86 in module 'MyRuntimePackage.bpl'. Read of address 00000080.

我已经准备了一个使用 TLabel 及其 PopupMenu 属性的简化示例,但在将复合组件放在表单/框架上时我仍然遇到同样的问题。

运行时包:

uses
  StdCtrls, Menus, ExtCtrls, Classes;

type
  TTestCompoundComponent = class(TPanel)
  private
    FSubCmp : TLabel;
    function    GetLabelPopupMenu() : TPopupMenu;
    procedure   SetLabelPopupMenu(AValue : TPopupMenu);
  protected
    procedure   Notification(AComponent: TComponent; Operation: TOperation); override;
  public
    constructor Create(AOwner : TComponent); override;
    destructor  Destroy(); override;
  published
    property    LabelPopupMenu : TPopupMenu read GetLabelPopupMenu write SetLabelPopupMenu;
  end;

...

function    TTestCompoundComponent.GetLabelPopupMenu() : TPopupMenu;
begin
  Result := FSubCmp.PopupMenu;
end;

procedure   TTestCompoundComponent.SetLabelPopupMenu(AValue : TPopupMenu);
begin
  if(GetLabelPopupMenu() <> AValue) then
  begin
    if(GetLabelPopupMenu() <> nil)
    then GetLabelPopupMenu().RemoveFreeNotification(Self);

    FSubCmp.PopupMenu := AValue;

    if(GetLabelPopupMenu() <> nil)
    then GetLabelPopupMenu().FreeNotification(Self);
  end;
end;

procedure   TTestCompoundComponent.Notification(AComponent: TComponent; Operation: TOperation);
begin      
  inherited;
  if((AComponent = GetLabelPopupMenu()) AND (Operation = opRemove))
  then SetLabelPopupMenu(nil);
end;

constructor TTestCompoundComponent.Create(AOwner : TComponent);
begin
  inherited;
  FSubCmp := TLabel.Create(nil);
  FSubCmp.Parent := Self;
end;

destructor TTestCompoundComponent.Destroy();
begin
  FSubCmp.Free;
  inherited;
end;

设计时包:

procedure Register;
begin
  RegisterComponents('MyTestCompoundComponent', [TTestCompoundComponent]);
end;

最佳答案

@kobik 的回答解释了 AV 的根本原因(在创建 FSubCmp 之前访问 FSubCmp.PopupMenu 属性)。但是,对于您要实现的目标而言,您的整个组件代码过于复杂。

您应该将您的组件设置为 TLabelOwner,然后您可以完全删除析构函数。并且您还应该在构造函数中调用 FSubCmp.SetSubComponent(True)(特别是如果您打算稍后在对象检查器中公开 TLabel,因此用户可以在设计时自定义其属性):

constructor TTestCompoundComponent.Create(AOwner : TComponent);
begin
  inherited;
  FSubCmp := TLabel.Create(Self);
  FSubCmp.SetSubComponent(True);
  FSubCmp.Parent := Self;
end;

您的Notification() 方法应该设置FSubCmp.PopupMenu := nil 直接响应opRemove,而不是调用设置标签弹出菜单(无)。您已经知道 PopupMenu 已分配并且它正在被销毁,因此检索 PopupMenu 的额外代码(重复)检查它是否为 nil ,并调用 RemoveFreeNotification(),对于 opRemove 操作来说都是矫枉过正:

procedure TTestCompoundComponent.Notification(AComponent: TComponent; Operation: TOperation);
begin      
  inherited;
  if (Operation = opRemove) and (AComponent = LabelPopupMenu) then
    FSubCmp.PopupMenu := nil;
end;

而您的 SetLabelPopupMenu() 方法通常只是一个碍眼的地方,所有那些对 GetLabelPopupMenu() 的冗余调用。只调用一次并将返回的对象指针存储到一个局部变量,然后您可以根据需要使用该变量:

procedure TTestCompoundComponent.SetLabelPopupMenu(AValue: TPopupMenu);
var
  PM: TPopupMenu;
begin
  PM := LabelPopupMenu;

  if (PM <> AValue) then
  begin
    if (PM <> nil) then
      PM.RemoveFreeNotification(Self);

    FSubCmp.PopupMenu := AValue;

    if (AValue <> nil) then
      AValue.FreeNotification(Self);
  end;
end;

但是,您的 Notification() 方法实际上完全多余,应该完全删除。 TLabel 已经在它自己的 PopupMenu 属性上调用了 FreeNotification(),并且有它自己的 Notification() 实现如果 TPopupMenu 对象被释放,则将 PopupMenu 属性设置为 nil。您根本不需要手动处理。因此,SetLabelPopupMenu() 中的所有额外代码都是多余的,应该删除:

procedure TTestCompoundComponent.SetLabelPopupMenu(AValue: TPopupMenu);
begin
  FSubCmp.PopupMenu := AValue;
end;

这也意味着@kobik 提出的修复是多余的,也可以删除1:

function TTestCompoundComponent.GetLabelPopupMenu: TPopupMenu;
begin
  Result := FSubCmp.PopupMenu;
end;

1:除非你想处理用户决定直接释放你的 TLabel 的情况(这是愚蠢的,实际上没有人会真正这样做,但在技术上仍然可行),那么您将需要 Notification() 来处理这种情况(将您的组件指定为 TLabelOwner 将为您调用 FreeNotificatio():

function TTestCompoundComponent.Notification(AComponent: TComponent; Opration: TOperation);
begin
  inherited;
  if (Operation = opRemove) and (AComponent = FSubCmp) then
    FSubCmp := nil;
end;

function TTestCompoundComponent.GetLabelPopupMenu: TPopupMenu;
begin
  if FSubCmp <> nil then
    Result := FSubCmp.PopupMenu
  else
    Result := nil;
end;

话虽如此,这里是您的代码的简化版本:

uses
  StdCtrls, Menus, ExtCtrls, Classes;

type
  TTestCompoundComponent = class(TPanel)
  private
    FSubCmp: TLabel;
    function GetLabelPopupMenu: TPopupMenu;
    procedure SetLabelPopupMenu(AValue: TPopupMenu);
  public
    constructor Create(AOwner: TComponent); override;
  published
    property LabelPopupMenu: TPopupMenu read GetLabelPopupMenu write SetLabelPopupMenu;
  end;

...

constructor TTestCompoundComponent.Create(AOwner : TComponent);
begin
  inherited;
  FSubCmp := TLabel.Create(Self);
  FSubCmp.SetSubComponent(True);
  FSubCmp.Parent := Self;
end;

function TTestCompoundComponent.GetLabelPopupMenu: TPopupMenu;
begin
  Result := FSubCmp.PopupMenu;
end;

procedure TTestCompoundComponent.SetLabelPopupMenu(AValue: TPopupMenu);
begin
  FSubCmp.PopupMenu := AValue;
end;

甚至只是这样:

uses
  StdCtrls, Menus, ExtCtrls, Classes;

type
  TTestCompoundComponent = class(TPanel)
  private
    FSubCmp: TLabel;
  public
    constructor Create(AOwner: TComponent); override;
  published
    property SubLabel: TLabel read FSubCmp;
  end;

...

constructor TTestCompoundComponent.Create(AOwner : TComponent);
begin
  inherited;
  FSubCmp := TLabel.Create(Self);
  FSubCmp.SetSubComponent(True);
  FSubCmp.Parent := Self;
end;

关于delphi - 如何在复合组件中发布子组件的属性?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43915319/

相关文章:

delphi - 从单位中删除表格声明是否安全?

delphi - 如何在DLL(Delphi/TJVPluginManager + TJvPlugin)中实现回调方法

delphi - GIF动画TImage/Timage32

Java 组件最大尺寸

angularjs - Angular 1.5 $onInit 未触发 - typescript

javascript - 如何将此类基础高阶组件更改为功能组件?

node.js - 如何安装最新的ionic2组件

delphi - 在C++ Builder 2010中使用软件包时,如何解决“加载了两个不同的CRTLDLL”的问题?

delphi - 以分钟/秒为单位获取wav音频的长度

architecture - 此图是有效的 UML 组件图吗?