How do I code a property with sub-properties? (redux) - delphi

I am sure that I got a good answer to my previous question because I have previously had a great deal of help on other questions from the guys who posted there.
But I am obviously doing something wrong, because when I copy the example code what the object inspector shows me for the MyProp property is a single text input field. I was expecting to see something that looks like the Font property, with Pitch, font family, etc i.e I expect to see a tree structure but I don't see the Color, Height or Width properties of the MyProp property.
Any ideas? Again, I copied that code exactly.
Edit: I forgot to mention (in this question) that I am using TMS scripter pro, which allows users to design forms at run time and provides its own object inspector, but that is probably derived from standard Delphi stuff, I guess.
Anyway, it appears that I am too dumb to code Delphi as I simply can't get this to work.
Edit: TMS assure me that if the class with "sub-properties) is descended from TPresistent then it will appear in the object inspector with sub-properties, just like Font, Anchors, etc
When I use this code, the "Warning" property appears as a text field in the object inspector and has no sub-properties
unit IntegerEditBox;
// An edit box which only accepts integer values and warns if the value is not in a certain range
interface
uses
SysUtils, Classes, Controls, StdCtrls,
EditBox_BaseClass;
type
TWarning = Class(TPersistent)
private
FWarningBelowValue : Integer;
FWarningAboveValue : Integer;
FWarningEmailTo : String;
FWarningSmsTo : String;
published
property WarningBelowValue : Integer read FWarningBelowValue write FWarningBelowValue;
property WarningAboveValue : Integer read FWarningAboveValue write FWarningAboveValue;
property WarningEmailTo : String read FWarningEmailTo write FWarningEmailTo;
property WarningSmsTo : string read FWarningSmsTo write FWarningSmsTo;
end;
TIntegerEditBox = class(TEditBox_BaseClass)
private
FWarning : TWarning;
procedure WriteValue(const newValue : Integer);
protected
// The new property which w/e introduce in this class
FValue : Integer;
public { Public declarations }
Constructor Create(AOwner: TComponent); override; // This constructor uses defaults
property Text;
published { Published declarations - available in the Object Inspector at design-time }
property Hint;
// Now our own properties, which we are adding in this class
property Value : Integer read FValue write WriteValue;
property Warning : TWarning read FWarning write FWarning ;
end; // of class TIntegerEditBox()
procedure Register;
implementation
uses
Dialogs;
procedure Register;
begin
RegisterComponents('Standard', [TIntegerEditBox]);
end;
Constructor TIntegerEditBox.Create(AOwner: TComponent);
begin
inherited; // Call the parent Create method
Hint := 'Only accepts a number|Only accepts a number'; // Tooltip | status bar text
Mandatory := True;
Value := 0;
Text := IntToStr(Value);
end;
procedure TIntegerEditBox.WriteValue(const newValue : Integer);
begin
Text := IntToStr(newValue);
end;
end.

The original version of the demo code neglected to create an instance of the property object.
constructor TMyControl.Create(AOwner: TComponent)
begin
inherited;
FMyProp := TCustomType.Create;
end;
Don't forget to free it in the destructor.
Remy's comment on that answer points out that the property needs to be assigned differently. The property's write accessor shouldn't write directly to the field. Instead, it should have a setter method that works like this:
procedure TMyControl.SetMyProp(const Value: TCustomType);
begin
FMyProp.Assign(Value);
end;
That also highlights the requirement that the property class's Assign method be implemented, or else you'll get strange error messages like "Cannot assign a TCustomType to a TCustomType." A simple implementation could go like this:
procedure TCustomType.Assign(Source: TPersistent);
begin
if Source is TCustomType then begin
Color := TCustomType(Source).Color;
Height := TCustomType(Source).Height;
Width := TCustomType(Source).Width;
end else
inherited;
end;

Related

Delphi7Personal EAccessViolation while creating component derived from TImage

