但是,使用属性编辑器(在相同代码中提供)创建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.
上述来源改编自another answer here on StackOverflow.
任何想法为什么我不能为OnTest创建一个方法?
提前致谢!
解决方法
>您想要或需要一个能够容纳多个子组件的自定义组件.
>这些子组件将由该自定义组件创建.
>子组件需要能够在代码中通过其名称引用为设计时的任何正常组件;因此不是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组合保存在单独的TStockItems ObjectList中.列表拥有库存项目,并在“完成”部分中释放列表.
>实现GetNamePath,以便在Object Inspector中将slave显示为Slave1,而不是SlaveWrappers(0).
>为TSlaveWrapper类的事件添加了额外的属性编辑器.不知何故,默认TMethodProperty的GetFormMethodName会导致您获得的错误.原因将在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,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,'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,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';