Component property not considered on runtime in Delphi XE2 - delphi

I am developing a component but I can't make it consider a property set at design-time.
The following is an excerpt of the component:
TRVEditFrame = class(TFrame)
...
private
{ Private declarations }
FRVEditor:TCustomRichView;
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
protected
function GetRVEditor:TCustomRichView;
procedure SetRVEditor(Editor:TCustomRichView);
published
property RVEditor:TCustomRichView read GetRVEditor write SetRVEditor;
end;
...
constructor TRVEditFrame.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
SetRVEditor(FRVEditor);
...
end;
function TRVEditFrame.GetRVEditor:TCustomRichView;
begin
Result:=FRVEditor;
end;
procedure TRVEditFrame.SetRVEditor(Editor:TCustomRichView);
begin
if Assigned(Editor) then begin
FRVEditor:=Editor;
end;
end;
I can register the component, place it in ther form and set FRVEditor on design-time.
Problem is when I run the application the code inside SetRVEditor() is not executed because Editor=nil.
If I was able to set FRVEditor on design-time, how come that it is=nil on run time ? How can I fix this ?
I add here my further comments because the explanation is too long
#Kenneth, thank you for your reply
TCustomRichView is part of a third part component set that manages
hypertext documents and has 4 more specialized descendents and you
are right, TCustomRichView shouldn't be used in a real application.
TRVEditFrame is the component I am developing.
The idea behind my component is to create one single frame (hence the choice of the component TFrame) with menus, shortcuts, popup menus etc to manage each of the 4 TCustomRichView descendents.
This is exactly the reason why I use TCustomRichView: I can "slot" any of the 4 descendents into my component-frame. This is the same principle of TDatasource that can be connected with TTAble and TQuery (they have the same ancestor).
I suppose the reason why the VCL doesn't link RVEditor to the TCustomRichView descendent I set on design-time is because TFrame has no OnCreate event, like TForm for instance.
So far I managed to solve the issue by calling TRVEditFrame.SetRVEditor manually in the TForm.OnCreate that hosts TRVEditFrame but I was wondering if there are better methods to do so and that is why I have asked advice here.
I know you can create a OnCreate event for TFrames as well, maybe I can place TRVEditFrame.SetRVEditor in there but, again, I was wondering if there was a better method.
Regarding the last part of your comment, I am aware of the register procedure but take into account the component is under development. When I develope components I never install them in the IDE because I prefer to keep the test stuff outside the "official" one.
I use this method and as soon as the component is ready then I register it with the procedure you mention. If I want to implement other features to the same component I can work on the test ones and keep on using the "official" one I have in the IDE at the same time.

My suggestion is to change the design in order to get the frame component referencing the custom editor by using the Notification method, instead of manually setting a property. So, change your frame class to
TRVEditFrame = class(TFrame)
...
private
{ Private declarations }
FRVEditor: TCustomRichView;
protected
procedure Notification(aComponent: TComponent; aOperation: TOperation); override;
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
public
property RVEditor:TCustomRichView read FRVEditor;
end;
The implementation of the Notification method does the magic of connecting/disconnecting the frame to the custom editor
procedure TRVEditFrame.Notification(aComponent: TComponent;
aOperation: TOperation);
begin
inherited;
if aComponent is TCustomRichView then
if aOperation=opRemove then begin
if aComponent=FRVEditor then
FRVEditor := nil;
end else
FRVEditor := TCustomRichView(aComponent);
end;
I don´t know if you need any special handling when an editor is set/reset to the frame, so this code does nothing in special, just assigns the component or nil to the FRVEditor data member at the proper moment.

You should create child components.
constructor TRVEditFrame.Create(AOwner: TComponent);
begin
inherited; // Create(AOwner);
FRVEditor := TRichView.Create(self);
end;
DFM streaming engine may load properties of already created class, but it cannot create the class for you for two reasons:
1) DFM can not know of any special tuning done on created component, like what should be constructor parameters (if any), what constructor to use (of many), which niotifications and event handlers to attach and so on.
2) DFM can not know which type the property should be. For example i published TStrings oroperty - object of which class should be created to feel in ? TStrings? TStringList? TRichEditStringList? THashedStringList ? they all were inherited from TStrings and thus any of them is fine choice for DFM - but not for the ocmponent you write.
Thus DFM streaming subsystem is responsible for saving and loading properties of objects, but creating those object is purely your responsibility.
I believe you also may cut the corners by learning how IDE Designer creates your forms and making RVEdit a variable rather than property:
TRVEditFrame = class(TFrame)
...
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
published
var RVEditor:TCustomRichView; // just like users' forms are created by IDE
end;
Then hopefully TFrame constructor would create the content for this variable for you. But this design is fragile because any outer code would be able by any stupid mistake to make something like MyEditFrame.RVEditor := ... and cause a memory leak and unexpected failures of all the attached connections between the editor and the frame.

