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

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;

Related

Don´t save my published property in component frame

I have a Component inherited of the TFrame... But my published properties don't save the values in .dfm... The error occurs when closing Delphi and open again, reloading the project.
This error is: "Error reading MyComponent1.Obs: property Obs does not exists..."
TMyComponent = class(TFrame)
FObs: string;
procedure SetObs(const Value: string);
published
property Obs: string read FObs write SetObs;
end;
procedure register;
implementation
procedure register;
begin
RegisterComponents('My Components', [TMyComponent]);
end;
procedure TMyComponent .SetObs(const Value: string);
begin
if FObs <> Value then
FObs := Value;
end;
I've managed to reproduce the error and then fix it, but can't understand exactly why it happens so, has to do with visual inheritance which works in rather complex way (hope David or Remy could explain what happens here).
First of all, this error indicates that property actually was saved to dfm file. Point is, it's perfectly normal situation when some properties are absent in dfm, it just means that property must have default value (or value which ancestor had), so IDE never raises error because it didn't find some property in file.
Here the opposite happens: property 'obs' was found in dfm, but wasn't found in class itself. The same happened when I created new project and then added frame unit to project itself. It seems, in that case visual inheritance took place instead of normal inheritance, which is: IDE found, that class TMyComponent is described in 'visual' unit belonging to project, found that line:
TMyComponent = class (TFrame)
and made TMyComponent merely alias for TFrame. That's why error occurred: TFrame really doesn't have 'obs' property.
And here is workaround: Don't add this frame to project itself. Instead, add frame from component palette. This way it works as expected. If IDE can't find files of this component, add folder containing them to search path.

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

Freeing focused custom control dynamically causes crash

A custom control that I wrote is implicated in a crash when it is destroyed. It is hard to pin down the exact circumstances and it might be a factor that the control is parented by a 3rd party control.
Edit 8 October 2014
I've now got a much better SSCCE that illustrates the crash using only TMediaPlayer (from the Delphi VCL) on TForm. So I've deleted a lot of what I wrote before. Please see the edit history for that. (It turns out that CM_EXIT in the former call stack was a red-herring.)
Here's the SSCCE:
unit Unit1;
interface
uses
System.Classes, Vcl.Controls, Vcl.Forms, Vcl.Menus, Vcl.MPlayer;
type
TForm1 = class(TForm)
MainMenu: TMainMenu;
CrashMenuItem: TMenuItem;
procedure CrashMenuItemClick(Sender: TObject);
procedure FormShow(Sender: TObject);
private
fControl : TMediaPlayer;
end;
var
Form1: TForm1;
implementation
uses
Vcl.Dialogs;
{$R *.dfm}
procedure TForm1.CrashMenuItemClick(Sender: TObject);
begin
ShowMessage('Message');
fControl.Free;
end;
procedure TForm1.FormShow(Sender: TObject);
begin
fControl := TMediaPlayer.Create(Form1);
fControl.Parent := Form1;
end;
end.
The call to ShowMessage immediately before freeing the control is crucial.
After dismissing the dialog, the TMediaPlayer control gets a WM_SETFOCUS.
It's destructor is then called. Inherited TCustomControl.Destroy frees the canvas and then inherited TWinControl.Destroy calls TWinControl.RemoveFocus, so it gets a WM_KILLFOCUS.
TMediaPlayer.WMKillFocus calls Paint directly, which tries to use the freed canvas and crashes.
(Previously I had a custom control where CMFocusChanged called Invalidate. The effect was the same but the call stack was rather more involved.)
My original 3 questions, that NGLN has answered below:
Am I doing something wrong merely calling FreeAndNil(fMyControl)? Must I unparent it before destroying it? But this doesn't seem necessary with any other controls, so more likely that will just hide the underlying bug.
Should my control have something in its destructor to fix this so that TWinControl knows not to try to repaint it?
Is there perhaps a bug in the 3rd party parent control? Is it the case that my control should certainly not receive a WM_PRINTCLIENT message once it has started to be destroyed? (The 3rd party control seems to make an explicit call to its inherited TWinControl.Update when it receives CM_EXIT as a consequence of my control losing focus.)
But the real question remains: Is there anything wrong with the code in my SSCCE, or is there a bug in the Delphi VCL?
(Incidentally, the same problem will occur with any descendent of TCustomControl. I used TMediaPlayer for convenience.)
Am I doing something wrong merely calling FreeAndNil(fMyControl)?
No, every control should be able to be freed at any given time, as long as all references to the control are cleared (nilled) and the instance's code isn't run anymore.
Must I unparent it before destroying it? But this doesn't seem necessary with any other controls, so more likely that will just hide the underlying bug.
No, indeed no need to.
Should my control have something in its destructor to fix this so that TWinControl knows not to try to repaint it?
No, normally there is no need to. The VCL has this all build in already. For testing purposes or as a (temporary) workaround, you could try to override PaintWindow with something like if not (csDestroying in ComponentState) then.
Is there perhaps a bug in the 3rd party parent control? Is it the case that my control should certainly not receive a WM_PRINTCLIENT message once it has started to be destroyed? (The 3rd party control seems to make an explicit call to its inherited TWinControl.Update when it receives CM_EXIT as a consequence of my control losing focus.)
The parent control indeed receives CM_EXIT, because it had a focussed control, and now it has not anymore (ie. Form.ActiveControl = nil). So that's normal behaviour. As for why the parent sends a WM_PRINTCLIENT to the control (how do you know that request comes from the parent? It seems to start at the Update call.) I do not know. To rule out the possibility of a buggy parent, retry your case with a different parent.
Update (due to question edit):
TMediaPlayer.WMKillFocus calls Paint directly...
procedure TMediaPlayer.WMKillFocus(var Message: TWMKillFocus);
begin
Paint;
end;
That is taboo! That is definitely a bug in the VCL. Paint should never be called directly other than by a request for painting via a WM_PAINT message. I have submitted a report on QC.
(Previously I had a custom control where CMFocusChanged called Invalidate. The effect was the same but the call stack was rather more involved.)
...
(Incidentally, the same problem will occur with any descendent of TCustomControl. I used TMediaPlayer for convenience.)
That is not the case with a test here in D7 and XE2.

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