Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Delphi's message keyword handler syntax with variable constants?

Tags:

delphi

Short Version

Any way to use:

    procedure WMStuff(var Message: TMessage); message WM_Stuff;

when WM_Stuff is a variable?

Long version

Delphi has an absolutely lovely bit of compiler magic to make handling messages so easy. You simply tag your procedure with the message WM_TheMessage keyword:

procedure WMGrobFrobber(var Message: TMessage); message WM_GrobFrobber;

and your procedure will be called to handle that message. No subclassing. No replacing, storing, and calling base window procedures. Just easy simple magic.

For constants

The message works great when the WM_GrobFrobber is a const:

const
   WM_GrobFrobber = WM_APP + $12A9;  //hopefully nobody's used this message before

But the downside of a constant declared like that is:

  • i have to hope no other component or library has already used that constant for something else when the message gets broadcast to all windows
  • i cannot broadcast, send, or post that message to other processes

Windows recommends you use RegisterWindowMessage to ensure you safely have a unique message number:

Defines a new window message that is guaranteed to be unique throughout the system. The message value can be used when sending or posting messages.

The RegisterWindowMessage function is typically used to register messages for communicating between two cooperating applications.

If two different applications register the same message string, the applications return the same message value. The message remains registered until the session ends.

Only use RegisterWindowMessage when more than one application must process the same message. For sending private messages within a window class, an application can use any integer in the range WM_USER through 0x7FFF. (Messages in this range are private to a window class, not to an application. For example, predefined control classes such as BUTTON, EDIT, LISTBOX, and COMBOBOX may use values in this range.)

And that is what i need:

  • i will be sending (or receiving) messages from other window classes (e.g. TForm1 vs TForm2 vs TVirtualTreeHintWorkerThread)
  • i will be sending (or receiving) messages from other windows classes in other applications.

So i register my message:

var
   WM_GrobFrobber: Cardinal;

initialization
    WM_GrobFrobber := RegisterWindowMessage('Contoso.Grobber.GrobFrobber message');

The constant variable

But now i can no longer use the nice syntax:

procedure WMGrobFrobber(var Message: TMessage); message WM_GrobFrobber;
   //Constant expression expected

:(

I tried hacking an assignable type constant:

{$J+}
const
   WM_GrobFrobber: Cardinal = 0;

initialization
    WM_GrobFrobber := RegisterWindowMessage('Contoso.Grobber.GrobFrobber message');

But the message keyword does not accept *pseudo-*constants either:

procedure WMGrobFrobber(var Message: TMessage); message WM_GrobFrobber;
   //Constant expression expected

Is there any way to salvage the lovely, easy message syntax, and not have to subclass every window that might want to handle a message?

Especially since the message really isn't a constant; but is invented and registered by people who are not me.

like image 661
Ian Boyd Avatar asked May 09 '19 20:05

Ian Boyd


2 Answers

As David states in his answer, message ID's in declarative message handlers are required to be constant expressions, so there is no way to implement a handler for a variable message number in this way.

However, you still do not need to sub-class every window to be able to respond to such messages. Or rather, you do not need to perform any further sub-classing than you are already doing by declaring a form or control class.

You can handle your custom, registered message by over-riding the virtual WndProc method. You won't be able to use a select .. case statement to handle the message since this similarly requires constant expressions for the matching cases, but you can use a simple if .. then statement to catch your message, calling inherited for everything else:

procedure TMyForm.WndProc(var aMessage: TMessage);
begin
  if aMessage.Msg = WM_GrobFrobber then
  begin
    { Handle the message or pass to a WMGrobFrabber() method 
      with suitably repacked and typed params, as required/desired }
  end
  else
    inherited WndProc(aMessage);
end;

You could introduce a virtual WMGrobFrabber in a form class which you then use consistently as the base class for all forms in your application(s), so that you can simply override that method to handle this message, rather than having to regurgitate the WndProc conditional handler code every time.

This doesn't solve all your problems. It doesn't provide a way to use the declarative message handler syntax, but it is still quite elegant (imho).

If such messages are used exclusively for responding to broadcast messages (which I believe is the only circumstance in which you need be concerned about message id's conflicting with those used by others) then you could create a non-visual component that implements a message handler specifically to respond to this message by firing an event with a published event handler. Then you don't need to sub-class at all to implement a response to such broadcasts on a form, just drop a handler component on the form and implement a handler for the component's event.

That is obviously more complicated than would be appropriate to deal with in an answer to this question, but might be worth considering.

like image 130
Deltics Avatar answered Nov 20 '22 02:11

Deltics


The message ID associated with a message method must be a constant expression.

like image 21
David Heffernan Avatar answered Nov 20 '22 02:11

David Heffernan