Related

How to properly publish an event executed from the 'Loaded' procedure?

In a runtime only package, I've defined a TFrame descendant which publishes the OnLoaded event:
type
TMyMethod = procedure() of object;
TMyFrame = class(TFrame)
protected
FOnLoaded : TMyMethod;
procedure Loaded(); override;
published
property OnLoaded : TMyMethod read FOnLoaded write FOnLoaded;
end;
implementation
{$R *.dfm}
procedure TMyFrame.Loaded();
begin
inherited;
if(Assigned(FOnLoaded))
then FOnLoaded();
end;
In a designtime only package, I've registered TMyFrame component as follows:
unit uMyRegistrations;
interface
uses
Classes, uMyFrame;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('MyTestComponents', [
TMyFrame
]);
end;
I've installed the designtime package, I can find TMyFrame in the tool palette and its OnLoaded event is shown in the object inspector.
I've dragged a TMyFrame into a form, then I've assigned the OnLoaded event by doubleclicking from the object inspector.
After assigning the event, I noticed that an access violation error message appears each time I try to open the form's file in Delphi (It let me open the ".pas" file, but I can't switch to visual designer view).
Did I correctly published the OnLoaded event? If so, what else is wrong?
Further Informations:
I'm using Delphi 2007 (don't know if it matters).
The error also appears by doing the same thing with different parent classes (Not only for TFrame descendants).
Updated (somewhat less bogus) answer
You accepted my original answer, but what I wrote was not correct. Rob Kennedy pointed to an article by former Embarcadero developer Allen Bauer on the topic of Assigned.
Allen explains that the Assigned function only tests one pointer of the two pointers in a method pointer. The IDE at design time takes advantage of this by assigning sentinel values to any published method properties (i.e. events). These sentinel values have nil for one of the two pointers in the method pointer (the one that Assigned checks), and an index identifying the property value in the other pointer.
All this means that False is returned when you call Assigned at design time. So long as you check published method pointers with Assigned before calling them, then you will never call them at design time.
So what I originally wrote cannot be true.
So I dug a bit deeper. I used the following very simple code, testing with XE7:
type
TMyControl = class(TGraphicControl)
protected
FSize: Integer;
procedure Loaded; override;
end;
....
procedure TMyControl.Loaded;
begin
inherited;
FSize := InstanceSize;
end;
....
procedure Register;
begin
RegisterComponents('MyTestComponents', [TMyControl]);
end;
This was enough to cause an AV in the IDE at design time whenever the Loaded method was executed.
My conclusion is that the IDE does some rather underhand things when streaming, and your objects are not in a fit state to use when the Loaded method is called. But I don't really have a better understanding than that.
Original (very bogus) answer
You must not execute event handlers at design time, and your code does just that. The reason being that at design time the event handler's code is not available.
The control's code is available, the IDE has loaded it – but the code that implements the event handler is not. That code is not part of the design time package, it is part of the project that is currently open in the IDE. After all, it might not even compile yet!
The Loaded method should defend against this like so:
procedure TMyFrame.Loaded();
begin
inherited;
if not (csDesigning in ComponentState) and Assigned(FOnLoaded) then
FOnLoaded();
end;

Display a warning when dropping a component on a form at design time