I have an assignment to create a game in Delphi and since the board will be the size 7x7 i wanted to do it by deriving my own component from TImage. Due to the fact that I wanted to have position of all tiles in the board in the array and I wanted to use Create method to do it but whenever I tried I have encountered EAccessViolation, while calling .Create(self)
Here is my component's code:
unit iles1;
interface
SysUtils, Classes, Controls, ExtCtrls;
type
Tiles1 = class(TImage)
private
FPlayer:Boolean; //determines whether it is an empty field or a player
FTeam:Boolean; //determines the team the tile belogns to
FBall:Boolean; //posession of the ball
{FBackLight : whether it is available to interact with this component,
with the method on click after one of the tiles has already been chosen,
if it is not lit but belongs to the same team, it is flagged as chosen
but not as lit, this field is used to determine whether i can pass a ball
to this direction or swap places with other player from the same team}
FBackLight:Boolean;
FChosen:Boolean; //whether the player decided to click on it
{FPostion determines where it is in a table, it ranges from
36 to 0 where its position divided by 10 determines the column
and position mod 10 determines the row}
FPosition:Byte;
{ Private declarations }
protected
{ Protected declarations }
public
constructor Create(AOwner : TComponent); override;
{ Public declarations }
published
property Team: boolean
read FTeam
write FTeam;
property Ball: boolean
read FBall
write FBall;
property Player:boolean
read FPlayer
write FPlayer;
property BackLight:boolean
read FBackLight
write FBackLight;
property Chosen:boolean
read FChosen
write FChosen;
property Position:byte
read FPosition
write FPosition;
end;
{ property Ball: Boolean;
//read FHasBall
//write FSetBall;
end;}
{ Published declarations }
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Samples', [Tiles1]);
end;
{Creator procedure calling the Timage creator
and setting parent to self(impossible here, then i will do it in
main window), visible to true
}
constructor Tiles1.Create(AOwner:TComponent);
begin
inherited;
FPlayer:=false;
FTeam:=false;
FBall:=false;
FBackLight:=false;
FChosen:=false;
FPosition:=0;
end;
end.
And here i have my main menu method that uses it:
procedure TForm1.FormCreate(Sender: TObject);
var x,y:Integer;
begin
for y:=1 to INAROW do begin
for x:=1 to INAROW do begin
tiles[x,y].Create(self);
tiles[x,y].Parent:=self;
tiles[x,y].Visible:=true;
tiles[x,y].Top:=(y-1)*(GAPBETWEEN+TILES1HEIGHT)+GAPTOP;
tiles[x,y].Left:=GAPLEFT+(x-1)*(GAPBETWEEN+TILES1WIDTH);
tiles[x,y].Width:=TILES1WIDTH;
tiles[x,y].Height:=TILES1HEIGHT;
tiles[x,y].Position:=10*x+y;
tiles[x,y].BackLight:=false;
tiles[x,y].Ball:=false;
tiles[x,y].Player:=false;
tiles[x,y].Chosen:=false;
end;
end;
setAlphaTeam;
setBetaTeam;
setTiles;
end;
tiles[X,Y] := Tiles1.Create(self);
assuming tiles is an array of Tiles1.
Constructors are effectively class methods, you call them on the class, not the instance.
You are getting an access violation because tiles[X,Y] is nil. If you commented out the create line, you'd get it trying to set the Parent Property.

Getting the Component Name in the Constructor?

