Detect help button click in MessageBox? - delphi

In Delphi XE7, I need to use the Help button in a MessageBox. MSDN states:
MB_HELP 0x00004000L Adds a Help button to the message box. When the
user clicks the Help button or presses F1, the system sends a WM_HELP
message to the owner.
However, when I click the Help button in the MessageBox, no WM_HELP message seems to be sent to the application:
procedure TForm1.ApplicationEvents1Message(var Msg: tagMSG; var Handled: Boolean);
begin
if Msg.message = WM_HELP then
CodeSite.Send('ApplicationEvents1Message WM_HELP');
end;
procedure TForm1.btnShowMessageBoxClick(Sender: TObject);
begin
MessageBox(Self.Handle, 'Let''s test the Help button.', 'Test', MB_ICONINFORMATION or MB_OK or MB_HELP);
end;
So how can I get the MessageBox Help button click and how can I detect which MessageBox did it come from?

The documentation says, with my emphasis:
the system sends a WM_HELP message to the owner.
That is MSDN code for the fact that the message is delivered synchronously directly to the window procedure. In other words, it has been sent using SendMessage, or an equivalent API.
You've attempted to handle it in TApplicationEvents.OnMessage which is used to intercept asynchronous messages. That is messages that are placed on the message queue. These messages are (typically) placed on the queue with PostMessage.
So the reason for you never seeing the message in TApplicationEvents.OnMessage is that the message is never placed in the queue. Instead you need to handle the message in the owner window's window procedure. In Delphi the simplest way to do that is as follows:
type
TForm1 = class(TForm)
....
protected
procedure WMHelp(var Message: TWMHelp); message WM_HELP;
end;
....
procedure TForm1.WMHelp(var Message: TWMHelp);
begin
// your code goes here
end;
As for how to detect which message box was responsible for the message being sent, there's no simple way when using MessageBox. Perhaps the best would be to switch to MessageBoxIndirect. That allows you to specify an ID in the dwContextHelpId field of MSGBOXPARAMS. That ID is passed to the recipient of the WM_HELP message, as described in the documentation.
If you are going to display a topic an a help file, in response to the user pressing the help button, then you might consider the VCL function MessageDlg. That allows you to pass a help context ID, and the framework will show the application help file, passing on that help context ID.

Minimal working sample:
type
TForm20 = class(TForm)
procedure FormCreate(Sender: TObject);
protected
procedure WMHelp(var Message: TWMHelp); message WM_HELP;
end;
procedure TForm20.FormCreate(Sender: TObject);
begin
MessageBox(Handle, 'Help test', nil, MB_OK or MB_HELP);
end;
procedure TForm20.WMHelp(var Message: TWMHelp);
begin
Caption := 'Help button works';
end;

Related

PostMessage for all instances of a specific form (ClassName):

In a VCL Forms program, I have a Form that implements a method for handling windows messages and updating some controls on the Form, something like:
procedure OnMsgTest (var Msg: TMessage); message WM_CUSTOMTEST;
I use PostMessage with a custom message to this Form, using a code like this:
h := FindWindow('TFrmTest', nil);
if IsWindow(h) then begin
PostMessage(h, WM_CUSTOMTEST, 0, 0);
end;
When the Form is instantiated several times, using the above code to send the message, only one Form instance updates the information on the screen. I would like all open and instantiated Forms to receive the message.
An important note: PostMessage can occur within the Form process itself, but also from another process. So, I believe a loop through the Forms would not work.
What would be the best approach to reach my goal?
You would have to enumerate all running top-level windows, posting the message to each matching window individually. You could use EnumWindows() or a FindWindow/Ex() loop for that, but a simpler solution is to use PostMessage(HWND_BROADCAST) to broadcast a message that is registered with RegisterWindowMessage(). Only windows that handle the registered message will react to it, other windows will simply ignore it. For example:
type
TMyForm = class(TForm)
protected
procedure WndProc(var Msg: TMessage); override;
end;
...
var
WM_CUSTOMTEST: UINT = 0;
procedure TMyForm.WndProc(var Msg: TMessage);
begin
if (Msg.Msg = WM_CUSTOMTEST) and (WM_CUSTOMTEST <> 0) then
begin
...
end else
inherited;
end;
initialization
WM_CUSTOMTEST := RegisterWindowMessage('SomeUniqueNameHere');
Then you can do this when needed:
if WM_CUSTOMTEST <> 0 then
PostMessage(HWND_BROADCAST, WM_CUSTOMTEST, 0, 0);

Is possible to use messages in a class procedure?