I'm tidying up components used in a large legacy project, I've eliminated about 90 of 220 custom components, replacing them with standard Delphi controls. Some of the remaining components require a significant amount work to remove which I don't have available. I would like to prevent anyone from making additional use of some of these components and was wondering if there was a way of showing a message if the component is dropped on the form at design time - something like "Don't use this control, use x or y instead".
Another possibility would to hide the control on the component pallet (but still have the control correctly render on the form at design time).
There is protected dynamic method TComponent.PaletteCreated, which is called only in one case: when we add this component to a form from component palette.
Responds when the component is created from the component palette.
PaletteCreated is called automatically at design time when the component has just been created from the component palette. Component writers can override this method to perform adjustments that are required only when the component is created from the component palette.
As implemented in TComponent, PaletteCreated does nothing.
You can override this method to show warning, so it will alert the user just one time, when he tries to put it to form.
UPDATE
I couldn't make this procedure work in Delphi 7, XE2 and Delphi 10 Seattle (trial version), so it seems that call to PaletteCreated from IDE is not implemented.
I sent report to QC:http://qc.embarcadero.com/wc/qcmain.aspx?d=135152
maybe developers will make it work some day.
UPDATE 2
There are some funny workarounds, I've tried them all this time, works normally. Suppose that TOldBadButton is one of components that shouldn't be used. We override 'Loaded' procedure and WMPaint message handler:
TOldBadButton=class(TButton)
private
fNoNeedToShowWarning: Boolean; //false when created
//some other stuff
protected
procedure Loaded; override;
procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
//some other stuff
end;
and implementation:
procedure TBadOldButton.Loaded;
begin
inherited;
fNoNeedToShowWarning:=true;
end;
procedure TOldBadButton.WMPaint(var Message: TWMPAINT);
begin
inherited;
if (csDesigning in ComponentState) and not fNoNeedToShowWarning then begin
Application.MessageBox('Please, don''t use this component','OldBadButton');
fNoNeedToShowWarning:=true;
end;
end;
The problem is, this works only for visual components. If you have custom dialogs, imagelists etc, they never get WMPaint message. In that case we can add another property, so when it is shown in object inspector, it calls getter and here we display warning. Something like this:
TStupidOpenDialog = class(TOpenDialog)
private
fNoNeedToShowWarning: boolean;
function GetAawPlease: string;
procedure SetAawPlease(value: string);
//some other stuff
protected
procedure Loaded; override;
//some other stuff
published
//with name like this, probably will be on top in property list
property Aaw_please: string read GetAawPlease write SetAawPlease;
end;
implementation:
procedure TStupidOpenDialog.Loaded;
begin
inherited;
fNoNeedToShowWarning:=true; //won't show warning when loading form
end;
procedure TStupidOpenDialog.SetAawPlease(value: string);
begin
//nothing, we need this empty setter, otherwise property won't appear on object
//inspector
end;
function TStupidOpenDialog.GetAawPlease: string;
begin
Result:='Don''t use this component!';
if (csDesigning in ComponentState) and not fNoNeedToShowWarning then begin
Application.MessageBox('Please, don''t use this component','StupidOpenDialog');
fNoNeedToShowWarning:=true;
end;
end;
Older versions of Delphi always scroll object inspector to the top when new component is added from palette, so our Aaw_please property will surely work. Newer versions tend to start with some chosen place in property list, but non-visual components usually have quite a few properties, so it shouldn't be a problem.
To determine when the component is first created (dropped on the form)?
Override "CreateWnd" and use the following if statement in it:
if (csDesigning in ComponentState) and not (csLoading in ComponentState) then
// We have first create
More detail here >>
Link

Override construct error

