"无法为未命名的组件创建方法"

LaK*_*ven 8 delphi events properties editor

以下代码(在包中注册时)为我们提供了一个TParentComponent在托盘中注册的组件Test.

但是,使用属性编辑器(在相同代码中提供)创建Child对象时,IDE将显示错误消息" 无法为未命名的组件创建方法".

奇怪的是,Child对象确实有一个名字.

这是来源:

unit TestEditorUnit;

interface

uses
  Classes, DesignEditors, DesignIntf;

type  
  TParentComponent = class;

  TChildComponent = class(TComponent)
  private
    FParent: TParentComponent;
    FOnTest: TNotifyEvent;
    procedure SetParent(const Value: TParentComponent);
  protected
    procedure SetParentComponent(AParent: TComponent); override;
  public
    destructor Destroy; override;
    function GetParentComponent: TComponent; override;
    function HasParent: Boolean; override;
    property Parent: TParentComponent read FParent write SetParent;
  published
    property OnTest: TNotifyEvent read FOnTest write FOnTest;
  end;

  TParentComponent = class(TComponent)
  private
    FChilds: TList;
  protected
    procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    property Childs: TList read FChilds;
  end;

  TParentPropertyEditor = class(TPropertyEditor)
  public
    function GetAttributes: TPropertyAttributes; override;
    function GetValue: string; override;
    procedure Edit; override;
  end;

procedure Register;

implementation

uses
  ColnEdit;

type
  TChildComponentCollectionItem = class(TCollectionItem)
  private
    FChildComponent: TChildComponent;
    function GetName: string;
    function GetOnTest: TNotifyEvent;
    procedure SetName(const Value: string);
    procedure SetOnTest(const Value: TNotifyEvent);
  protected
    property ChildComponent: TChildComponent read FChildComponent write FChildComponent;
    function GetDisplayName: string; override;
  public
    constructor Create(Collection: TCollection); override;
    destructor Destroy; override;
  published
    property Name: string read GetName write SetName;
    property OnTest: TNotifyEvent read GetOnTest write SetOnTest;
  end;

  TChildComponentCollection = class(TOwnedCollection)
  private
    FDesigner: IDesigner;
  public
    property Designer: IDesigner read FDesigner write FDesigner;
  end;

procedure Register;
begin
  RegisterClass(TChildComponent);
  RegisterNoIcon([TChildComponent]);
  RegisterComponents('Test', [TParentComponent]);
  RegisterPropertyEditor(TypeInfo(TList), TParentComponent, 'Childs', TParentPropertyEditor);
end;

{ TChildComponent }

destructor TChildComponent.Destroy;
begin
  Parent := nil;
  inherited;
end;

function TChildComponent.GetParentComponent: TComponent;
begin
  Result := FParent;
end;

function TChildComponent.HasParent: Boolean;
begin
  Result := Assigned(FParent);
end;

procedure TChildComponent.SetParent(const Value: TParentComponent);
begin
  if FParent <> Value then
  begin
    if Assigned(FParent) then
      FParent.FChilds.Remove(Self);
    FParent := Value;
    if Assigned(FParent) then
      FParent.FChilds.Add(Self);
  end;
end;

procedure TChildComponent.SetParentComponent(AParent: TComponent);
begin
  if AParent is TParentComponent then
    SetParent(AParent as TParentComponent);
end;

{ TParentComponent }

constructor TParentComponent.Create(AOwner: TComponent);
begin
  inherited;
  FChilds := TList.Create;
end;

destructor TParentComponent.Destroy;
var
  I: Integer;
begin
  for I := 0 to FChilds.Count - 1 do
    TComponent(FChilds[0]).Free;
  FChilds.Free;
  inherited;
end;

procedure TParentComponent.GetChildren(Proc: TGetChildProc; Root: TComponent);
var
  i: Integer;
begin
  for i := 0 to FChilds.Count - 1 do
    Proc(TComponent(FChilds[i]));