I want use messagesin my program and i've a question: Can I use messages in a class procedure or Can I use messages in a procedure without class?
Here is my code:
const
WM_CUSTOM_TCP_CLIENT = WM_USER + 10;
type
TFeedbackEvent = class
public
class procedure feedback(var msg: TMessage); message WM_CUSTOM_TCP_CLIENT;
end;
The Delphi returns the following message:
[Error] unit.pas(33): Invalid message parameter list
Thank you very much.
There is a very nice article on the topic: Handling Messages in Delphi 6. This is a must read.
Handling or processing a message means that your application responds
in some manner to a Windows message. In a standard Windows
application, message handling is performed in each window procedure.
By internalizing the window procedure, however, Delphi makes it much
easier to handle individual messages; instead of having one procedure
that handles all messages, each message has its own procedure. Three
requirements must be met for a procedure to be a message-handling
procedure:
The procedure must be a method of an object.
The procedure must take one var parameter of a TMessage or other message-specific record type.
The procedure must use the message directive followed by the constant value of the message you want to process.
As you can read in the article, the procedure must be a method of an object, not a class. So you cannot just use message handlers in a class procedure.
A possible workaround to handle messages in a class instance (also in object instance or window-less applications), is to manually create window handle via AllocateHWND, and process messages yourself via a WndProc procedure.
There is a good example on this in delphi.about.com: Sending messages to non-windowed applications (Page 2):
The following sample is a version of the above example, modified to work with class method. (If using class method is not really required, use original example from the link above instead):
First, you need to declare a window handle field and a WndProc procedure:
TFeedbackEvent = class
private
FHandle: HWND;
protected
class procedure ClassWndProc(var msg: TMessage);
end;
procedure WndProc(var msg: TMessage);
Then, process the messages manually:
procedure WndProc(var msg: TMessage);
begin
TFeedbackEvent.ClassWndProc(msg);
end;
procedure TFeedbackEvent.ClassWndProc(var msg: TMessage);
begin
if msg.Msg = WM_CUSTOM_TCP_CLIENT then
// TODO: Handle your message
else
// Let default handler process other messages
msg.Result := DefWindowProc(FHandle, msg.Msg, msg.wParam, msg.lParam);
end;
Finally, at the end of the file, declare initialization and finalization section to create/destroy the handle:
initialization
FHandle := AllocateHWND(WndProc);
finalization
DeallocateHWnd(FHandle);
Of course, this is not the recommended way to do this (especially watch for problems with initialization/finalization), it was just an example to show that it is possible.
Unless you have some very strange requirement to use class method, its better to use regular class method and object constructor and destructor instead initialization and finalization sections (as shown in Sending messages to non-windowed applications (Page 2)).

delphi: how to trap/remove all error messages? [duplicate]

