使用带有可变常量的Delphi的message关键字处理程序语法吗?

时间:2019-05-09 20:59:02

标签: delphi

短版

任何使用方式:

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

WM_Stuff是变量吗?

长版

Delphi具有一点点可爱的编译器魔力,使处理消息如此容易。您只需使用message WM_TheMessage关键字标记过程即可:

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

,将调用您的过程来处理该消息。没有子类。无需替换,存储和调用基本窗口过程。只是简单的简单魔术。

对于常量

WM_GrobFrobberconst时,此消息效果很好:

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

但是这样声明的常量的缺点是:

  • 我必须希望当消息广播到所有窗口时,没有其他组件或库已经将该常量用于其他用途
  • 我无法将该消息广播,发送或发布到其他进程

Windows建议您使用 RegisterWindowMessage 来确保安全地拥有唯一的消息编号:

  

定义一个新的窗口消息,该消息在整个系统中保证是唯一的。发送或发布消息时可以使用消息值。

     

RegisterWindowMessage 函数通常用于注册消息,以便在两个合作的应用程序之间进行通信。

     

如果两个不同的应用程序注册了相同的消息字符串,则这些应用程序将返回相同的消息值。该消息将保持注册状态,直到会话结束。

     

仅当多个应用程序必须处理同一条消息时,才使用 RegisterWindowMessage 。为了在窗口类中发送私人消息,应用程序可以使用WM_USER到0x7FFF范围内的任何整数。 (此范围内的消息是窗口类而非应用程序所专有的。例如,预定义的控件类,例如按钮 EDIT LISTBOX ,并且 COMBOBOX 可能会使用此范围内的值。)

这就是我所需要的:

  • 我将从其他窗口类(例如TForm1 vs TForm2 vs TVirtualTreeHintWorkerThread)发送(或接收)消息
  • 我将从其他应用程序中的其他Windows类发送(或接收)消息。

所以我注册了我的消息:

var
   WM_GrobFrobber: Cardinal;

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

常量变量

但是现在我不能再使用漂亮的语法了:

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