end;

{ TChildComponentCollectionItem }

constructor TChildComponentCollectionItem.Create(Collection: TCollection);
begin
  inherited;
  if Assigned(Collection) then
  begin
    FChildComponent := TChildComponent.Create(TComponent(TOwnedCollection(Collection).Owner).Owner);
    FChildComponent.Name := TChildComponentCollection(Collection).Designer.UniqueName(TChildComponent.ClassName);
    FChildComponent.Parent := TParentComponent(TComponent(TOwnedCollection(Collection).Owner));
  end;
end;

destructor TChildComponentCollectionItem.Destroy;
begin
  FChildComponent.Free;
  inherited;
end;

function TChildComponentCollectionItem.GetDisplayName: string;
begin
  Result := FChildComponent.Name;
end;

function TChildComponentCollectionItem.GetName: string;
begin
  Result := FChildComponent.Name;
end;

function TChildComponentCollectionItem.GetOnTest: TNotifyEvent;
begin
  Result := FChildComponent.OnTest;
end;

procedure TChildComponentCollectionItem.SetName(const Value: string);
begin
  FChildComponent.Name := Value;
end;

procedure TChildComponentCollectionItem.SetOnTest(const Value: TNotifyEvent);
begin
  FChildComponent.OnTest := Value;
end;

{ TParentPropertyEditor }

procedure TParentPropertyEditor.Edit;
var
  LCollection: TChildComponentCollection;
  i: Integer;
begin
  LCollection := TChildComponentCollection.Create(GetComponent(0), TChildComponentCollectionItem);
  LCollection.Designer := Designer;
  for i := 0 to TParentComponent(GetComponent(0)).Childs.Count - 1 do
    with TChildComponentCollectionItem.Create(nil) do
    begin
      ChildComponent := TChildComponent(TParentComponent(GetComponent(0)).Childs[i]);
      Collection := LCollection;
    end;
  ShowCollectionEditorClass(Designer, TCollectionEditor, TComponent(GetComponent(0)), LCollection, 'Childs');
end;

function TParentPropertyEditor.GetAttributes: TPropertyAttributes;
begin
  Result := [paDialog];
end;

function TParentPropertyEditor.GetValue: string;
begin
  Result := 'Childs';
end;

end.
Run Code Online (Sandbox Code Playgroud)

上面的源代码来自StackOverflow上的另一个答案.

任何想法为什么我不能创建一个方法OnTest

提前致谢!

NGL*_*GLN 6

设计时间要求摘要

  • 您想要或需要一个能够容纳多个子组件的自定义组件.
  • 这些子组件将由该自定义组件创建.
  • 子组件需要能够在代码中通过其名称引用为设计时间的任何正常组件; 因此不是Form.CustomComponent.Children[0],Form.Child1而是.
  • 因此,子组件需要声明 - 并因此添加到 - 模块的源文件(Form,Frame或DataModule).
  • 子组件将由默认IDE集合编辑器管理.
  • 因此,孩子需要完全被包裹成一个TCollectionItem.

评估当前代码

你已经很顺利了,但除了你的问题之外,代码还有一些需要改进的地方:

  • 您创建的集合永远不会被释放.
  • 每次显示集合编辑器时都会创建一个新集合.
  • 如果从TreeView中删除子项,则旧的相应CollectionItem将保留,从而生成AV.
  • 设计时间和运行时代码不分开.

这是您的代码的重写工作版本,具有以下更改:

  • 调用特殊组件Master,因为Parent与Delphi混淆太多Parent(已经有两种).因此,孩子被召唤Slave.
  • 从站被保存在TComponentList(单元Contnrs)中,以便在单个从站被破坏时自动更新列表.ComponentList拥有从属.
  • 对于每个Master,只创建一个Collection.这些Master-Collection组合保存在单独的TStockItemsObjectList中.列表拥有库存项目,列表将在"完成"部分中释放.
  • GetNamePath实现,以便Slave1在Object Inspector中显示slave ,而不是as SlaveWrappers(0).
  • 为TSlaveWrapper类的事件添加了额外的属性编辑器.某种程度上GetFormMethodName,默认会TMethodProperty导致您获得错误.原因很明显Designer.GetObjectName,但我不确切知道为什么.现在GetFormMethodName是覆盖,它解决了你的问题的问题.

