【问题标题】:"Cannot create a method for an unnamed component"“无法为未命名的组件创建方法”
【发布时间】:2013-05-05 17:54:56
【问题描述】:

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

但是,当您使用属性编辑器(在相同代码中提供)创建子对象时,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.

以上来源改编自another answer here on StackOverflow

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

提前致谢!

【问题讨论】:

    标签: delphi events properties editor


    【解决方案1】:

    设计时间要求总结

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

    当前代码的评估

    你已经做得很好了,但是除了你的问题之外,代码还有几点需要改进:

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

    解决方案

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

    • 特殊组件被称为Master,因为Parent与Delphi 的Parent 混淆太多(已经有两种)。因此,孩子被称为Slave
    • 奴隶被保存在TComponentList(单位Contnrs)中,以在个别奴隶被破坏的情况下自动更新列表。 ComponentList 拥有从属服务器。
    • 对于每个 Master,只会创建一个 Collection。这些主集合组合保存在单独的TStockItems ObjectList 中。 List 拥有库存项目,并且列表在 Finalization 部分中被释放。
    • 实现了GetNamePath,以便从属设备在对象检查器中显示为Slave1,而不是SlaveWrappers(0)
    • 为 TSlaveWrapper 类的事件添加了一个额外的属性编辑器。不知何故,默认TMethodPropertyGetFormMethodName 会导致您遇到错误。原因在于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.
    

    编辑器代码

    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.
    

    注册码

    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.
    

    封装代码

    requires
      rtl,
      DesignIDE;
    
    contains
      MasterSlave in 'MasterSlave.pas',
      MasterSlaveEdit in 'MasterSlaveEdit.pas',
      MasterSlaveReg in 'MasterSlaveReg.pas';
    

    【讨论】:

    • @NGLN,你是如何强制它为 Slave1、Slave2... 生成成员变量的?如果我有具有集合属性的组件,我怎样才能使它具有集合项的成员变量?
    • @Nedko 注意TSlaves 不是一个集合,它只是一个列表。适当的集合是在设计时按需制作的。如果您在运行时需要该集合,则合并上面的运行时和设计时代码。请注意,您将拥有两个从属属性,即集合和列表,它们的名称不能相同。
    • @NGLN 我的意思是设计时间——当我在 IDE 集合编辑器中创建新集合项时,我希望它为该新集合项创建成员 var。
    • @Nedko 那么这个答案正好回答了你的问题。
    【解决方案2】:

    在 About.com 的 "Creating Custom Delphi Components, Part 2, Page 4 of 5" 文章中找到了足够的“解决方法”。

    完整的示例源代码在他们的文章中,并且(似乎)适用于所有版本的 Delphi。

    然而,应该注意的是,这个解决方案并不是完美,因为它不允许您将集合编辑器与父组件和子组件分开(这意味着您必须为这两个组件使集合编辑器能够工作,并将其放置在您的运行时包中)。

    对于我现在的需求,这将是可行的......但如果有人可以直接根据我的问题中发布的示例代码找到更好的解决方案,那就太好了(我会将该答案标记为正确应该任何人提供它)。

    【讨论】:

      猜你喜欢
      • 2011-02-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多