I am creating a custom control derived from TCustomControl, for example:
type
TMyCustomControl = class(TCustomControl)
private
FText: string;
procedure SetText(const Value: string);
protected
procedure Paint; override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property Text: string read FText write SetText;
end;
Note, the above is incomplete for purpose of the example to keep it short and simple.
Anyway, in my control I have a Paint event which displays text (from FText field) using Canvas.TextOut.
When my component is added to the Delphi Form Designer (before any user changes can be made to the component) I want the TextOut to display the name of the Component - TButton, TCheckBox, TPanel etc are examples of this with their caption property.
If I try to assign the name of my Component to FText in the constructor it returns empty, eg '';
constructor TMyCustomControl.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FText := Name; //< empty string
ShowMessage(Name); //< empty message box too
end;
If I change FText := Name to FText := 'Name'; it does output the text to my Component so I do know it is not a problem within the actual code, but obviously this outputs 'Name' and not the actual Component name like MyCustomControl1, MyCustomControl2 etc.
So my question is, how can you get the name of your Component from its constructor event?
The Name property has not been assigned yet when the constructor is running. At design-time, the IDE assigns a value to the Name property after the component has been dropped onto the Designer, after the control's constructor has exited. At runtime, the Name property is set by the DFM streaming system instead, which is also invoked after the constructor has exited.
Either way, the TControl.SetName() property setter validates the new value, and then sets the new value to the control's Text property to match if the current Text value matches the old Name value and the control's ControlStyle property includes the csSetCaption flag (which it does by default). When the Text property changes for any reason, the control automatically sends itself a CM_TEXTCHANGED notification. You can have your control catch that message and call Invalidate() on itself to trigger a new repaint. Inside of your Paint() handler, simply draw the current Name as-is, whatever value it happens to be. If it is blank, so be it. Don't try to force the Name, let the VCL handle it for you normally.
I believe the proper way to handle this is to use the inherited Text or Caption property of TCustomControl, and to make sure that the csSetCaption ControlStyle is set.
To apply the name you may override TComponent.Loaded method.
But i don't think You should copy Name to Text. Those are semantically separate properties and adding unexpected binding to them would hurt you some day.
Rather WMPaint method should check if the Text is empty and then render Name then, but the very property of Text should not be changed.
procedure TMyComponent.WMPaint; message WM_Paint; var InternalCaption: string;
begin
....
InternalCaption := Self.Text;
If InternalCaption = '' then InternalCaption := Self.Name;
If InternalCaption = '' then InternalCaption := Self.ClassName;
....
Self.Canvas.OutText(InternalCaption);
If anything - you should keep properties separated just for the simple reason that Name := 'AAA'; Name := 'BBB'; should not make Text and name out of sync. And with your approach 1st statement would settle the Text and the second would make old Name still displayed after the actual name changed.
Un easy way is to override the method SetName:
TMyCaptionString = type of WideString;
TMyLabel = class(TCustomControl)
private
FCaption: TMyCaptionString;
FCaptionAsName: Boolean;
procedure SetCaption(Value: TMyCaptionString);
protected
procedure SetName(const NewName: TComponentName); override;
public
constructor Create(AOwner: TComponent); override;
property Caption: TMyCaptionString read FCaption write SetCaption;
end;
implementation
constructor TMyLabel.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
ControlStyle := ControlStyle + [csOpaque, csReplicatable,csSetCaption];
FCaptionAsName := (csDesigning in ComponentState) and not (csReadingState in ControlState);
...
end;
procedure TMyLabel.SetName(const NewName: TComponentName);
begin
if FCaptionAsName then
begin
FCaptionAsName := FCaption = Name;
FCaption := NewName;
invalidate;
end;
inherited SetName(NewName);
end;
procedure TMyLabel.SetCaption(Value: TMyCaptionString);
begin
if FCaption <> Value then
begin
FCaption := Value;
Invalidate;
FCaptionAsName := False;
end;
end;
I needed my own variable for the Caption poreprty, because I want to use widestring instead unicode and to write a custom Property editor. Sorry that i'm writing in old topic, but i hope this will helpfull.

Why use property in a class?