备注

按集合中项目的顺序(使用集合编辑器的箭头按钮)所做的更改尚未保留.试着让自己实现.

在TreeView中,每个Slave现在都是Master的直接子项,而不是Slaves属性的子项,正如通常在集合中看到的那样:

在此输入图像描述

为此,我认为TSlaves应该来自TPersistent,并且ComponentList将被包装在其中.这肯定是另一个不错的尝试.

组件代码

unit MasterSlave;

interface

uses
  Classes, Contnrs;

type
  TMaster = class;

  TSlave = class(TComponent)
  private
    FMaster: TMaster;
    FOnTest: TNotifyEvent;
    procedure SetMaster(Value: TMaster);
  protected
    procedure SetParentComponent(AParent: TComponent); override;
  public
    function GetParentComponent: TComponent; override;
    function HasParent: Boolean; override;
    property Master: TMaster read FMaster write SetMaster;
  published
    property OnTest: TNotifyEvent read FOnTest write FOnTest;
  end;

  TSlaves = class(TComponentList)
  private
    function GetItem(Index: Integer): TSlave;
    procedure SetItem(Index: Integer; Value: TSlave);
  public
    property Items[Index: Integer]: TSlave read GetItem write SetItem; default;
  end;

  TMaster = class(TComponent)
  private
    FSlaves: TSlaves;
  protected
    procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    property Slaves: TSlaves read FSlaves;
  end;

implementation

{ TSlave }

function TSlave.GetParentComponent: TComponent;
begin
  Result := FMaster;
end;

function TSlave.HasParent: Boolean;
begin
  Result := FMaster <> nil;
end;

procedure TSlave.SetMaster(Value: TMaster);
begin
  if FMaster <> Value then
  begin
    if FMaster <> nil then
      FMaster.FSlaves.Remove(Self);
    FMaster := Value;
    if FMaster <> nil then
      FMaster.FSlaves.Add(Self);
  end;
end;

procedure TSlave.SetParentComponent(AParent: TComponent);
begin
  if AParent is TMaster then
    SetMaster(TMaster(AParent));
end;

{ TSlaves }

function TSlaves.GetItem(Index: Integer): TSlave;
begin
  Result := TSlave(inherited Items[Index]);
end;

procedure TSlaves.SetItem(Index: Integer; Value: TSlave);
begin
  inherited Items[Index] := Value;
end;

{ TMaster }

constructor TMaster.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FSlaves := TSlaves.Create(True);
end;

destructor TMaster.Destroy;
begin
  FSlaves.Free;
  inherited Destroy;
end;

procedure TMaster.GetChildren(Proc: TGetChildProc; Root: TComponent);
var
  I: Integer;
begin
  for I := 0 to FSlaves.Count - 1 do
    Proc(FSlaves[I]);
end;

end.
Run Code Online (Sandbox Code Playgroud)

编辑代码

unit MasterSlaveEdit;

interface

uses
  Classes, SysUtils, MasterSlave, Contnrs, DesignEditors, DesignIntf, ColnEdit;