I'm new into the Delphi coding and I get an error when trying to override a constructor , can you advice me on what I'm doing wrong or what should I do to get to the desired result.
I want to override the constructor of a frame so that it will chance the Caption of an label it containts to a specific text.
here is the code
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls;
type
TfrmMesaj = class(TFrame)
Panel1: TPanel;
private
{ Private declarations }
public
{ Public declarations }
constructor Create(name : string); override;
end;
implementation
{$R *.dfm}
{ TfrmMesaj }
{ TfrmMesaj }
constructor TfrmMesaj.Create(name: string);
begin
inherited;
Panel1.Color := clRed;
Panel1.Caption := name;
end;
end.
When I try to compile I get the following errors :
[DCC Error] frameMesaj.pas(17): E2037 Declaration of 'Create' differs from previous declaration
[DCC Error] frameMesaj.pas(32): E2008 Incompatible types
What Am I doing wrong and how can I achive what I want?
Stefan has explained why your override isn't working. Basically, whenever you override a virtual method the signatures of the two methods must match exactly. However, I'm strongly opposed to the use of reintroduce. And I'll explain why at the bottom of this answer. (Please also note that reintroduce quite specifically does not override the ancestor method. It only hides the warning that the method is hiding the ancestor's method.)
A couple of better options:
Use a different name for your constructor
You don't have to name your constructors Create. You could for example add a second constructor as: constructor CreateWithCaption(AName: string);. Note that I didn't even make this constructor virtual. Only make methods virtual if you intend them to behave polymorphically. (Which means you want subclasses to be able to change the implementation even when called from the base class.)
This option is very much like overload as suggested by Stefan.
Use a factory method to create your frame
As systems get larger it can be useful to separate the processing of creating some objects from the work they actually do. This is done using factory methods whose sole purpose is to create other objects that are ready to interact with the rest of your system. E.g.
//I've chosen to demonsrate this on a form, but you could also implement a dedicated factory class
function TMyForm.CreateMessageFrame(ACaption: string): TFrame;
begin
//Note the factory method intends the form to own all frames created.
Result := TfrmMesaj.Create(Self);
//The factory method ensures the frame is "ready"
//This does violate Law of Demeter, but you could easily add a method to the fram to resolve that.
Result.Panel1.Color := clRed;
Result.Panel1.Caption := ACaption;
end;
What's wrong with reintroduce anway?
Reintroduce is only applicable when the method on the base class is virtual.
The method should only be virtual if it's intended to be used polymorphically.
This means that the method is intended to be called from a base class reference, but might need to take special action to work correctly in some subclasses.
Since your question was dealing with overriding TComponent.Create, it will serve very nicely to illustrate by way of example.
constructor TComponent.Create(AOwner: TComponent); is virtual quite specifically so component creation behaves polymorphically. This is so that when components are streamed from a .DFM file they will be created correctly even though the reference used to create them is of type TComponent.
If you hide this constructor, then any special actions you need to take when your frame is streamed and created from a .DFM will not happen.
Also any subclasses of your frame will not be able to override constructor Create(AOwner: TComponent); because it is hidden.
The constructor of TFrame looks like this:
constructor Create(AOwner: TComponent); virtual;
If you want to override it you must keep the signature:
constructor Create(AOwner: TComponent); override;
If you want to add your own version providing a name you need to overload it so that both versions exist side by side:
constructor Create(name: string); reintroduce; overload; // see Edit below
If you want to hide the virtual one you need to write reintroduce (not recommended):
constructor Create(name: string); reintroduce;
Edit:
reintroduce is also needed when you overload a virtual method even without hiding it. This has been reported and discussed here: http://qc.embarcadero.com/wc/qcmain.aspx?d=106026
Why don't you simply use OnCreeate event instead of trying to override default Constructor method.
In your case OnCreate event would be enough since you are not making any critical changes to the TFrame component itself but only to other components that have been placed on it.

Using Child's events

