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.
Related
One day ago I had started to rewrite one of my old components and I decided to improve its readability.
My component is a typical TWinControl that has overridden WndProc to handle a lot of messages of my own. There are so many code for each message and it became a problem for me to read code.
So, looking for a solution to improve code inside WndProc, I have organized these large pieces of code in procedures that called each time when appropriate message has delivered in WndProc. That's how it looks now:
procedure TMyControl.WndProc(var Message: TMessage);
begin
case Message.Msg of
WM_WINDOWPOSCHANGED:
WMWINDOWPOSCHANGED(Message);
WM_DESTROY:
WMDESTROY(Message);
WM_STYLECHANGED:
WMSTYLECHANGED(Message);
// lots of the same procedures for Windows messages
// ...
MM_FOLDER_CHANGED:
MMFOLDERCHANGED(Message);
MM_DIRECTORY_CHANGED:
MMDIRECTORYCHANGED(Message);
// lots of the same procedures for my own messages
// ...
else
Inherited WndProc(Message);
end;
end;
Unfortunately Inherited word in these procedures doesn't work anymore!
Important note: in some of WM_XXX messages I didn't call Inherited to perform my own handling of such message, so code shown below will break down my efforts to implement some features.
procedure TMyControl.WndProc(var Message: TMessage);
begin
Inherited WndProc(Message);
case Message.Msg of
WM_WINDOWPOSCHANGED:
WMWINDOWPOSCHANGED(Message);
// further messages
// ...
end;
end;
I also want to avoid inserting Inherited after each message-ID as shown below, because it looks awful and I think there is exists more elegant way to override WndProc.
procedure TMyControl.WndProc(var Message: TMessage);
begin
case Message.Msg of
WM_WINDOWPOSCHANGED:
begin
Inherited WndProc(Message);
WMWINDOWPOSCHANGED(Message);
end;
// further messages
// ...
end;
end;
So my question is:
how to properly override WndProc to have an ability to use code grouped in procedures and to be able to call for original window procedure only for some messages?
As RM's answer stated, your message handling methods can call inherited WndProc(Message) instead of just inherited, and that will work fine.
However, by introducing methods with the same names as the messages they are processing, you are exposing knowledge of the specific messages you are processing. So you may find it easier to just use Message Methods instead of overriding WndProc, eg:
type
TMyControl = class(...)
private
procedure WMWindowPosChanged(var Message: TMessage); message WM_WINDOWPOSCHANGED;
procedure WMDestroy(var Message: TMessage); message WM_DESTROY;
procedure WMStyleChanged(var Message: TMessage); message WM_STYLECHANGED;
// and so on ...
end;
Your message methods can then call inherited (or not) as needed, eg:
procedure TMyControl.WMWindowPosChanged(var Message: TMessage);
begin
inherited;
//...
end;
Calling inherited WndProc from WMWINDOWPOSCHANGED will call the inherited one. So you can do it like this:
procedure WMWINDOWPOSCHANGED(var Message: TMessage)
begin
// call inherited WndProc if you need to
inherited WndProc(Message);
.. do you own processing
end;
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);
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;
I read here about how to create a Window Handle into a non-windowed conrol. I did just the way I read, but nothing happens. So I come to you guys.
My class is this way right now:
interface
type
TMyObject = class
private
fMsgHandlerHWND : HWND;
procedure WndMethod(var Msg: TMessage);
public
constructor Create;
destructor Destroy; Override;
end;
implementation
constructor TMyObject.Create;
begin
inherited;
fMsgHandlerHWND := AllocateHWnd(WndMethod);
end;
destructor TMyObject.Destroy;
begin
deallocatehwnd(fMsgHandlerHWND);
inherited;
end;
procedure TMyObject.WndMethod(var Msg: TMessage);
begin
if Msg.Msg = WM_KEYUP then
MessageBeep(0)
else
Msg.Result := DefWindowProc(fMsgHandlerHWND, Msg.Msg, Msg.wParam, Msg.lParam);
end;
I do use my FormCreate to execute var := TMyObject.Create.
Following the line where Windows sends broadcast messages when I press/release a key (correct me if I'm wrong); I'm not sure why it did not work. Somoeone can tell me what did I do wrong? There is another way to catch KeyBoard input with a non-windowed object? If so, how?
Keyboard events are delivered to the window with input focus. That's never going to be your hidden window.
if you want to catch input events the cleanest way is to use the OnMessage event of the global Application object. All queued messages pass through this event handler. Subscribe to it using a TApplicationEvents instance.
I saw your tip about : TTouchKeyboard: send keystroke to other program
How can I send the keys to the other form in the same Delphi application?
And how can I call the form with the TTouchKeyboard? (Show, showModal, parameters?)
Thanks!
ShowModal is a bad idea... you focus the caller...
You can still use the same tip with the form which contains the keyboard, in order to stay disable...
Then, you can add a property with the handle of the form which should get the keystroke.
And finally, you hack the TTouchKeyboard to set the focus to the form with the handle you previously set...
For instance, your TTouchKeyboard hack could be like this:
type
TMyKeyboard = class(TTouchKeyboard)
protected
procedure WndProc(var Message: TMessage); override;
end;
type
TForm1 = class(TForm)
...
private
fHandleOfTheTargetForm: HWND;
public
property HandleOfTheTargetForm: HWND read fHandleOfTheTargetForm write fHandleOfTheTargetForm;
...
procedure TMyKeyboard.WndProc(var Message: TMessage);
begin
if (Assigned(Form1)) then
begin
if Form1.HandleOfTheTargetForm <> 0 then
begin
SetForegroundWindow(HandleOfTheTargetForm);
end;
end;
inherited;
end;
You can find a quick demo project here.