:(

我尝试破解可分配的类型常量

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

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

但是message关键字也不接受* pseudo- *常量:

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

有什么方法可以挽救可爱,简单的message语法,而不必将可能要处理消息的每个窗口都子类化吗?

尤其是因为消息确实不是恒定;但由不是我的人发明和注册。

3 个答案:

答案 0 :(得分:11)

正如David在他的回答中指出的那样,声明性消息处理程序中的消息ID必须是常量表达式,因此无法以这种方式实现可变消息号的处理程序。

但是,您仍然不需要为每个窗口都子类化才能响应此类消息。或更确切地说,您不需要执行任何进一步子类化操作,而无需通过声明表单或控件类来完成该操作。

您可以通过覆盖虚拟WndProc方法来处理自定义的注册消息。您将无法使用select .. case语句来处理消息,因为这同样需要匹配情况下的常量表达式,但是您可以使用简单的if .. then语句来捕获消息,并调用{{ 1}}:

inherited

您可以在表单类中引入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; virtual,然后将其一致地用作应用程序中所有表单的基类,以便您可以简单地重写该方法来处理此消息,而不必每次都重新定义WMGrobFrabber条件处理程序代码。

这不能解决您的所有问题。它没有提供使用声明式消息处理程序语法的方法,但是仍然很优雅(恕我直言)。

如果此类消息专门用于响应广播消息(我相信这是您唯一需要担心消息ID与其他人使用的ID冲突的情况),则可以创建一个非可视组件来实现消息处理程序,专门通过使用已发布的事件处理程序触发事件来响应此消息。然后,您根本不需要子类即可在表单上实现对此类广播的响应,只需将处理程序组件放在表单上并为该组件的事件实现处理程序即可。

这显然比在回答这个问题时要处理的要复杂得多,但可能值得考虑。

答案 1 :(得分:8)

message method关联的消息ID必须为constant expression

答案 2 :(得分:0)

如果将此代码保存到HeartWare.VCL.Extensions.MultiCastMessage.PAS

UNIT HeartWare.VCL.Extensions.MultiCastMessage;

INTERFACE

USES WinAPI.Messages,
     VCL.Forms,
     Generics.Collections;

TYPE
  TForm         = CLASS(VCL.Forms.TForm)
                    DESTRUCTOR  Destroy; OVERRIDE;
                  STRICT PRIVATE
                    TYPE        TMessageNo       = Cardinal;
                    TYPE        TMessageHandler  = REFERENCE TO PROCEDURE(VAR MSG : TMessage);
                    TYPE        TMessageHandlers = TList<TMessageHandler>;
                    TYPE        TEvents          = TObjectDictionary<TMessageNo,TMessageHandlers>;
                    VAR         Events           : TEvents;
                  STRICT PROTECTED
                    TYPE        TToken           = RECORD
                                                     MsgNo,Index        : Cardinal
                                                   END;
                  PROTECTED
                    PROCEDURE   WndProc(VAR Message : TMessage); OVERRIDE;
                    FUNCTION    AddHandler(MsgNo : TMessageNo ; Handler : TMessageHandler) : TToken;
                    PROCEDURE   RemoveHandler(VAR Token : TToken);
                  END;

IMPLEMENTATION

USES System.SysUtils;

FUNCTION TForm.AddHandler(MsgNo : TMessageNo ; Handler : TMessageHandler) : TToken;
  VAR
    Handlers    : TMessageHandlers;

  BEGIN
    IF NOT Assigned(Events) THEN Events:=TEvents.Create([doOwnsValues]);
    IF NOT Events.TryGetValue(MsgNo,Handlers) THEN BEGIN
      Handlers:=TMessageHandlers.Create;
      Events.Add(MsgNo,Handlers)
    END;
    Result.MsgNo:=MsgNo;
    Result.Index:=Handlers.Add(Handler)
  END;

DESTRUCTOR TForm.Destroy;
  BEGIN
    FreeAndNIL(Events);
    INHERITED
  END;

PROCEDURE TForm.RemoveHandler(VAR Token : TToken);
  BEGIN
    Events[Token.MsgNo][Token.Index]:=NIL;
    Token:=Default(TToken)
  END;

PROCEDURE TForm.WndProc(VAR Message : TMessage);
  VAR
    Handlers    : TMessageHandlers;
    Handler     : TMessageHandler;

  BEGIN
    IF Assigned(Events) AND Events.TryGetValue(Message.Msg,Handlers) THEN
      FOR Handler IN Handlers DO IF Assigned(Handler) AND (Message.Result=0) THEN Handler(Message);
    IF Message.Result=0 THEN INHERITED
  END;


END.

并将此单元作为最后一个单元包含在表单中,您希望该单元既可以处理一条消息的多个处理程序,也可以处理与运行时相关的消息号的消息,那么您可以轻松地完成此任务。

按以下方式使用它:

创建一个新的VCL应用程序,并在窗体上放置三个按钮,分别为 RegisterBtn UnregisterBtn ExecBtn ,并加上适当的标题。确保在属性编辑器中将 UnregisterBtn ExecBtn 的“ Enabled”属性设置为“ FALSE”,将 RegisterBtn 的属性设置为“ TRUE” 。在您的表单中创建一个消息处理程序,如下所示:

PROCEDURE TForm1.Handler(VAR MSG : TMessage);
  BEGIN
    ShowMessage('Handler!')
  END;

并将以下变量定义也添加到您的表单中:

Token : TForm.TToken;

(即。

private
  { Private declarations }
  Token       : TForm.TToken;
  PROCEDURE   Handler(VAR MSG : TMessage);

然后为所有三个创建点击处理程序,并按如下所示进行填写:

PROCEDURE TForm1.RegisterBtnClick(Sender : TObject);
  BEGIN
    RegisterBtn.Enabled:=FALSE;
    UnregisterBtn.Enabled:=TRUE;
    ExecBtn.Enabled:=TRUE;
    Token:=AddHandler(RegisterWindowMessage('Ohhh'),Handler);
    ShowMessage('MessageNo: '+IntToHex(Token.MsgNo,4))
  END;

PROCEDURE TForm1.UnregisterBtnClick(Sender : TObject);
  BEGIN
    RemoveHandler(Token);
    ExecBtn.Enabled:=FALSE;
    UnregisterBtn.Enabled:=FALSE;
    RegisterBtn.Enabled:=TRUE
  END;

PROCEDURE TForm1.ExecBtnClick(Sender : TObject);
  BEGIN
    PostMessage(Handle,Token.MsgNo,0,0)
  END;

运行它,然后单击“注册”,“取消注册”和“执行”按钮进行试用。