type
  TMasterEditor = class(TComponentEditor)
  private
    function Master: TMaster;
  public
    procedure ExecuteVerb(Index: Integer); override;
    function GetVerb(Index: Integer): String; override;
    function GetVerbCount: Integer; override;
  end;

  TMasterProperty = class(TPropertyEditor)
  private
    function Master: TMaster;
  public
    procedure Edit; override;
    function GetAttributes: TPropertyAttributes; override;
    function GetValue: String; override;
  end;

  TOnTestProperty = class(TMethodProperty)
  private
    function Slave: TSlave;
  public
    function GetFormMethodName: String; override;
  end;

  TSlaveWrapper = class(TCollectionItem)
  private
    FSlave: TSlave;
    function GetName: String;
    function GetOnTest: TNotifyEvent;
    procedure SetName(const Value: String);
    procedure SetOnTest(Value: TNotifyEvent);
  protected
    function GetDisplayName: String; override;
  public
    constructor Create(Collection: TCollection); override;
    constructor CreateSlave(Collection: TCollection; ASlave: TSlave);
    destructor Destroy; override;
    function GetNamePath: String; override;
  published
    property Name: String read GetName write SetName;
    property OnTest: TNotifyEvent read GetOnTest write SetOnTest;
  end;

  TSlaveWrappers = class(TOwnedCollection)
  private
    function GetItem(Index: Integer): TSlaveWrapper;
  public
    property Items[Index: Integer]: TSlaveWrapper read GetItem; default;
  end;

implementation

type
  TStockItem = class(TComponent)
  protected
    Collection: TSlaveWrappers;
    Designer: IDesigner;
    Master: TMaster;
    procedure Notification(AComponent: TComponent; Operation: TOperation);
      override;
  public
    destructor Destroy; override;
  end;

  TStockItems = class(TObjectList)
  private
    function GetItem(Index: Integer): TStockItem;
  protected
    function CollectionOf(AMaster: TMaster; Designer: IDesigner): TCollection;
    function Find(ACollection: TCollection): TStockItem;
    property Items[Index: Integer]: TStockItem read GetItem;
      default;
  end;

var
  FStock: TStockItems = nil;

function Stock: TStockItems;
begin
  if FStock = nil then
    FStock := TStockItems.Create(True);
  Result := FStock;
end;

{ TStockItem }

destructor TStockItem.Destroy;
begin
  Collection.Free;
  inherited Destroy;
end;

procedure TStockItem.Notification(AComponent: TComponent;
  Operation: TOperation);
var
  I: Integer;
begin
  inherited Notification(AComponent, Operation);
  if Operation = opRemove then
    for I := 0 to Collection.Count - 1 do
      if Collection[I].FSlave = AComponent then
      begin
        Collection[I].FSlave := nil;
        Collection.Delete(I);
        Break;
      end;
end;

{ TStockItems }

function TStockItems.CollectionOf(AMaster: TMaster;
  Designer: IDesigner): TCollection;
var
  I: Integer;
  Item: TStockItem;
begin
  Result := nil;
  for I := 0 to Count - 1 do
    if Items[I].Master = AMaster then
    begin
      Result := Items[I].Collection;
      Break;
    end;
  if Result = nil then
  begin
    Item := TStockItem.Create(nil);
    Item.Master := AMaster;
    Item.Designer := Designer;
    Item.Collection := TSlaveWrappers.Create(AMaster, TSlaveWrapper);
    for I := 0 to AMaster.Slaves.Count - 1 do
    begin
      TSlaveWrapper.CreateSlave(Item.Collection, AMaster.Slaves[I]);
      Item.FreeNotification(AMaster.Slaves[I]);
    end;
    Add(Item);
    Result := Item.Collection;
  end;
end;

function TStockItems.GetItem(Index: Integer): TStockItem;
begin
  Result := TStockItem(inherited Items[Index]);
end;

function TStockItems.Find(ACollection: TCollection): TStockItem;
var
  I: Integer;
begin
  Result := nil;
  for I := 0 to Count - 1 do
    if Items[I].Collection = ACollection then
    begin
      Result := Items[I];
      Break;
    end;
end;

{ TMasterEditor }

procedure TMasterEditor.ExecuteVerb(Index: Integer);
begin
  case Index of
    0: ShowCollectionEditor(Designer, Master,
      Stock.CollectionOf(Master, Designer), 'Slaves');
  end;
end;

