任何使用方式:
procedure WMStuff(var Message: TMessage); message WM_Stuff;
WM_Stuff
是变量吗?
Delphi具有一点点可爱的编译器魔力,使处理消息如此容易。您只需使用message WM_TheMessage
关键字标记过程即可:
procedure WMGrobFrobber(var Message: TMessage); message WM_GrobFrobber;
,将调用您的过程来处理该消息。没有子类。无需替换,存储和调用基本窗口过程。只是简单的简单魔术。
当WM_GrobFrobber
是const
时,此消息效果很好:
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
)发送(或接收)消息所以我注册了我的消息:
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
语法,而不必将可能要处理消息的每个窗口都子类化吗?
尤其是因为消息确实不是恒定;但由不是我的人发明和注册。
答案 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;
运行它,然后单击“注册”,“取消注册”和“执行”按钮进行试用。