I have run into an issue a couple of weeks ago that appear to have no logical explanation. I'm building an application with Delphi 2007 using AlphaControls and a WebBrowser component placed on a form. The TWebBrowser fetches a banner from the web and displays it into the UI. bad thing is that as soon as the form with the banner is displayed, I get the "Could not obtain OLE Control window handle", while the browser is being displayed outside of the form, in the top left corner of the desktop.
I've been trying basically anything to figure it out, but the debugger does not provide too much information about what's going on (that's all I get: First chance exception at $770C4B32. Exception class EOleError with message 'Could not obtain OLE control window handle'. Process project1.exe (3700)). Funny thing is that the same TWebBrowser on Form1 of a new project works without any issues.
Any thoughts on that would be highly appreciated.
It is caused by the html form being closed. The vendor's forums show some code that will fix the problem.
http://www.bsalsa.com/forum/showthread.php?t=255
Set Cancel to True in the OnWindowClosing event and navigate to an
empty page if it is the main webbrowser. In case your webbrowser is a
popup window, you may want to close the form the EWB is on.
procedure TForm2.EmbeddedWB1WindowClosing(ASender: TObject; IsChildWindow: WordBool; var Cancel: WordBool);
begin
Cancel := True;
(ASender as TEmbeddedWB).GoAboutBlank;
end;
TWebBrowser is still being focused as ActiveControl and TOleControl.HookControlWndProc is being called on a ActiveControl which is no longer in the memory. As a result EOleError exception is raised because the window handle cannot be obtained. You can avoid this by setting ActiveControl to nil (changing the active control focus) prior to shutting down the application.
ActiveControl := nil;
This is the function which causes the exception (OleCtrls.pas):
procedure TOleControl.HookControlWndProc;
var
WndHandle: HWnd;
begin
if (FOleInPlaceObject <> nil) and (WindowHandle = 0) then
begin
WndHandle := 0;
FOleInPlaceObject.GetWindow(WndHandle);
// Exception is raised here because WndHandle could not be obtained
if WndHandle = 0 then raise EOleError.CreateRes(#SNoWindowHandle);
WindowHandle := WndHandle;
DefWndProc := Pointer(GetWindowLong(WindowHandle, GWL_WNDPROC));
CreationControl := Self;
SetWindowLong(WindowHandle, GWL_WNDPROC, Longint(#InitWndProc));
SendMessage(WindowHandle, WM_NULL, 0, 0);
end;
end;
Another way is to trap WM_PARENTNOTIFY message with the parameter WM_DESTROY when the destroy message is being sent to TWebBrowser handle because the parent form (where TWebBrowser is nested in) gets a WM_PARENTNOTIFY message:
procedure ParentNotify(var Msg: TMessage); message WM_PARENTNOTIFY;
implementation of message handler:
procedure TMyForm.ParentNotify(Var Msg: TMessage);
begin
if (Msg.WParamLo = WM_DESTROY) and (Msg.LParam = mywebbrowser.Handle) then close;
end;

Catch onMinimize Event For a Form (Delphi)

I found 2 ways for catching onMinimize event.
First: On FormResize event:
if MyForm.WindowState = wsMinimized then ......
Second: Declaring the message handler like this:
procedure WMSize(var Msg: TMessage); message WM_SIZE;
And then:
procedure TForm57.WMSize(var Msg: TMessage);
begin
if Msg.WParam = SIZE_MINIMIZED then ....
end;
Which way is better?!
OnResize is fired in response to the same message (WM_SIZE). Unless you need to react before the VCL handles the message (update scrollbars, align controls etc.), you don't need to attach a message handler. Otherwise, be sure to handle it before the inherited call (which is missing in your sample).
second is better. as WindowState is not necessarily wsMinimized.

OLE Control window handle error with WebBrowser and Delphi 2007

I have run into an issue a couple of weeks ago that appear to have no logical explanation. I'm building an application with Delphi 2007 using AlphaControls and a WebBrowser component placed on a form. The TWebBrowser fetches a banner from the web and displays it into the UI. bad thing is that as soon as the form with the banner is displayed, I get the "Could not obtain OLE Control window handle", while the browser is being displayed outside of the form, in the top left corner of the desktop.
I've been trying basically anything to figure it out, but the debugger does not provide too much information about what's going on (that's all I get: First chance exception at $770C4B32. Exception class EOleError with message 'Could not obtain OLE control window handle'. Process project1.exe (3700)). Funny thing is that the same TWebBrowser on Form1 of a new project works without any issues.
Any thoughts on that would be highly appreciated.
It is caused by the html form being closed. The vendor's forums show some code that will fix the problem.
http://www.bsalsa.com/forum/showthread.php?t=255
Set Cancel to True in the OnWindowClosing event and navigate to an
empty page if it is the main webbrowser. In case your webbrowser is a
popup window, you may want to close the form the EWB is on.
procedure TForm2.EmbeddedWB1WindowClosing(ASender: TObject; IsChildWindow: WordBool; var Cancel: WordBool);
begin
Cancel := True;
(ASender as TEmbeddedWB).GoAboutBlank;
end;
TWebBrowser is still being focused as ActiveControl and TOleControl.HookControlWndProc is being called on a ActiveControl which is no longer in the memory. As a result EOleError exception is raised because the window handle cannot be obtained. You can avoid this by setting ActiveControl to nil (changing the active control focus) prior to shutting down the application.
ActiveControl := nil;
This is the function which causes the exception (OleCtrls.pas):
procedure TOleControl.HookControlWndProc;
var
WndHandle: HWnd;
begin
if (FOleInPlaceObject <> nil) and (WindowHandle = 0) then
begin
WndHandle := 0;
FOleInPlaceObject.GetWindow(WndHandle);
// Exception is raised here because WndHandle could not be obtained
if WndHandle = 0 then raise EOleError.CreateRes(#SNoWindowHandle);
WindowHandle := WndHandle;
DefWndProc := Pointer(GetWindowLong(WindowHandle, GWL_WNDPROC));
CreationControl := Self;
SetWindowLong(WindowHandle, GWL_WNDPROC, Longint(#InitWndProc));
SendMessage(WindowHandle, WM_NULL, 0, 0);
end;
end;
Another way is to trap WM_PARENTNOTIFY message with the parameter WM_DESTROY when the destroy message is being sent to TWebBrowser handle because the parent form (where TWebBrowser is nested in) gets a WM_PARENTNOTIFY message:
procedure ParentNotify(var Msg: TMessage); message WM_PARENTNOTIFY;
implementation of message handler:
procedure TMyForm.ParentNotify(Var Msg: TMessage);
begin
if (Msg.WParamLo = WM_DESTROY) and (Msg.LParam = mywebbrowser.Handle) then close;
end;

Resources