function TMasterEditor.GetVerb(Index: Integer): String;
begin
  case Index of
    0: Result := 'Edit slaves...';
  else
    Result := '';
  end;
end;

function TMasterEditor.GetVerbCount: Integer;
begin
  Result := 1;
end;

function TMasterEditor.Master: TMaster;
begin
  Result := TMaster(Component);
end;

{ TMasterProperty }

procedure TMasterProperty.Edit;
begin
  ShowCollectionEditor(Designer, Master,
    Stock.CollectionOf(Master, Designer), 'Slaves');
end;

function TMasterProperty.GetAttributes: TPropertyAttributes;
begin
  Result := [paDialog];
end;

function TMasterProperty.GetValue: String;
begin
  Result := Format('(%s)', [Master.Slaves.ClassName]);
end;

function TMasterProperty.Master: TMaster;
begin
  Result := TMaster(GetComponent(0));
end;

{ TOnTestProperty }

function TOnTestProperty.GetFormMethodName: String;
begin
  Result := Slave.Name + GetTrimmedEventName;
end;

function TOnTestProperty.Slave: TSlave;
begin
  Result := TSlaveWrapper(GetComponent(0)).FSlave;
end;

{ TSlaveWrapper }

constructor TSlaveWrapper.Create(Collection: TCollection);
begin
  CreateSlave(Collection, nil);
end;

constructor TSlaveWrapper.CreateSlave(Collection: TCollection; ASlave: TSlave);
var
  Item: TStockItem;
begin
  inherited Create(Collection);
  if ASlave = nil then
  begin
    Item := Stock.Find(Collection);
    FSlave := TSlave.Create(Item.Master.Owner);
    FSlave.Name := Item.Designer.UniqueName(TSlave.ClassName);
    FSlave.Master := Item.Master;
    FSlave.FreeNotification(Item);
  end
  else
    FSlave := ASlave;
end;

destructor TSlaveWrapper.Destroy;
begin
  FSlave.Free;
  inherited Destroy;
end;

function TSlaveWrapper.GetDisplayName: String;
begin
  Result := Name;
end;

function TSlaveWrapper.GetName: String;
begin
  Result := FSlave.Name;
end;

function TSlaveWrapper.GetNamePath: String;
begin
  Result := FSlave.GetNamePath;
end;

function TSlaveWrapper.GetOnTest: TNotifyEvent;
begin
  Result := FSlave.OnTest;
end;

procedure TSlaveWrapper.SetName(const Value: String);
begin
  FSlave.Name := Value;
end;

procedure TSlaveWrapper.SetOnTest(Value: TNotifyEvent);
begin
  FSlave.OnTest := Value;
end;

{ TSlaveWrappers }

function TSlaveWrappers.GetItem(Index: Integer): TSlaveWrapper;
begin
  Result := TSlaveWrapper(inherited Items[Index]);
end;

initialization

finalization
  FStock.Free;

end.
Run Code Online (Sandbox Code Playgroud)

注册码

unit MasterSlaveReg;

interface

uses
  Classes, MasterSlave, MasterSlaveEdit, DesignIntf;

procedure Register;

implementation

procedure Register;
begin
  RegisterClass(TSlave);
  RegisterNoIcon([TSlave]);
  RegisterComponents('Samples', [TMaster]);
  RegisterComponentEditor(TMaster, TMasterEditor);
  RegisterPropertyEditor(TypeInfo(TSlaves), TMaster, 'Slaves',
    TMasterProperty);
  RegisterPropertyEditor(TypeInfo(TNotifyEvent), TSlaveWrapper, 'OnTest',
    TOnTestProperty);
end;

end.
Run Code Online (Sandbox Code Playgroud)

包裹代码

requires
  rtl,
  DesignIDE;

contains
  MasterSlave in 'MasterSlave.pas',
  MasterSlaveEdit in 'MasterSlaveEdit.pas',
  MasterSlaveReg in 'MasterSlaveReg.pas';
Run Code Online (Sandbox Code Playgroud)