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.
Related
I'm developing a component for Query. It works like the "Properties" feature of DevExpress, but I need to place the order of the Unpublished Property I wrote to DFM with DefineProperties in the DFM file at the top of the TCollectionItem.
It works the same way in DevExpress. If you add a Field to the cxGrid and assign a value to the Properties property, you will see the value "PropertiesClassName" in the DFM file at the top.
When I open the DFM file and bring this Property to the top, the setter property of the "PropertiesClassName" Property works and I create that Class. It works seamlessly when reading data from the DFM stream. But no matter what I did I couldn't get the "PropertiesClassName" Property value to the top.
If you create a cxGrid on the form and add Field, and then take the "PropertiesClassName" property from DFM to the bottom of the DFM file, when you open the form again, you will see that it cannot find the relevant Class and an error occurs.
To change the DFM flow, I first assigned a value to the "PropertiesClassName" Property and then created the Class, but the problem was not solved. I did the opposite of this but the problem is still the same.
DFM Context
object QuerySearchEngine1: TQuerySearchEngine
SearchFields = <
item
FieldName = 'TestField'
Properties.Convert2String = True
PropertiesClassName = 'TSearchBooleanProperties'
end>
DFM Context should be like
object QuerySearchEngine1: TQuerySearchEngine
SearchFields = <
item
PropertiesClassName = 'TSearchBooleanProperties'
FieldName = 'TestField'
Properties.Convert2String = True
end>
Classes
TSearchField = class(TCollectionItem)
private
FFieldName: string;
FProperties: TSearchFieldProperties;
FPropertiesClassName: string;
private
procedure SetFieldName(const Value: string);
procedure SetProperties(const Value: TSearchFieldProperties);
private
procedure ReaderProc(Reader: TReader);
procedure WriterProc(Writer: TWriter);
procedure SetPropertiesClassName(const Value: string);
protected
constructor Create(Collection: TCollection); override;
procedure DefineProperties(Filer: TFiler); override;
public
property PropertiesClassName: string read FPropertiesClassName write SetPropertiesClassName;
published
property FieldName: string read FFieldName write SetFieldName;
property Properties: TSearchFieldProperties read FProperties write SetProperties;
end;
procedure TSearchField.DefineProperties(Filer: TFiler);
begin
inherited;
Filer.DefineProperty('PropertiesClassName', ReaderProc, WriterProc, FPropertiesClassName <> '');
end;
procedure TSearchField.SetPropertiesClassName(const Value: string);
begin
var Item: TSearchFieldPropertiesItem;
if TryValidateSearchFieldPropertiesClassName(Value, Item) then
begin
if not Assigned(FProperties) or not (FProperties.ClassType = Item.ClassType) then
begin
if Assigned(FProperties) then
begin
FProperties.Free;
FProperties := nil;
end;
FPropertiesClassName := Item.ClassType.ClassName;
FProperties := Item.ClassType.Create;
end;
end
else
begin
FPropertiesClassName := '';
if Assigned(FProperties) then
begin
FProperties.Free;
FProperties := nil;
end;
end;
end;
Property Editor
type
TSearchFieldPropertiesProperty = class(TClassProperty)
private
function GetInstance: TPersistent;
public
function GetAttributes: TPropertyAttributes; override;
procedure GetValues(Proc: TGetStrProc); override;
function GetValue: string; override;
procedure SetValue(const Value: string); override;
end;
function TSearchFieldPropertiesProperty.GetValue: string;
begin
for var I := 0 to Self.PropCount - 1 do
begin
var Inst := Self.GetComponent(I);
if Assigned(Inst) and Self.HasInstance(Inst) then
begin
if Inst is TSearchField then
begin
var PropInst := GetObjectProp(Inst, Self.GetPropInfo);
if Assigned(PropInst) then
begin
for var Item in SearchFieldPropertiesList do
begin
if PropInst.ClassType = Item.ClassType then
begin
Result := Item.Name;
Exit;
end;
end;
end;
end;
end;
end;
end;
procedure TSearchFieldPropertiesProperty.SetValue(const Value: string);
begin
var Item: TSearchFieldPropertiesItem;
if TryValidateSearchFieldPropertiesName(Value, Item) then
begin
var Inst := GetInstance;
if Assigned(Inst) then
begin
var Context := TRttiContext.Create;
var Rtype := Context.GetType(Inst.ClassType);
for var Prop in Rtype.GetProperties do
begin
if SameText(Prop.Name, 'PropertiesClassName') then
begin
Prop.SetValue(Inst, TValue.From<string>(Item.ClassType.ClassName));
Break;
end;
end;
end;
end;
end;
Pic for Design Time
The only problem is changing the order of the Property in that DFM flow.
Original answer at the bottom, here is a new suggestion:
We actually have something very similar in the JVCL where TJvHotTrackPersistent publishes a HotTrackOptions property.
This property is backed by an instance of TJvHotTrackOptions that gets derived in other classes that need specialized versions of it.
To tell the streaming subsystem to use the actual class found at streaming time, the constructor of that options class calls SetSubComponent(True); which places csSubComponent in the ComponentStyle property.
So what you should do is get rid of your DefineProperties, have TSearchFieldProperties inherit from TComponent and call SetSubComponent(True) in its constructor.
Then you create as many classes derived from TSearchFieldProperties as you need, each with its own set of published properties.
This means you should also get rid of the methods you showed in your submission.
In the end, you should have something along those lines:
type
TSearchFieldProperties = class(TComponent)
public
constructor Create(AOwner: TComponent); override;
end;
TIntegerSearchFieldProperties = class(TSearchFieldProperties)
private
FIntValue: Integer;
published
property IntValue: Integer read FIntValue write FIntValue;
end;
constructor TSearchFieldProperties.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
SetSubComponent(True);
end;
With this you do not fight against the streaming system but rather work with it in the way it is meant to be used.
But if you stop there, you'll notice there is no way for you to specify the actual class name to be used for the TSearchFieldProperties instance used for the TSearchField.Properties property.
The only way to get the class name to be streamed before the subcomponent is streamed is to actually declare the class name as a published property, declared before the subcomponent like this:
type
TSearchField = class(TCollectionItem)
published
// DO NOT change the order of those two properties, PropertiesClassName must come BEFORE Properties for DFM streaming to work properly
property PropertiesClassName: string read GetPropertiesClassName write SetPropertiesClassName;
property Properties: TSearchFieldProperties read FProperties write SetProperties;
end;
function TSearchField.GetPropertiesClassName: string;
begin
Result := Properties.ClassName;
end;
procedure TSearchField.SetPropertiesClassName(const AValue: string);
begin
FProperties.Free; // no need to test for nil, Free already does it
FProperties := TSearchFieldPropertiesClass(FindClass(AValue)).Create(self);
end;
It might work if you just declare the published property like without creating a csSubComponent hierarchy but you'll most likely stumble on other hurdles along the way.
Note: this answer is wrong because DefineProperties is called last in TWriter.WriteProperties and so there is no way to change the order properties defined like this are written.
What if you change your DefineProperties override from this:
procedure TSearchField.DefineProperties(Filer: TFiler);
begin
inherited;
Filer.DefineProperty('PropertiesClassName', ReaderProc, WriterProc, FPropertiesClassName <> '');
end;
to this:
procedure TSearchField.DefineProperties(Filer: TFiler);
begin
Filer.DefineProperty('PropertiesClassName', ReaderProc, WriterProc, FPropertiesClassName <> '');
inherited DefineProperties(Filer);
end;
Basically, call the inherited method AFTER you have defined your own property.
Note that I also specified which inherited method is called. I know it's not required, but it makes intent clearer and allows for Ctrl-Click navigation.
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.
i am working on a custom component in Delphi -7 for which i have some published properties
private
{ Private declarations }
FFolderzip ,Fimagezip,Ftextzip,FActivatezipComp : Boolean;
FMessagebo : string;
published
{ Published declarations }
{component Properties}
{#1.Folder Zip}
property ZipFolder : Boolean read FFolderzip write FFolderzip default False;
{#2.Send imagezip ?}
property ZipImage : Boolean read Fimagezip write Fimagezip default False;
{#3.text files}
property ZipText : Boolean read Ftextzip write Ftextzip default False;
{#4.message}
property ZipMessage: String read FMessagebo write FMessagebo ;
{#5.Activate }
property ActivateZipper: Boolean read FActivatezipComp write FActivatezipComp Default false;
end;
When the user drops the component on the application, ActivateZipper properties give the use a option to activate/enable or deactivate/disable the component from executing.
The component creates a file
so in the constructor i have this, CreateATextFileProc will create the file in the application folder.So if i check in the constructor if the ActivateZipper is true or false..
i have a constructor
constructor TcomProj.Create(aOwner: TComponent);
begin
inherited;
if ActivateZipper then CreateATextFileProc;
end;
The ActivateZipper is always false even if i set it to true in the object inspector.
How can the component be disabled from doing it working with published property?
The constructor is too early. The design-time property values have not been streamed into the component yet. You need to wait until your component's Loaded() method is called before you can then act on the values. If you create the component dynamically at run-time, you also need a property setter since there are no DFM values and thus Loaded() will not be called.
type
TcomProj = class(TComponent)
private
...
procedure SetActivateZipper(Value: Boolean);
protected
procedure Loaded; override;
published
property ActivateZipper: Boolean read FActivatezipComp write SetActivateZipper;
end;
procedure TcomProj.SetActivateZipper(Value: Boolean);
begin
if FActivatezipComp <> Value then
begin
FActivatezipComp := Value;
if ActivateZipper and ((ComponentState * [csDesigning, csLoading, csLoading]) = []) then
CreateATextFileProc;
end;
end;
procedure TcomProj.Loaded;
begin
inherited;
if ActivateZipper then CreateATextFileProc;
end;
ActivateZipper is always False even if I set it to True in the object inspector.
Your activation code is now placed in the constructor. A few things about that:
In the constructor, all private fields are zero (0, '', null, nil, depending on type) initialized. If you do not set it otherwise, then these fields remain zero once the component is created.
When you are changing properties with the object inspector, the component is already created.
Your code will never run.
When you do want to initialize it otherwise at creation, then change also the default storage specifier.
What you need is a property setter that will be invoked whenever the property is changed by means of object inspector or code:
private
procedure SetZipperActive(Value: Boolean);
published
property ZipperActive: Boolean read FZipperActive write SetZipperActive default False;
procedure TcomProj.SetZipperActive(Value: Boolean);
begin
if FZipperActive <> Value then
begin
FZipperActive := Value;
if FZipperActive then
CreateATextFile
else
...
end;
You might consider to turn off this functionality at design-time, since it is likely that you want the actual zipping only at run-time. Then test for the csDesigning flag in ComponentState:
procedure TcomProj.SetZipperActive(Value: Boolean);
begin
if FZipperActive <> Value then
begin
FZipperActive := Value;
if csDesigning in ComponentState then
if FZipperActive then
CreateATextFile
else
...
end;
I'm writing simple component. What I want to achieve is that my MethodOptions will change in Object Inspector according to Method I choose.
Something like this:
So far I coded:
TmyMethod = (cmFirst, cmSecond);
TmyMethodOptions = class(TPersistent)
published
property SomethingInBase: boolean;
end;
TmyMethodOptionsFirst = class(TmyMethodOptions)
published
property SomethingInFirst: boolean;
end;
TmyMethodOptionsSecond = class(TmyTMethodOptions)
published
property SomethingInSecond: boolean;
end;
TmyComponent = class(TComponent)
private
fMethod: TmyMethod;
fMethodOptions: TmyMethodOptions;
procedure ChangeMethod(const Value: TmyMethod);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property Method: TmyMethod read fMethod write ChangeMethod default cmFirst;
property MethodOptions: TmyMethodOptions read fMethodOptions
write fMethodOptions;
end;
implementation
procedure TmyComponent.ChangeMethod(const Value: TmyMethod);
begin
fMethod := Value;
fMethodOptions.Free;
// case...
if Value = cmFirst then
fMethodOptions := TmyMethodOptionsFirst.Create
else
fMethodOptions := TmyMethodOptionsSecond.Create;
// fMethodOptions.Update;
end;
constructor TmyComponent.Create(AOwner: TComponent);
begin
inherited;
fMethodOptions := TmyMethodOptions.Create;
fMethod := cmFirst;
end;
destructor TmyComponent.Destroy;
begin
fMethodOptions.Free;
inherited;
end;
Of course it does almost nothing (except hanging IDE) and I don't have any starting point where to search the suitable knowledge to achieve this.
If I understand correctly I believe that this the same technique the Developer Express implemented in their Quantum Grid component, for dynamically showing different properties for various field types in the grid. There is an explanation of the mechanism here: Technology of the QuantumGrid
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;