I have a custom component (TScrollBox) that when dropped on a form, it will add a label inside the ScrollBox. How can I disable the ScrollBox's events (onClick, OnMouseDown, ect..) and instead enable the events for the child (Tlabel)
unit MyScrollBox;
interface
uses
System.SysUtils, System.Classes, Vcl.Controls, Vcl.Forms, Vcl.StdCtrls;
type
TMyScrollComponent = class(TScrollBox)
private
FLabel : TLabel;
procedure SetLabelText(AText : string);
function GetLabelText : string;
protected
constructor Create(AOwner : TComponent); override;
published
property LabelText : string read GetLabelText write SetLabelText;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Samples', [TMyScrollComponent]);
end;
constructor TMyScrollComponent.Create(AOwner : TComponent);
begin
inherited;
FLabel := TLabel.Create(self);
FLabel.Parent := self;
FLabel.Caption := 'Hello From Scrollbox!';
end;
procedure TMyScrollComponent.SetLabelText(AText : string);
begin
FLabel.Caption := AText;
end;
function TMyScrollComponent.GetLabelText : string;
begin
result := FLabel.Caption;
end;
end.
The published events in TScrollBox cannot be suppressed in derived classes. So, treating your question literally, there is no way to achieve what you ask.
What you could do is derive from TScrollingWinControl. This is the ancestor to TScrollBox. It doesn't publish the events that you want to associate with the control contained in your scroll box.
Then you can surface events in your custom control that are connected to the control contained in your custom control.
Judging from your recent questions I cannot help in thinking that your approach is wrong. I feel that you should have a custom control that has built in scrolling capability.
The event handlers for TControls are declared as protected and dynamic. Redeclare them using the override directive in your derived class - see TScrollBox Members Protected Methods;
To override MouseDown, add the MouseDown method to the TDBCalendar class and many other pages.
But: If you want to implement your own new events you'd have to do something like this:
...
private
fNewEvent:TNotifyEvent;
procedure setNewEvent(notify:TNotifyEvent);
function getNewEvent:TNotifyEvent;
procedure DoOnNewEvent;
....
published
property OnNewEvent:TNotifyEvent read getNewEvent write setNewEvent;
i.e. - You need to implement a property of a method type, like TNotifyEvent which is built into Delphi. You can also create your own if you need to. If you want to see your event in the IDE like other Delphi components' events, you must declare it as published.
Then: In your new component implementation section do something like this:
procedure TMyclass.DoOnNewEvent;
begin
if assigned (fNewEvent) then
begin
....doStuff...
fNewEvent(self);
end;
end;
You call DoOnNewEvent when the event that you want to control 'happens' in your code, so that the function assigned to fNewEvent will get called at that point in your code. (This is commonly known as a callback - when something "happens" in module A it calls back into module B letting it know that it happened, etc.)
If you want to define new GUI behavior, you have to examine the controls you're interested in and understand how to capture their actual "physical" events - i.e. when did the scrollbar scroll, when was the mouse clicked, and when that happens you call your DoOnNewEvent method. (This generally involves inspecting Windows messages coming into your application, "message cracking",etc - these messages inform your application of what's happening in "the outside world".)
In your consumer class, for example your main form where you're putting your scroll box, once you successfully publish your new event, you will see your event in the IDE on your new component, and you assign it and define the behavior you want for it in your consumer class, just like any other event in the IDE.
Take a look at the VCL source code for a simple component to get a better idea of what it looks like.
But: That's only if you really need your own new published events because overriding the parent's events is not sufficient for your needs.

Can you remove a published property from a delphi component and not cause errors on the forms where the component is used?

I have a component that was developed by my company, and would like to remove one of the published properties that is no longer needed and clashes with the way the component works now.
Is there a way to remove the property and not cause property not found errors at runtime or design time when forms that use the component are loaded?
i.e. Is there a way to make Delphi silently drop a component property?
Yes. Just remove the property, then override DefineProperties and process it there. That will satisfy the streaming system by loading the value which you can just throw away. Be sure to do nothing when writing the stream.
Depending on the property, the easiest would be to leave the property, but mark it as deprecated and just have the Read/Write bits point to a field that's never used.
Alternatively, you can override DefineProperties and call Filer.DefineProperty('PropertyName', ReadProc); where PropertyName is the property you've removed, and ReadProc is a function that calls various TReader Read* functions. This has the advantage that the properties aren't in your interface anymore.
For example, say you've removed this property:
property Center: TPoint read FPoint write SetPoint;
Here's what you would add to your component:
TMyComponent = class...
private
procedure SkipReadPoint(Reader: TReader);
protected
procedure DefineProperties(Filer: TFiler); override;
end;
procedure TMyComponent.DefineProperties(Filer: TFiler);
begin
inherited;
Filer.DefineProperty('Center', SkipReadPoint, nil, False);
end;
procedure TMyComponent.SkipReadPoint(Reader: TReader);
begin
Reader.ReadListBegin;
Reader.ReadInteger;
Reader.ReadInteger;
Reader.ReadListEnd;
end;
Skipping a simple type like Boolean or Integer is easier, since you can just call ReadBoolean or ReadInteger without bothering with the ReadList functions.
In our case we had a lot of similar properties across a bunch of classes (for CLX compatibility) so we had global "dummy" functions like this:
procedure DummyReadBool(Self: Pointer; Reader: TReader);
begin
Reader.ReadBoolean;
end;
const
SkipReadBool: TMethod = (Code: #DummyReadBool; Data: nil);
and then the DefineProperty call looks like this:
Filer.DefineProperty('PropName', TReaderProc(SkipReadBool), nil, False);
That way each class doesn't have to have duplicate Skip* functions.
You can't "silently" remove it, if it's stored in the .DFM or referenced in code.
If it wasn't referenced in code, and it's stored in text .DFM files, the JVCL has a utility called DFM Cleaner that will go through the .DFMs and remove the properties for you. Then you can open the forms and recompile your applications safely. It's part of the JVCL and is placed in the $(JVCL)\DevTools folder during install.

Resources