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);
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;
The problem
When running an application written in C, that uses some dll's written in Delphi XE7, I run into an access violation in the following code, which is in the vcl.forms.pas of the vcl library.
procedure TCustomForm.CMAppSysCommand(var Message: TMessage);
{$IF NOT DEFINED(CLR)}
type
PWMSysCommand = ^TWMSysCommand;
{$ENDIF}
begin
Message.Result := 0;
if (csDesigning in ComponentState) or (FormStyle = fsMDIChild) or
(Menu = nil) or Menu.AutoMerge then
{$IF DEFINED(CLR)}
with TWMSysCommand.Create(Message) do
{$ELSE}
with PWMSysCommand(Message.lParam)^ do
{$ENDIF}
begin
SendCancelMode(nil);
if SendAppMessage(CM_APPSYSCOMMAND, CmdType, Key) <> 0 then //Here the debugger shows the access violation
Message.Result := 1;
end;
end;
The access violation occurs on the line with SendAppMessage, and seems to be caused by the fact that the Message.LParam is 0. The message is a WM_SYSCOMMAND message. Is there a way to track where this message originated? In the call stack, all functions are part of the VCL or system files.
This answer suggest that in general it is hard to trace the sender of a windows message. However, since in my case everything is within the same application, I hope that might make it easier.
What have I tried?
Overruling the vcl source
Previously, this same bug appeared in the forms.pas and was fixed by adding a copy of that file to the project and then checking that LParam <> 0 in this function.
I have tried doing the same thing with the vcl.forms.pas that is now used, but this leads to compilation errors. Even with answers as here I was not able to build it. However, many google hits also suggested that it is in general a bad idea to change things in the vcl, so I try to avoid that option.
Other questions on StackOverFlow
This article gave me good information about the underlying system and how it might have occured that the Message.LParam is 0. However, I did not know how to find the source of the message or what class I should be looking for that generated it.
The solution
As described in Remy's accepted answer below, the immediate problem could be solved by having the class provide a CMAppSysCommand function to guard against LParam = 0.
What you describe should not be possible under normal conditions.
There are only two places in the entire VCL where CM_APPSYSCOMMAND is sent from:
TWinControl.WMSysCommand(), which is called when a UI control receives a WM_SYSCOMMAND message. The LParam of the CM_APPSYSCOMMAND message is never set to 0, it is set to a pointer to the TMessage record of the original WM_SYSCOMMAND message:
Form := GetParentForm(Self);
if (Form <> nil) and
(Form.Perform(CM_APPSYSCOMMAND, 0, Winapi.Windows.LPARAM(#Message)) <> 0) then
Exit;
TCustomForm.CMAppSysCommand(), which is called when a Form receives a CM_APPSYSCOMMAND message. It forwards the message to the TApplication window (using SendAppMessage(), which just calls SendMessage(Application.Handle, ...) with the provided parameters):
with PWMSysCommand(Message.lParam)^ do
begin
...
if SendAppMessage(CM_APPSYSCOMMAND, CmdType, Key) <> 0 then
Message.Result := 1;
end;
The other question you mention explains how CM_APPSYSCOMMAND is used by the VCL, but does not say anything that would suggest how its LParam could ever be 0 in TCustomForm.CMAppSysCommand(), because it can't ever be 0 under normal circumstances. It can be 0 in TApplication.WndProc(), but that is perfectly OK.
The only possibility I can think of would be if someone is manually sending a fake CM_APPSYSCOMMAND message (which is CM_BASE + 23 = $B017, aka WM_APP + $3017) directly to your TForm window. Only TWinControl should ever be doing that. And since TWinControl uses Perform() instead of SendMessage() for that send, you should be seeing TWinControl.WMSysCommand() on the call stack of TCustomForm.CMAppSysCommand(). If you do not, then the message is fake. And if it is being sent using SendMessage() instead of Perform(), there is no way to know where the message is coming from.
However, in any case, this is very easy to guard against, without altering any VCL source code. Simply have your DLL's TForm class provide its own message handler for CM_APPSYSCOMMAND, either using the message directive, or by overriding the virtual WndProc() method. Either way, you can discard the message if the LParam is 0, eg:
type
TMyForm = class(TForm)
...
private
procedure CMAppSysCommand(var Message: TMessage); message CM_APPSYSCOMMAND;
...
end;
procedure TMyForm.CMAppSysCommand(var Message: TMessage);
begin
if Message.LParam = 0 then
Message.Result := 0
else
inherited;
end;
type
TMyForm = class(TForm)
...
protected
procedure WndProc(var Message: TMessage); override;
...
end;
procedure TMyForm.WndProc(var Message: TMessage);
begin
if (Message.Msg = CM_APPSYSCOMMAND) and (Message.LParam = 0) then
Message.Result := 0
else
inherited;
end;
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)).
I have two applications that I need to communicate via PostMessage (SendMessage is ruled out as I need to have the second Application able to output to Excel whilst being called.
So far Application 1 makes a call to Application 2, making sure that it is open, and when it is, App 1 is set to disabled.
When the user is finished with Application 2 I need to send a message back to Applciation 1 to allow it to unlock.
I have registered the same Windows Message in each application using:
const
MyMessage = 'My-Message';
var
MyMessageID: cardinal;
procedure TMF.FormCreate(Sender: TObject);
begin
MyMessageID := RegisterWindowMessage(MyMessage);
end;
And in Application 2 I can Post the message to Application 1 using:
targetHandle := FindWindow(Pchar('TMF'), Pchar('Send Test'));
...
if PostMessage(targetHandle, (MyMessageID), 0, 0) then
...
What I don't understand is how I declare the Handler for the Message in Application 1.
If I was using a standard Windows Message, such as WM_COPYDATA I'd declare a procedure
procedure WMCopyData(var Msg: TWMCopyData); message WM_COPYDATA;
But that falls down because I can't declare MyMessageID early enough.
Perhaps it's because it's Friday afternoon, but what am I missing?
You cannot use the message keyword because the message constant is not known at compile time. Instead you have to override WndProc:
procedure WndProc(var Message: TMessage); override;
....
procedure TMF.WndProc(var Message: TMessage);
begin
inherited;
if Message.Msg = MyMessageID then begin
....
end;
end;
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.