I was just wondering about why should I use property in a class instead of "normal" variables (class attributes?). What I mean is this:
TSampleClass = class
public
SomeInfo: integer;
end;
TPropertyClass = class
private
fSomeInfo: integer;
public
property SomeInfo: integer read fSomeInfo write fSomeInfo;
end;
What is the big difference? I know that I can define getter and setter methods for getting or saving the property respectively, but that is possible even without the variable being a "property".
I tried searching for why to use it, but nothing useful came up, so I'm asking here.
Thank you
This is just a very simple example of a specific case, but still, it is a very common case.
If you have a visual control, you might need to repaint the control when you change a variable/property. For instance, let's say your control has a BackgroundColor variable/property.
The simplest way of adding such a variable/property is to let it be a public variable:
TMyControl = class(TCustomControl)
public
BackgroundColor: TColor;
...
end;
And in the TMyControl.Paint procedure, you paint the background using the value of the BackgroundColor. But this doesn't do it. Because if you change the BackgroundColor variable of an instance of the control, the control doesn't repaint itself. Instead, the new background colour will not be used until the next time the control redraws itself for some other reason.
So you have to do it like this:
TMyControl = class(TCustomControl)
private
FBackgroundColor: TColor;
public
function GetBackgroundColor: TColor;
procedure SetBackgroundColor(NewColor: TColor);
...
end;
where
function TMyControl.GetBackgroundColor: TColor;
begin
result := FBackgroundColor;
end;
procedure TMyControl.SetBackgroundColor(NewColor: TColor);
begin
if FBackgroundColor <> NewColor then
begin
FBackgroundColor := NewColor;
Invalidate;
end;
end;
and then the programmer using the control has to use MyControl1.GetBackgroundColor to obtain the colour, and to use MyControl1.SetBackgroundColor to set it. That's awkward.
Using properties, you can have the best of both worlds. Indeed, if you do
TMyControl = class(TCustomControl)
private
FBackgroundColor: TColor;
procedure SetBackgroundColor(NewColor: TColor);
published
property BackgroundColor: TColor read FBackgroundColor write SetBackgroundColor;
end;
...
procedure TMyControl.SetBackgroundColor(NewColor: TColor);
begin
if FBackgroundColor <> NewColor then
begin
FBackgroundColor := NewColor;
Invalidate;
end;
end;
then
from the programmer's point of view, he can both read and set the background colour using a single identifier, the MyControl1.BackgroundColor property, and
the control is repainted when he sets it!
There are real-life advantages:
Properties can be changed to be read/write/read'n'write easily, without need to hassle with separate Getters and Setters all over the code;
Properties can be made public/published in child classes by just adding one line in initialization section;
Properties are more friendly when it comes to setting fields, compare "Label.Font.SetSize(14)" with "Label.Font.Size := 14", you can align ":=" with tabs/spaces and code will be much more readable;
EDIT: Another thing I thought of, properties force you to limit Get/Set methods to only 1 parameter, which is good for OOP. Compare that to some over-engineered functions:
GetItem(Index:integer; ForcedIndex:boolean=false):TItem //Forced index to get any value
GetItem(Index:integer; out Res:PItem):boolean //Result signals if out pointer is valid
I know that I can define getter and setter methods for getting or saving the property respectively, but that is possible even without the variable being a "property".
Well, no. Setters and getters are just normal methods that are called as such only once they are used as the read and write members of a property. Not having a property means not having a getter or a setter, even if they are named as such.
Furthermore; setters and getters are typically declared private or protected. So being able to call them when you use a public field instead of using a public property would require to move those methods to the public section.
Also, a big difference between fields and properties is the ability to be published and thus can be used in the object inspector. Fields (of other types then class or interface) can not be declared as published.
Properties can also be of great importance - or be usefull - in inheritance. Technically, you can't override a property, but you can mimic override in several ways. Some examples where property Name can be called from TDescendant, each with its own purpose:
1) Abstraction:
TBase = class(TObject)
protected
function GetName: String; virtual; abstract;
procedure SetName(const Value: String); virtual; abstract;
public
property Name: String read GetName write SetName;
end;
TDescendant = class(TBase)
private
FName: String;
protected
function GetName: String; override;
procedure SetName(const Value: String); override;
end;
2a) Protection (like Krom mentioned, ):
TBase = class(TObject)
private
FName: String;
function GetName: String;
procedure SetName(const Value: String);
protected
property Name: String read GetName write SetName;
end;
TDescendant = class(TBase)
public
property Name;
end;
2b)
TBase = class(TObject)
private
FName: String;
protected
function GetName: String;
procedure SetName(const Value: String);
end;
TDescendant = class(TBase)
public
property Name: String read GetName write SetName;
end;
By combinination of the above, you could change the behaviour of properties for descendant classes.
It is just a good programming practice to isolate the very "innards" of your class from the outside world. In addition, information about published properties are stored into RTTI generated for the class and can be accessed by their name, enumerated etc. This feature is used for example when reading a form from its serialized resource form.
One of main reason of using properties (regardless of it's more OO) is the validation of the input, for example if you need to limit the age of an employee class to be in valid range like 18..40
TEmp = class
private
FName: string;
FAge: Integer;
procedure SetAge(const Value: Integer);
procedure SetName(const Value: string);
published
property Name:string read FName write SetName;
property Age:Integer read FAge write SetAge;
end;
.....
procedure TEmp.SetAge(const Value: Integer);
begin
if not (Value in [18..40]) then
raise Exception.Create('Age must be between 18 and 40')
else
FAge := Value;
end;
You cant monitor the change in a variable without a property.
your read/writes for property dont have to be a variable they can be functions. And then you can manage the "onChange" of a property.
eg
TmyChange = procedure(Sender: Tobject) of object;
private
Fchange : TmyChange;
public
property SomeInfo: integer read getFoo write setFoo;
property onChange : TmyChange read Fchange write Fchange;
function getFoo : integer
begin
return localFoo;
end;
function setFoo (value : integer)
begin
// validate incoming value
localFoo=value;
if assigned(Fchange) then Fchange(self);
end;

how to add TAboutBox in TCustomControls?

I want to add aboutbox/dialogbox on my Custom component. how to make the small button[...] appear on the object inspector? just like the assigning a picure on the Timage component.
You must define a property similar to this:
//: InformaciĆ³n acerca del paquete de componentes
property AboutMe:TFAbout read FAboutG stored false;
TFAbout is a class, that define the form that you want to see (About form), when the user click on the property in "Object Inspector".
Additionally, you must register a "Property Editor", if you want see a buuton with the three point |...| in OI.
This is a sample unit:
unit UTAboutProp;
interface
uses
DesignIntf, DesignEditors;
type
TAboutGProp = class(TPropertyEditor)
public
procedure Edit(); override;
function GetValue(): string; override;
function GetAttributes(): TPropertyAttributes; override;
end;
implementation
uses
SysUtils, FormAbout, UConstantes;
procedure TAboutGProp.Edit();
begin
with TFAbout.Create(nil) do
try
ShowModal();
finally
Free();
end;
end;
function TAboutGProp.GetValue(): string;
begin
result := Format(GLIBSI_LBL,[GLIBSI_VERSION]);
result := '1.0';
end;
function TAboutGProp.GetAttributes(): TPropertyAttributes;
begin
result := [paDialog,paReadOnly];
end;
end.
Only rest to "register" this "property Editor" for work with your About property; This is important for "link" your property with your editor.
Where you have the code for register the component, add the code for register the property:
RegisterPropertyEditor(TypeInfo(TFAbout),nil,'',TAboutGProp);
Regards

How to improve the use of Delphi Frames

I've used frames in Delphi for years, and they are one of the most powerful features of the VCL, but standard use of them seems to have some risk such as:
It's easy to accidentally move or edit the frame sub-components on a frame's host form without realising that you are 'tweaking' with the frame - I know this does not affect the original frame code, but it's generally not what you would want.
When working with the frame you are still exposed to its sub-components for visual editing, even when that frame is years old and should not be touched.
So I got to thinking....
Is there a way of 'grouping' components such that their positions are 'locked'? This would be useful for finished forms as well as frames. Often other developers return code to me where only the form bounds have changed and even they did not intend any change.
Is there any way of turning a frame and its components into a single Delphi component? If so, the frame internals would be completely hidden and its useability would increase further.
I'm interested in any thoughts...
Brian.
Registering your frames as a component solves both 1. and 2.:
the components on the frame are locked when you put that frame control on a form or other frame
you will get a component (actually: control) that you can design visually
But: there are a few catches (which can be solved, see article link), of which the most important is this one:
When you put components on your frame, and later drop that frame as a component on a Delphi form or frame, the components are visible in the Structure Pane.
The problem is that because they are visible in the structure pane, you can delete them, causing access violations.
The trick to solve this to not forget the 'sprig'.
I learned that valuable lesson from Ray Konopka during DelphiLive 2009.
Since the lesson is so valuable, I wrote a blog post on it that describes it in detail.
The essential portion is this little piece of code (more details in the blog post):
procedure RegisterFramesAsComponents(const Page: string; const FrameClasses: array of TFrameClass);
var
FrameClass: TFrameClass;
begin
for FrameClass in FrameClasses do
begin
RegisterComponents(Page, [FrameClass]);
RegisterSprigType(FrameClass, TComponentSprig);
end;
end;
Hope this helps.
--jeroen
Yes, just register them as components. :-)
Design your frame normally and after this register it. Also be sure to not have unwanted dependencies on different units since these are linked when your 'component' is used. Also you can add published properties in order to use them in the Object Inspector later. See for example the following code generated by the IDE (see also my comments):
unit myUnit;
uses
...
type
TmyComp = class(TFrame) //set your frame name to be the name your component
ToolBar1: TToolBar; //different components added in the form designer
aliMain: TActionList;
...
published //this section is added by hand
property DataSource: TDataSource read FDataSource write SetDataSource; //some published properties added just for exemplification
property DefFields: string read FDefFields write SetDefFields;
...
end;
procedure Register; //added by hand
implementation
{$R *.DFM}
procedure Register;
begin
RegisterComponents('MyFrames', [TmyComp]); //register the frame in the desired component category
end;
Compile the above in a package of your choice, install it and check you component palette. :-)
HTH
I'm almost always creating frame instances in code. This is easy and worked well for me so far.
Just for increasing contribution, note that if you go to Structure window and right-click on the TFrame name that you chose, and click on the Add to Palete menu option.
This will make a component out of your Frame and you don't need to create any Register procedure. ;-)
I also ran into that problem when trying to use frames as components. There are various possibilities to fix the obvious issues, but they all undermine the principle of information hiding (all the frame's subcomponents are exposed as published properties, which means everyone can access them).
I solved it by implementing a generic "frame control" component:
unit RttiBrow.Cbde.FrameControl;
interface
uses
Classes, Controls, Forms, Messages, ExtCtrls;
type
TFrameClass = class of TFrame;
TComponentFrame = class (TFrame)
private
function GetClientHeight: Integer;
function GetClientWidth: Integer;
procedure SetClientHeight(const Value: Integer);
procedure SetClientWidth(const Value: Integer);
function GetOldCreateOrder: Boolean;
procedure SetOldCreateOrder(const Value: Boolean);
function GetPixelsPerInch: Integer;
procedure SetPixelsPerInch(const Value: Integer);
function GetTextHeight: Integer;
procedure SetTextHeight(const Value: Integer);
published
{ workarounds for IDE bug }
property ClientWidth: Integer read GetClientWidth write SetClientWidth stored False;
property ClientHeight: Integer read GetClientHeight write SetClientHeight stored False;
property OldCreateOrder: Boolean read GetOldCreateOrder write SetOldCreateOrder stored False;
property PixelsPerInch: Integer read GetPixelsPerInch write SetPixelsPerInch stored False;
property TextHeight: Integer read GetTextHeight write SetTextHeight stored False;
end;
TComponentFrame<TFrameControl: class { TControl }> = class (TComponentFrame)
private
function GetController: TFrameControl; inline;
protected
property Controller: TFrameControl read GetController;
public
constructor Create (AOwner: TComponent); override;
end;
TFrameControl<T: TFrame> = class (TWinControl)
private
FFrame: T;
function PlainFrame: TFrame;
protected
procedure CreateParams (var Params: TCreateParams); override;
property Frame: T read FFrame;
public
constructor Create (AOwner: TComponent); override;
property DockManager;
published
property Align;
property Anchors;
property BiDiMode;
property Color;
property Constraints;
property Ctl3D;
property UseDockManager default True;
property DockSite;
property DoubleBuffered;
property DragCursor;
property DragKind;
property DragMode;
property Enabled;
property Font;
property ParentBiDiMode;
property ParentBackground;
property ParentColor;
property ParentCtl3D;
property ParentDoubleBuffered;
property ParentFont;
property ParentShowHint;
property ShowHint;
property TabOrder;
property TabStop;
property Touch;
property Visible;
property OnAlignInsertBefore;
property OnAlignPosition;
property OnCanResize;
property OnConstrainedResize;
property OnDockDrop;
property OnDockOver;
property OnDragDrop;
property OnDragOver;
property OnEndDock;
property OnEndDrag;
property OnEnter;
property OnExit;
property OnGesture;
property OnGetSiteInfo;
property OnMouseActivate;
property OnMouseDown;
property OnMouseEnter;
property OnMouseLeave;
property OnMouseMove;
property OnMouseUp;
property OnResize;
property OnStartDock;
property OnStartDrag;
property OnUnDock;
end;
implementation
uses
Windows;
{ TFrameControl<T> }
constructor TFrameControl<T>.Create(AOwner: TComponent);
begin
inherited;
FFrame := T (TFrameClass (T).Create (Self));
PlainFrame.Parent := Self;
PlainFrame.Align := alClient;
end;
procedure TFrameControl<T>.CreateParams(var Params: TCreateParams);
begin
inherited;
Params.Style := Params.Style or WS_CLIPCHILDREN;
Params.ExStyle := Params.ExStyle or WS_EX_CONTROLPARENT;
end;
function TFrameControl<T>.PlainFrame: TFrame;
begin
Result := FFrame; // buggy compiler workaround
end;
{ TComponentFrame }
function TComponentFrame.GetOldCreateOrder: Boolean;
begin
Result := False;
end;
function TComponentFrame.GetPixelsPerInch: Integer;
begin
Result := 0;
end;
function TComponentFrame.GetTextHeight: Integer;
begin
Result := 0;
end;
procedure TComponentFrame.SetClientHeight(const Value: Integer);
begin
Height := Value;
end;
procedure TComponentFrame.SetClientWidth(const Value: Integer);
begin
Width := Value;
end;
procedure TComponentFrame.SetOldCreateOrder(const Value: Boolean);
begin
end;
procedure TComponentFrame.SetPixelsPerInch(const Value: Integer);
begin
end;
procedure TComponentFrame.SetTextHeight(const Value: Integer);
begin
end;
function TComponentFrame.GetClientHeight: Integer;
begin
Result := Height;
end;
function TComponentFrame.GetClientWidth: Integer;
begin
Result := Width;
end;
{ TComponentFrame<TFrameControl> }
constructor TComponentFrame<TFrameControl>.Create(AOwner: TComponent);
begin
inherited;
Assert (AOwner <> nil);
Assert (AOwner.InheritsFrom (TFrameControl));
end;
function TComponentFrame<TFrameControl>.GetController: TFrameControl;
begin
Result := TFrameControl (Owner);
end;
end.
With this class, adding a frame as a component becomes a two-stage process:
// frame unit
type
TFilteredList = class;
TFrmFilteredList = class (TComponentFrame<TFilteredList>)
// lots of published sub-components and event methods like this one:
procedure BtnFooClick(Sender: TObject);
end;
TFilteredList = class (TFrameControl<TFrmFilteredList>)
private
procedure Foo;
public
// the component's public interface
published
// the component's published properties
end;
procedure Register;
...
procedure Register;
begin
RegisterComponents ('CBDE Components', [TFilteredList]);
end;
procedure TFrmFilteredList.BtnFooClick(Sender: TObject);
begin
Controller.Foo;
end;
procedure TFilteredList.Foo;
begin
end;
...
When using this approach, the user of your component won't see your sub-components.

Resources