I created my own Component : TPage , which Contains Subcomponent TPaper (TPanel).
The problem is, that when I put controls such as TMemo or TButton on the TPaper (which fills up nearly whole area), the controls do not load at all. see example below
TPaper = class(TPanel)
protected
constructor Create(AOwner: TComponent);override;
destructor Destroy;override;
public
procedure Paint; override;
end;
TPage = class(TCustomControl)
private
FPaper:TPaper;
protected
procedure CreateParams(var Params:TCreateParams); override;
public
constructor Create(AOwner: TComponent);override;
destructor Destroy;override;
published
property Paper: TPaper read FPaper write FPaper;
end;
constructor TPage.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
PaperOrientation:=poPortrait;
PaperSize:=psA4;
PaperBrush:=TBrush.Create;
PaperBrush.Color:=clWhite;
PDFDocument:=Nil;
FPaper:=TPaper.Create(Self);
FPaper.Parent:=Self;
FPaper.SetSubComponent(True);
end;
...
Memo1 is parented in TPaper (TPanel) at design-time, but after
pressing "Run" it does not exist.
procedure TForm1.btn1Click(Sender: TObject);
begin
if not Assigned(Memo1) then ShowMessage('I do not exist'); //Memo1 is nil
end;
Have you any idea what's wrong?
Thanks a lot
P.S Delphi 7
When I put TMemo inside TPaper and save the unit (Unit1), after inspection of associated dfm file, there is no trace of TMemo component. (Thats why it can not load to app.)
Serge is right. Delphi only streams components that are owned by the Form they reside in. In order to avoid the EClassNotfound Exception, which occurs during reading of the form file (You should now at least see a Tpaper component in your dfm file) you must register the class by using the RegisterClass function (in the unit Classes). A good place for this would be in the initialisation section of your unit.
If setting the owner of Tpaper to a Form is not an option, then you can still get Delphi to stream your subcomponents by overriding the Getchildren and GetChildOwner methods and applying the logic TCustomForm uses:
TPage = class
...
public
procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override;
function GetChildOwner:TComponent; override;
end;
procedure TPage.GetChildren(Proc: TGetChildProc; Root: TComponent); // this is copied
var // from
I: Integer; // TCustomForm
OwnedComponent: TComponent;
begin
inherited GetChildren(Proc, Root);
if Root = Self then
for I := 0 to ComponentCount - 1 do
begin
OwnedComponent := Components[I];
if not OwnedComponent.HasParent then Proc(OwnedComponent);
end;
end;
function TPage.GetChildOwner: TComponent;
begin
inherited;
Result:=Self;
end;
The question is 5 years ago, but because I came across the same problem and could not find a workable solution in the network decided to share what I found as a solution after much testing.
TClientPanel = class(TCustomControl)
private
procedure WMNCHitTest(var Message: TWMNCHitTest); message WM_NCHITTEST;
public
constructor Create(AOwner: TComponent); override;
end;
TMainPanel = class(TCustomControl)
private
FClient: TClientPanel;
protected
function GetChildOwner: TComponent; override;
procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override;
procedure ReadState(Reader: TReader); override;
procedure CreateComponentEvent(Reader: TReader; ComponentClass: TComponentClass; var Component: TComponent);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
...
end;
constructor TClientPanel.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
ControlStyle := ControlStyle + [csAcceptsControls, csNoDesignVisible];
end;
procedure TClientPanel.WMNCHitTest(var Message: TWMNCHitTest);
begin
if not (csDesigning in ComponentState) then
Message.Result := HTTRANSPARENT
else
inherited;
end;
var
TClientPanel_Registered: Boolean = False;
constructor TMainPanel.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FClient := TClientPanel.Create(Self);
FClient.Parent := Self;
FClient.Align := alClient;
Exclude(FComponentStyle, csInheritable);
if not TClientPanel_Registered then
begin
RegisterClasses([TClientPanel]);
TClientPanel_Registered := True;
end;
end;
destructor TMainPanel.Destroy;
begin
FClient.Free;
inherited Destroy;
end;
function TMainPanel.GetChildOwner: TComponent;
begin
Result := Self;
end;
procedure TMainPanel.GetChildren(Proc: TGetChildProc; Root: TComponent);
begin
Proc(TControl(FClient));
end;
procedure TMainPanel.CreateComponentEvent(Reader: TReader; ComponentClass: TComponentClass; var Component: TComponent);
begin
if ComponentClass.ClassName = 'TClientPanel' then Component := FClient;
end;
procedure TMainPanel.ReadState(Reader: TReader);
begin
Reader.OnCreateComponent := CreateComponentEvent;
inherited ReadState(Reader);
Reader.OnCreateComponent := nil;
end;
Not very professional, but I hope it will help :^)
P.S. just did a quick test (XE5), but basically works.
Related
I have written a Delphi component that has a property of type TStrings. All works well except that when the String List Editor is launched, the "Code Editor" button is disabled. Anyone know what I need to set to allow this?
Perhaps this is due to being called from the collection editor?
The entire component is is about 80 lines so I put it all here. It is a VCL component.
// Simple example of of creating a OwnedCollection of TStrings
unit TextStorageMin;
interface
uses
System.Classes, System.SysUtils, Winapi.Windows, System.Generics.Collections;
type
// Storage class to store TStrings
TStorageStrings = class(TCollectionItem)
private
FStrings: TStrings;
procedure SetStrings(const Value: TStrings);
public
published
constructor Create(Collection: TCollection); override;
destructor Destroy; override;
// Why, when this is brought up in the Strings List Editor, is
// the "Code Editor" not enabled.
property Strings: TStrings read FStrings write SetStrings;
end;
// Just simple Owned Collection
TStorageList = class(TOwnedCollection);
// This our component.
TTextStorageMin = class(TComponent)
private
FStorageList: TStorageList;
public
published
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
property Items: TStorageList read FStorageList write FStorageList;
end;
procedure Register;
implementation
// Register it
procedure Register;
begin
RegisterComponents('CompDev', [TTextStorageMin]);
end;
{ TTextStorage }
constructor TTextStorageMin.Create(AOwner: TComponent);
begin
inherited;
FStorageList := TStorageList.Create(AOwner, TStorageStrings);
end;
destructor TTextStorageMin.Destroy;
begin
FStorageList.Free;
inherited;
end;
{ TStorageStrings }
constructor TStorageStrings.Create(Collection: TCollection);
begin
inherited;
FStrings := TStringList.Create;
end;
destructor TStorageStrings.Destroy;
begin
FStrings.Free;
inherited;
end;
procedure TStorageStrings.SetStrings(const Value: TStrings);
begin
FStrings.Assign(Value);
end;
Your main component is coded all wrong. It is completely mismanaging the ownership of the TStorageList object. It is assigning the wrong Owner to the object, and there is no property setter implementee to avoid a memory leak and taking ownership of an external object (in this case, one created and destroyed by the IDE at design-time).
Also, your TStorageStrings class is missing an overload of Assign() (or AssignTo()), which also plays into the above mismanagement.
The code should look more like this instead:
// Simple example of of creating a OwnedCollection of TStrings
unit TextStorageMin;
interface
uses
System.Classes;
type
// Storage class to store TStrings
TStorageStrings = class(TCollectionItem)
private
FStrings: TStrings;
procedure SetStrings(const Value: TStrings);
public
constructor Create(Collection: TCollection); override;
destructor Destroy; override;
procedure Assign(ASource: TPersistent); override;
published
property Strings: TStrings read FStrings write SetStrings;
end;
// Just simple Owned Collection
TStorageList = class(TOwnedCollection);
// This our component.
TTextStorageMin = class(TComponent)
private
FStorageList: TStorageList;
procedure SetItems(const Value: TStorageList);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property Items: TStorageList read FStorageList write SetItems;
end;
procedure Register;
implementation
// Register it
procedure Register;
begin
RegisterComponents('CompDev', [TTextStorageMin]);
end;
{ TTextStorage }
constructor TTextStorageMin.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FStorageList := TStorageList.Create(Self, TStorageStrings);
end;
destructor TTextStorageMin.Destroy;
begin
FStorageList.Free;
inherited;
end;
procedure TTextStorageMin.SetItems(const Value: TStorageList);
begin
FStorageList.Assign(Value);
end;
{ TStorageStrings }
constructor TStorageStrings.Create(Collection: TCollection);
begin
inherited Create(Collection);
FStrings := TStringList.Create;
end;
destructor TStorageStrings.Destroy;
begin
FStrings.Free;
inherited;
end;
procedure TStorageStrings.Assign(ASource: TPersistent);
begin
if ASource is TStorageStrings then
FStrings.Assign(TStorageStrings(ASource).Strings)
else
inherited;
end;
procedure TStorageStrings.SetStrings(const Value: TStrings);
begin
FStrings.Assign(Value);
end;
end.
I am trying to do simple thing: Add a Canvas property on the TScrollBox descendant. I have read this article
but my ScrollBox descendant simply does not draw on the canvas. May anybody tell me, what is wrong?
TfrmScrollContainer = class (TScrollBox)
private
FCanvas: TCanvas;
FControlState:TControlState;
procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
protected
procedure Paint;
procedure PaintWindow(DC: HDC); override;
property Canvas: TCanvas read FCanvas;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
end;
... this is exact copy, how TWincontrol turns to TCustomControl (but it fails somewhere)
constructor TfrmScrollContainer.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FCanvas := TControlCanvas.Create;
TControlCanvas(FCanvas).Control := Self;
end;
destructor TfrmScrollContainer.Destroy;
begin
FCanvas.Free;
inherited Destroy;
end;
procedure TfrmScrollContainer.WMPaint(var Message: TWMPaint);
begin
Include(FControlState, csCustomPaint);
inherited;
Exclude(FControlState, csCustomPaint);
end;
procedure TfrmScrollContainer.PaintWindow(DC: HDC);
begin
FCanvas.Lock;
try
FCanvas.Handle := DC;
try
TControlCanvas(FCanvas).UpdateTextFlags;
Paint;
finally
FCanvas.Handle := 0;
end;
finally
FCanvas.Unlock;
end;
end;
procedure TfrmScrollContainer.Paint; // this is not executed (I do not see any ellipse)
begin
Canvas.Brush.Color:=clRed;
Canvas.Ellipse(ClientRect);
end;
Thanx
You are not including csCustomPaint to ControlState, you're including it to the similarly named field you declared. Your field is not checked, the ascendant control does not know anything about it. To solve, replace
procedure TfrmScrollContainer.WMPaint(var Message: TWMPaint);
begin
Include(FControlState, csCustomPaint);
inherited;
Exclude(FControlState, csCustomPaint);
end;
with
procedure TfrmScrollContainer.WMPaint(var Message: TWMPaint);
begin
ControlState := ControlState + [csCustomPaint];
inherited;
ControlState := ControlState - [csCustomPaint];
end;
Alternatively your scroll box may parent any control for your custom painting to work. The inherited WM_PAINT handler checks to see the control count and if it's not 0 it calls the paint handler instead of delivering the message to the default handler.
I'm hoping to create something like a "TOwnedStringList" (class name is a fiction) that I could construct as:
sl := TOwnedStringList.Create(Self);
sl.Sorted := True;
sl.Duplicates := dupIgnore;
sl.Add(...);
// etc...
Where Self could be a Form (for example), so that the Owner will auto free the StringList.
I want to be able to avoid calling sl.Free myself.
Is This possible?
That's going to be a little messy. You'd need to do something like this.
type
TOwnerComponent = class(TComponent)
private
FOwnedObject: TObject;
public
constructor Create(Owner: TComponent; OwnedObject: TObject);
destructor Destroy; override;
end;
TOwnedStringList = class(TStringList)
private
FOwner: TOwnerComponent;
public
constructor Create(Owner: TComponent);
destructor Destroy; override;
end;
{ TOwnerComponent }
constructor TOwnerComponent.Create(Owner: TComponent; OwnedObject: TObject);
begin
inherited Create(Owner);
FOwnedObject := OwnedObject;
end;
destructor TOwnerComponent.Destroy;
begin
FOwnedObject.Free;
inherited;
end;
{ TOwnedStringList }
constructor TOwnedStringList.Create(Owner: TComponent);
begin
inherited Create;
if Assigned(Owner) then
FOwner := TOwnerComponent.Create(Owner, Self);
end;
destructor TOwnedStringList.Destroy;
begin
if Assigned(FOwner) and not (csDestroying in FOwner.ComponentState) then
begin
FOwner.FOwnedObject := nil;
FOwner.Free;
end;
inherited;
end;
Basically you create an instance of TOwnerComponent that is owned by the Owner that you pass to TOwnedStringList.Create. When that Owner dies, it destroys the TOwnerComponent which in turn destroys your string list.
The code is resilient to an explicit Free being called on the string list.
I need to know the basics behind making a component produce and manage sub-components. I originally tried this by creating a TCollection, and tried to put a name on each TCollectionItem. But I learned it's not that easy as I had hoped.
So now I am going to start this project from scratch again, and I'd like to get it right this time. These sub-components are not visual components, and should not have any display or window, just based off of TComponent. The main component holding these sub-components will also be based off of TComponent. So nothing here is visual at all, and I don't want a little icon on my form (in design time) for each of these sub-components.
I would like to be able to maintain and manage these sub-components in a collection-like fashion. The important thing is that these sub-components should be created, named and added to the form source, just like menu items are for example. This is the whole point of the idea in the first place, if they cannot be named, then this whole idea is kaput.
Oh, another important thing: the main component being the parent of all the sub-components needs to be able to save these sub-components to the DFM file.
EXAMPLE:
Instead of accessing one of these sub items like:
MyForm.MyItems[1].DoSomething();
I would instead like to do something like:
MyForm.MyItem2.DoSomething();
So I do not have to rely on knowing the ID of each sub item.
EDIT:
I felt it a little necessary to include my original code so it can be seen how the original collection works. Here's just the server side collection and collection item stripped from the full unit:
// Command Collections
// Goal: Allow entering pre-set commands with unique Name and ID
// Each command has its own event which is triggered when command is received
// TODO: Name each collection item as a named component in owner form
//Determines how commands are displayed in collection editor in design-time
TJDCmdDisplay = (cdName, cdID, cdCaption, cdIDName, cdIDCaption);
TJDScktSvrCmdEvent = procedure(Sender: TObject; Socket: TJDServerClientSocket;
const Data: TStrings) of object;
TSvrCommands = class(TCollection)
private
fOwner: TPersistent;
fOnUnknownCommand: TJDScktSvrCmdEvent;
fDisplay: TJDCmdDisplay;
function GetItem(Index: Integer): TSvrCommand;
procedure SetItem(Index: Integer; Value: TSvrCommand);
procedure SetDisplay(const Value: TJDCmdDisplay);
protected
function GetOwner: TPersistent; override;
public
constructor Create(AOwner: TPersistent);
destructor Destroy;
procedure DoCommand(const Socket: TJDServerClientSocket;
const Cmd: Integer; const Data: TStrings);
function Add: TSvrCommand;
property Items[Index: Integer]: TSvrCommand read GetItem write SetItem;
published
property Display: TJDCmdDisplay read fDisplay write SetDisplay;
property OnUnknownCommand: TJDScktSvrCmdEvent
read fOnUnknownCommand write fOnUnknownCommand;
end;
TSvrCommand = class(TCollectionItem)
private
fID: Integer;
fOnCommand: TJDScktSvrCmdEvent;
fName: String;
fParamCount: Integer;
fCollection: TSvrCommands;
fCaption: String;
procedure SetID(Value: Integer);
procedure SetName(Value: String);
procedure SetCaption(const Value: String);
protected
function GetDisplayName: String; override;
public
procedure Assign(Source: TPersistent); override;
constructor Create(Collection: TCollection); override;
destructor Destroy; override;
published
property ID: Integer read fID write SetID;
property Name: String read fName write SetName;
property Caption: String read fCaption write SetCaption;
property ParamCount: Integer read fParamCount write fParamCount;
property OnCommand: TJDScktSvrCmdEvent read fOnCommand write fOnCommand;
end;
////////////////////////////////////////////////////////////////////////////////
implementation
////////////////////////////////////////////////////////////////////////////////
{ TSvrCommands }
function TSvrCommands.Add: TSvrCommand;
begin
Result:= inherited Add as TSvrCommand;
end;
constructor TSvrCommands.Create(AOwner: TPersistent);
begin
inherited Create(TSvrCommand);
Self.fOwner:= AOwner;
end;
destructor TSvrCommands.Destroy;
begin
inherited Destroy;
end;
procedure TSvrCommands.DoCommand(const Socket: TJDServerClientSocket;
const Cmd: Integer; const Data: TStrings);
var
X: Integer;
C: TSvrCommand;
F: Bool;
begin
F:= False;
for X:= 0 to Self.Count - 1 do begin
C:= GetItem(X);
if C.ID = Cmd then begin
F:= True;
try
if assigned(C.fOnCommand) then
C.fOnCommand(Self, Socket, Data);
except
on e: exception do begin
raise Exception.Create(
'Failed to execute command '+IntToStr(Cmd)+': '+#10+e.Message);
end;
end;
Break;
end;
end;
if not F then begin
//Command not found
end;
end;
function TSvrCommands.GetItem(Index: Integer): TSvrCommand;
begin
Result:= TSvrCommand(inherited GetItem(Index));
end;
function TSvrCommands.GetOwner: TPersistent;
begin
Result:= fOwner;
end;
procedure TSvrCommands.SetDisplay(const Value: TJDCmdDisplay);
begin
fDisplay := Value;
end;
procedure TSvrCommands.SetItem(Index: Integer; Value: TSvrCommand);
begin
inherited SetItem(Index, Value);
end;
{ TSvrCommand }
procedure TSvrCommand.Assign(Source: TPersistent);
begin
inherited;
end;
constructor TSvrCommand.Create(Collection: TCollection);
begin
inherited Create(Collection);
fCollection:= TSvrCommands(Collection);
end;
destructor TSvrCommand.Destroy;
begin
inherited Destroy;
end;
function TSvrCommand.GetDisplayName: String;
begin
case Self.fCollection.fDisplay of
cdName: begin
Result:= fName;
end;
cdID: begin
Result:= '['+IntToStr(fID)+']';
end;
cdCaption: begin
Result:= fCaption;
end;
cdIDName: begin
Result:= '['+IntToStr(fID)+'] '+fName;
end;
cdIDCaption: begin
Result:= '['+IntToStr(fID)+'] '+fCaption;
end;
end;
end;
procedure TSvrCommand.SetCaption(const Value: String);
begin
fCaption := Value;
end;
procedure TSvrCommand.SetID(Value: Integer);
begin
fID:= Value;
end;
procedure TSvrCommand.SetName(Value: String);
begin
fName:= Value;
end;
This Thread helped me creating something as we discussed yesterday. I took the package posted there and modified it a bit. Here is the source:
TestComponents.pas
unit TestComponents;
interface
uses
Classes;
type
TParentComponent = class;
TChildComponent = class(TComponent)
private
FParent: TParentComponent;
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;
end;
TParentComponent = class(TComponent)
private
FChilds: TList;
protected
procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
property Childs: TList read FChilds;
end;
implementation
{ 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
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;
end.
TestComponentsReg.pas
unit TestComponentsReg;
interface
uses
Classes,
DesignEditors,
DesignIntf,
TestComponents;
type
TParentComponentEditor = class(TComponentEditor)
procedure ExecuteVerb(Index: Integer); override;
function GetVerb(Index: Integer): string; override;
function GetVerbCount: Integer; override;
end;
procedure Register;
implementation
uses
ColnEdit;
type
TChildComponentCollectionItem = class(TCollectionItem)
private
FChildComponent: TChildComponent;
function GetName: string;
procedure SetName(const Value: string);
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;
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]);
RegisterComponentEditor(TParentComponent, TParentComponentEditor);
end;
{ TParentComponentEditor }
procedure TParentComponentEditor.ExecuteVerb(Index: Integer);
var
LCollection: TChildComponentCollection;
i: Integer;
begin
LCollection := TChildComponentCollection.Create(Component, TChildComponentCollectionItem);
LCollection.Designer := Designer;
for i := 0 to TParentComponent(Component).Childs.Count - 1 do
with TChildComponentCollectionItem.Create(nil) do
begin
ChildComponent := TChildComponent(TParentComponent(Component).Childs[i]);
Collection := LCollection;
end;
ShowCollectionEditorClass(Designer, TCollectionEditor, Component, LCollection, 'Childs');
end;
function TParentComponentEditor.GetVerb(Index: Integer): string;
begin
Result := 'Edit Childs...';
end;
function TParentComponentEditor.GetVerbCount: Integer;
begin
Result := 1;
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;
procedure TChildComponentCollectionItem.SetName(const Value: string);
begin
FChildComponent.Name := Value;
end;
end.
The most important thing is the RegisterNoIcon which prevents showing the component on the form when you create it. The overridden methods in TChildComponent are causing them to be nested inside the TParentComponent.
Edit: I added a temporary collection to edit the items in the built-in TCollectionEditor instead of having to write an own one. The only disadvantage is that the TChildComponentCollectionItem has to publish every property that TChildComponent has published to be able to edit them inside the OI.
Use the TComponent.SetSubComponent routine:
type
TComponent1 = class(TComponent)
private
FSubComponent: TComponent;
procedure SetSubComponent(Value: TComponent);
public
constructor Create(AOwner: TComponent); override;
published
property SubComponent: TComponent read FSubComponent write SetSubComponent;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Samples', [TComponent1]);
end;
{ TComponent1 }
constructor TComponent1.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FSubComponent := TComponent.Create(Self); // Nót AOwner as owner here !!
FSubComponent.Name := 'MyName';
FSubComponent.SetSubComponent(True);
end;
procedure TComponent1.SetSubComponent(Value: TComponent);
begin
FSubComponent.Assign(Value);
end;
I now understand this sub component would be part of a collection item. In that case: no difference, use this method.
Implement TCollectionItem.GetDisplayName to "name" the collection items.
And concerning the collection: when this is a published property, the collection will automatically be named as the property name.
Be careful to implement GetOwner when you create properties of TPersistent.
I have created a custom control TOuterControl that is the parent for multiple TInnerControls.
Everything is working fine except that the IDE is creating icons for each the child TInnerControl's (InnerControl1 and InnerControl2 in the screenshot). How do I prevent the IDE from generating the icons?
unit TestControl;
interface
Procedure Register;
implementation
Uses
Classes,
Controls,
SysUtils,
DesignEditors,
DesignIntf,
VCLEditors;
Type
TOuterControl = Class;
TInnerControl = Class(TComponent)
Protected
FOuterControl : TOuterControl;
function GetParentComponent: TComponent; Override;
Function HasParent : Boolean; Override;
procedure SetParentComponent (Value: TComponent); Override;
End;
TOuterControl = Class(TCustomControl)
Protected
FInnerControls : TList;
Procedure Paint; Override;
Public
Constructor Create(AOwner : TComponent); Override;
Procedure AddInnerControl(AInnerControl : TInnerControl);
procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override;
End;
TOuterControlEditor = Class(TDefaultEditor)
Public
Procedure ExecuteVerb(Index : Integer); Override;
Function GetVerb (Index : Integer) : String; Override;
Function GetVerbCount : Integer; Override;
End;
procedure TOuterControl.AddInnerControl(AInnerControl: TInnerControl);
begin
AInnerControl.FOuterControl := Self;;
FInnerControls.Add(AInnerControl);
Invalidate;
end;
constructor TOuterControl.Create(AOwner: TComponent);
begin
inherited;
FInnerControls := TList.Create;
end;
procedure TOuterControl.GetChildren(Proc: TGetChildProc; Root: TComponent);
var
I : Integer;
begin
inherited;
For I := 0 To FInnerControls.Count - 1 Do
Proc(FInnerControls[I]);
end;
procedure TOuterControl.Paint;
begin
inherited;
Canvas.FillRect(ClientRect);
Canvas.TextOut(0,0, Format('Inner Control Count = %d', [FInnerControls.Count]));
end;
function TInnerControl.GetParentComponent: TComponent;
begin
Result := FOuterControl;
end;
function TInnerControl.HasParent: Boolean;
begin
Result := True;
end;
procedure TInnerControl.SetParentComponent(Value: TComponent);
begin
If Value Is TOuterControl Then
If FOuterControl <> Value Then
Begin
FOuterControl := TOuterControl(Value);
FOuterControl.AddInnerControl(Self);
End;
end;
procedure TOuterControlEditor.ExecuteVerb(Index: Integer);
Var
OuterControl : TOuterControl;
InnerControl : TInnerControl;
begin
inherited;
OuterControl := TOuterControl(Component);
If Index = 0 Then
Begin
InnerControl := TInnerControl.Create(OuterControl.Owner);
OuterControl.AddInnerControl(InnerControl);
End;
end;
function TOuterControlEditor.GetVerb(Index: Integer): String;
begin
Result := 'Add Inner';
end;
function TOuterControlEditor.GetVerbCount: Integer;
begin
Result := 1;
end;
Procedure Register;
Begin
RegisterComponents('AA', [TOuterControl]);
RegisterComponentEditor(TOuterControl, TOuterControlEditor);
End;
Initialization
Classes.RegisterClasses([TInnerControl]);
end.
You can prevent them from appeaing on the form with:
RegisterNoIcon([TInnerControl]);
More info on RegisterNoIcon can be found at http://docwiki.embarcadero.com/VCL/e/index.php/Classes.RegisterNoIcon
It's a little confusing having classes with a name that end with "Control" that aren't normal visual controls though.
If TInnerControl is meant to be used only inside a TOuterControl, then you should call SetSubComponent(True) during/after the TInnerControl's creation.
When you create the inner controls, you tell them that their owner is the form (the owner of the outer control). Therefore, the form draws them, just like it draws all the other components it owns. You probably want the outer control to own the inner ones.