Why isn't my TFrame "seeing" a posted message? - delphi

I just recently begun using TFrames heavily (OK, yes, I've been living under a rock...). I thought frames supported Message hander method declaration--and I've seen many examples of that. So why does this simple test unit for a TFrame never see the message it posts to itself? (I created the test when I figured out that message handlers weren't being called in my larger application.)
unit JunkFrame;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls;
const
DO_FORM_INITS = WM_USER + 99;
type
TFrame1 = class(TFrame)
Panel1: TPanel;
private
procedure DoFormInits(var Msg: TMessage); message DO_FORM_INITS;
public
constructor Create(AOwner: TComponent); override;
end;
implementation
{$R *.dfm}
constructor TFrame1.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
PostMessage(self.Handle, DO_FORM_INITS, 0, 0);
end;
procedure TFrame1.DoFormInits(var Msg: TMessage);
begin
ShowMessage('In DoFormInits!');
end;
end.
This frame only contains a TPanel, and the frame is used on a simple mainform which contains only the frame and a Close button.
What am I missing?

I see two possibilities:
Your program hasn't started processing messages yet. Posted messages are only processed when your program calls GetMessage or PeekMessage and then DispatchMessage. That occurs inside Application.Run, so if your program hasn't gotten there yet, then it won't process any posted messages.
Your frame's window handle has been destroyed and re-created. Accessing the Handle property forces the frame's window handle to be created, but if the frame's parent hasn't quite stabilized yet, then it might destroy its own window handle and re-create it. That forces all its children to do the same, so the handle you posted the message to doesn't exist by the time your program starts processing messages.
To fix the first problem, just wait. Your program will start processing messages eventually. To fix the second problem, override your frame's CreateWnd method and post the message there. That method gets called after the window handle has been created, so you avoid forcing the handle to be created prematurely. It's still possible for the handle to be destroyed and re-created, though, and CreateWnd will be called each time that happens, so you'll need to be careful since your initialization message might be posted more than once (but never to the same window handle multiple times). Whether that's correct depends on what kind of initialzation you need to do.

The only explanation for this that I can come up with is that your frame's handle is recreated after you post the message and before the message queue is pumped. Try posting in an OnShow.

Related

What is the difference between DefaultHandler(Message) and "inherited" in message handlers?

What exactly is the difference between calling DefaultHandler(Message) and inherited in a message handlers. e.g:
TScrollBox = class(TScrollingWinControl)
private
...
procedure WMNCHitTest(var Message: TMessage); message WM_NCHITTEST;
...
end;
procedure TScrollBox.WMNCHitTest(var Message: TMessage);
begin
DefaultHandler(Message);
end;
Why not call inherited here? when should I use either?
Let us consider the example that you provide. Suppose that instead of calling DefaultHandler, inherited was called (or equivalently no WM_NCHITTEST message handler was implemented by TScrollBox). In that scenario the message would be processed by TWinControl in this method:
procedure TWinControl.WMNCHitTest(var Message: TWMNCHitTest);
begin
with Message do
if (csDesigning in ComponentState) and (FParent <> nil) then
Result := HTCLIENT
else
inherited;
end;
This performs special treatment when designing, otherwise it calls inherited which ultimately leads to a call to DefaultHandler which forwards the message to the default window procedure.
Now, TScrollBox replaces that message handler with this one:
procedure TScrollBox.WMNCHitTest(var Message: TMessage);
begin
DefaultHandler(Message);
end;
That calls DefaultHandler directly, and so unconditionally forwards the message to the default window procedure.
So, what we conclude from this is that TScrollBox.WMNCHitTest simply changes behaviour at designtime by suppressing the code in TWinControl.WMNCHitTest which returns HTCLIENT.
I presume therefore that this was the motivation of the author of this VCL code. By using DefaultHandler the behaviour of the underlying window (as determined by the default window procedure) is restored, removing any behaviour modifications implemented by the intervening VCL code.
It is very hard to be sure in specific cases. DefaultHandler handles (in theory) all messages where a message handler handles only one. Default Handler is a public virtual method and so can be overridden explicitly. Message handlers are not declared virtual, and are usually private anyway, so for example in your code if you replace
inherited;
by
inherited WMNCHitTest( message );
the compile will fail.
The other issue is which ancestor overrides what. So one ancestor may override the Message Handler, and another may override Default Handler, which makes it very confusing without a lot of digging.
So I guess for me I have to fall back on what seems best for documentation. Which means, in general, I would use inherited, because I am falling back on the ancestors implementation of this particular message, not the ancestors implementation for messages in general.
Which I guess is another way of saying - I would go with Victoria's approach.

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;

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.

accessing components of frame1 with frame2, with frame2 being on frame1, in delphi, frames are created dynamically

Originally the question had to be how to access the components in the first place, however I somehow managed to figure it out. I am just learning Delphi so I am prone to dumb and obvious questions. I am also at a stage when I'm not actually writing anything useful, but just messing with random stuff to see how it works and maybe learn something.
Wall of text incoming, i want to explain what i am exploring at the moment...
Basically i have a form1 with a button1, pressing it creates a frame2, that frame2 has a button2, pressing button2 creates a frame3 within frame2 (it being frame3's parent and owner). Each frame has an another freeandnil-button. Upon pressing each button1/2/3, it gets disabled to prevent creating multiple instances. My original problem was that after using freeandnil-button, I couldnt access the button on the previous frame (it worked fine for forms, form1.button1.enabled:=true works fine from within frame2) that got disabled in order to re-enable it (frame2.button1.enabled:=true from within frame3 creates an access violation, I think).
Suppose I write something in the future that requires such communication? So I added an editbox to each frame, with a button on the other to change the editbox text, this is my current working solution:
procedure TFrame2.Button3Click(Sender: TObject);
var i,z:integer;
begin
for i := 0 to ComponentCount - 1 do
if components[i] is tframe3 then
for z := 0 to (components[i] as tframe3).ComponentCount - 1 do
if (components[i] as tframe3).Components[z] is TEdit then
((components[i] as tframe3).Components[z] as TEdit).Text:='ping';
end;
and
procedure TFrame3.Button3Click(Sender: TObject);
var i:integer;
begin
for i := 0 to parent.ComponentCount-1 do
if parent.components[i] is TEdit then
(parent.Components[i] as TEdit).Text:='pong';
end;
If I have a bunch of editboxes or whatever the hell, I suppose I could use Tag property to identify them, however, this much component counting and passing something AS something doesn't really look right or efficient enough to me.
My questions at the moment are: can it be done in a better way? and can someone provide the reasoning why cant I access "parent-frame" components from a "child-frame" in a dumb way (ie: frame2.button1.enabled:=true from within frame3)?
A couple of points:
Controls/Components are usually set to be owned by the form/frame that controls their lifetime and parented to the control that shows them. So when you create a TEdit to sit on a TPanel on a TForm, you would set TForm as the owner and TPanel as the parent of the TEdit. With frames you do the same: the frame is the owner of all controls on it, controls are parented to whatever container on the frame (can be the frame itself) is holding and and is thus reponsible for showing (painting) it.
When you iterate over the children of a control, iterating over Components uses the Owner relation; iterating over Controls uses the Parent relation. So your code above would already do a lot less if it were to iterate over the Controls.
Of course you can reference other controls in a "dumb" way, but you will have to provide for the access method. If you want others (not just child frames) you will at the very least have to declare a method to retrieve that control. Many ways to do this. One is to "ask" the form when you need it (using a method on the form), or have the form set a property on the frame when it is created...
Avoid accessing form's and frame's published properties. They are mainly there for Delphi's streaming system. When you tie your application together using your approach described above it can very soon become one incredibly tangled mess...
Example coming up (sometime this evening), it will take me a bit of time to also explain and I have work to do...
The following example deals only with the ping-pong game between the frames. Freeing any form or frame from one of its own event handlers is not such a good idea. Use the Release method for that as it prevents the form/frame from processing messages after is was freed. (Plenty of questions about this on SO by the way). Also, when you do release a frame from one of its own buttons, you will need to take care that the frame that created it has a chance to nil the references it may have held to that frame otherwise you are setting yourself up for some interesting to debug mayhem. Look into "Notification" and "NotifyControls" and the automatic notification by forms and frames that is sent to their owner/parent so these can remove the control from their components/controls collection. In your example, if you were to release Frame3 from its own "FreeAndNil" button's OnClick event handler, you would have to make sure that the Frame2 responds to the deletion (I think) notification message and nil's any references it holds to Frame3 (besides the ones that will already be cleared automatically in the components/controls collections).
Now, the ping pong game. There are a couple of ways to go about this.
Method 1
The first way is what you already tried with a loop over the components of the other frame. While it certainly is a way to avoid having to "use" the other frame, it is cumbersome and not very concise. Plus when you get more controls on your forms/frames, you will have to add a check on the name to know that you have the correct TEdit. And then you might just as well use the name directly, especially as one frame already has the other frame in its uses clause because it is creating it.
// PingFrame (your Frame2)
uses
...
Pong_fr;
type
TPingFrame = class(TFrame)
...
procedure CreateChildBtnClick(Sender: TObject);
procedure PingPongBtnClick(Sender: TObject);
private
FPong: TPongFrame; // This is the "extra" reference you need to nil when
// freeing the TPongFrame from one of its own event handlers.
...
end;
procedure TPingFrame.CreateChildBtnClick(Sender: TObject);
begin
CreateChildBtn.Enabled := False;
FPong := TPongFrame.Create(Self);
FPong.Parent := ContainerPnl;
FPong.Align := alClient;
end;
procedure TPingFrame.PingPongBtnClick(Sender: TObject);
begin
if Assigned(FPong) then
FPong.Edit1.Text := 'Ping';
end;
And on the other end:
// PongFrame (your Frame3)
type
TPongFrame = class(TFrame)
...
procedure PingPongBtnClick(Sender: TObject);
end;
implementation
uses
Ping_fr;
procedure TPongFrame.PingPong1BtnClick(Sender: TObject);
begin
(Owner as TPingFrame).Edit1.Text := 'Pong called';
end;
This method seems all nice and dandy, but it has drawbacks:
You need to use the unit that holds the TPingFrame (Ping_fr in this example). Not too bad I guess, but... as Ping_fr already uses Pong_fr (the unit holding TPongFrame), you can't have Ping_fr using Pong_fr in the interface section and have Pong_fr using Ping_fr in the interface section as well. Doing so would have Delphi throwing an error because of a circular references. This can be solved by moving one of the uses to the implementation section (as done in the example for the use of Ping_fr in the Pong_fr unit.
A bigger drawback is that there is now a very tight coupling between the two frames. You cannot change the TEdit in either one to another type of control (unless that happens to have a Text property as well) without having to change the code in the other unit as well. Such tight coupling is a cause of major headaches and it is good practice to try and avoid it.
Method 2
One way to decrease the coupling between the frames and allow each frame to change it controls as it sees fit is not to use the controls of another form/frame directly. To do so you declare a method on each frame that the other can call. Each method updates its own controls. And from the OnClick event handlers you no longer access the other frame's controls directly, but you call that method
type
TPingFrame = class(TFrame)
...
public
procedure Ping;
end;
implementation
procedure TPingFrame.PingPongBtnClick(Sender: TObject);
begin
if Assigned(FPong) then
FPong.Ping;
end;
procedure TPingFrame.Ping;
begin
Edit1.Text := 'Someone pinged me';
end;
And on the other end:
type
TPongFrame = class(TFrame)
...
public
procedure Ping;
end;
implementation
procedure TPongFrame.PingPongBtnClick(Sender: TObject);
begin
(Owner as TPingFrame).Ping;
end;
procedure TPongFrame.Ping;
begin
Edit1.Text := 'Someone pinged me';
end;
This is better than Method 1 as it allows both frames to change their controls without having to worryabout "outsiders" referencing them, but still has the drawback of the circular reference that can only be "solved" by moving one "use" to the implementation section.
It is a good practice to try and avoid circular references altogether instead of patching them by moving units to the implementation section's uses clause. A rule of thumb I use is:
Any form/frame may know and use the public interface of the forms/frames it instantiates (though it should avoid the controls in the "default visibility" part of that interface), but no form/frame should not have any knowledge of the specific form/frame that created it.
Method 3
One way of achieving this (there are many) it to use events just like the TButton's OnClick event.
On the TPongFrame side of things you can remove the use of Ping_fr. You no longer need it.
Then you need to declare a property and the field it references and declare a method to fire the event.
type
TPongFrame = class(TFrame)
private
FOnPingPongClicked: TNotifyEvent;
protected
procedure DoPingPongClicked;
public
property OnPingPongClicked: TNotifyEvent
read FOnPingPongClicked write FOnPingPongClicked;
end;
The Ping method stays and its implementation is unchanged. The PingPongBtnClick event handler also stays, but its implementation now becomes:
procedure TPongFrame.PingPongBtnClick(Sender: TObject);
begin
DoPingPongClicked;
end;
procedure TPongFrame.DoPingPongClicked;
begin
if Assigned(FOnPingPongClicked) then
FOnPingPongClicked(Self);
end;
You could put the code from DoPingPongClicked straight in here, but it is a good practice to fire an event in a separate method. It avoids duplicating the exact same code if you would have an event that can be fired from multiple parts of your code. And it also allows descendants (when you get those) to override the "firing" method (you'll have to mark it virtual in the ancestor) and do something specific all the times the event is fired.
On the TPingFrame side of things you need to code a handler for the new event:
type
TPingFrame = class(TFrame)
...
protected
procedure HandleOnPingPongClicked(Sender: TObject);
Note that the signature of this handler needs to be what the TNotifyEvent specifies. And of course you need to ensure that this event handler gets called when the event fires in TPongFrame:
procedure TPingFrame.CreateChildBtnClick(Sender: TObject);
begin
CreateChildBtn.Enabled := False;
FPong := TPongFrame.Create(Self);
FPong.Parent := ContainerPnl;
FPong.Align := alClient;
// Listen to event fired by PongFrame whenever it's PingPingBtn is clicked
FPong.OnPingPongClicked := HandleOnPingPongClicked;
end;
In the handler code you can do what you need to do. It can be general:
procedure TPingFrame.HandleOnPingPongClicked(Sender: TObject);
begin
Edit1.Text := 'OnPingPongClicked event was fired';
end;
And of course you can also use the fact that the event passes a reference to the instance firing the event as well:
procedure TPingFrame.HandleOnPingPongClicked(Sender: TObject);
var
Pong: TPongFrame;
begin
// This checks that Sender actually is a TPongFrame and throws an exception if not
Pong := Sender as TPongFrame;
// Use properties from the Pong instance that was passed in
Edit1.Text := 'OnPingPongClicked event was fired by ' + Pong.Name;
end;
Enjoy!

ExitProcess from the OnShow event of MainForm in Delphi

I have an application that on startup checks some conditions and launches an external program in the OnShow event of the Main Form. The problem is that if there is an error when launching the external program, I want the application to terminate immediately. But there is an issue with that, in that EurekaLog catches my exceptions and somehow disrupts the message loop there by negating all calls to Application.Teminate and any other normal shutdown methods.
So here is my question, would ExitProcess be the best route then to immediately terminating my application when this condition exists?
By the time OnShow has fired, you're too far into the program to decide that you don't really want the program to run. You should make that determination sooner. OnShow is not the place to decide that the form shouldn't be shown.
This is the sort of thing you should check before you even create the main form. Put your checks in the DPR file, and if you determine that the program shouldn't run, then simply call exit.
begin
Application.Initialize;
if not ApplicationShouldReallyStart then
exit;
Application.CreateForm(TMainAppForm, MainAppForm);
Application.Run;
end.
Fill in your own implementation of ApplicationShouldReallyStart. (And it really should be a separate function, not in-line in the DPR file. The IDE gets confused if the begin-end block in the DPR file gets too complex.)
Aside from that, do not call ExitProcess. Call Halt instead. Halt calls ExitProcess, but it also calls unit finalization sections and other Delphi-specific process-shutdown tasks.
Work WITH the system, not AGAINST it! You can't simply die in the middle of things. If you want to die do it within the rules--WM_CLOSE or maybe your own routine that says why it's dying and then sends a WM_CLOSE.
You better send a wmClose message to the window. Else you have a big chance to get into trouble because of other messages send to the form.
I wrote a small application to test a theory and here is what I would suggest.
Call the CLOSE method.
The following example unit closes the application with no errors in D2009.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TForm1 = class(TForm)
procedure FormShow(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormShow(Sender: TObject);
begin
close;
end;
end.
While I fully agree with Rob Kennedy here, I want to note, that you may use EurekaLog's routines to control error dialog behaviour.
For example:
uses
ExceptionLog, ECore;
...
begin
ForceApplicationTermination(tbTerminate);
// ... <- Bad code goes there
end;
That way, the application will be closed right after displaying error dialog.

Resources