Version corta
Cualquier forma de usar:
procedure WMStuff(var Message: TMessage); message WM_Stuff;
Cuando WM_Stuff
es una variable?
Versión larga
Delphi tiene una magia de compilación absolutamente encantadora para hacer que el manejo de mensajes sea tan fácil. Simplemente etiquete su procedimiento con la palabra clave message WM_TheMessage
:
procedure WMGrobFrobber(var Message: TMessage); message WM_GrobFrobber;
Y se llamará a su procedimiento para manejar ese mensaje. Sin subclases. No reemplaza, almacena y llama a los procedimientos de la ventana base. Simplemente fácil magia simple.
Para constantes
El mensaje funciona muy bien cuando WM_GrobFrobber
es un const
:
const
WM_GrobFrobber = WM_APP + $12A9; //hopefully nobody's used this message before
Pero la desventaja de una constante declarada así es:
- Tengo que esperar que ningún otro componente o biblioteca ya haya usado esa constante para otra cosa cuando el mensaje se transmita a todas las ventanas
- no puedo transmitir, enviar o publicar ese mensaje en otros procesos
Windows recomienda usar RegisterWindowMessage para garantizar que tenga un número de mensaje único de forma segura:
Define un nuevo mensaje de ventana que se garantiza que será único en todo el sistema. El valor del mensaje se puede usar al enviar o publicar mensajes.
La función RegisterWindowMessage se usa generalmente para registrar mensajes para comunicarse entre dos aplicaciones cooperantes.
Si dos aplicaciones diferentes registran la misma cadena de mensaje, las aplicaciones devuelven el mismo valor de mensaje. El mensaje permanece registrado hasta que finaliza la sesión.
Solo use RegisterWindowMessage cuando más de una aplicación debe procesar el mismo mensaje. Para enviar mensajes privados dentro de una clase de ventana, una aplicación puede usar cualquier número entero en el rango WM_USER hasta 0x7FFF. (Los mensajes en este rango son privados para una clase de ventana, no para una aplicación. Por ejemplo, clases de control predefinidas como BUTTON , EDIT , LISTBOX y COMBOBOX pueden usar valores en este rango).
Y eso es lo que necesito:
- enviaré (o recibiré) mensajes de otras clases de ventanas (por ejemplo,
TForm1
vsTForm2
vsTVirtualTreeHintWorkerThread
) - Enviaré (o recibiré) mensajes de otras clases de Windows en otras aplicaciones.
Entonces registro mi mensaje:
var
WM_GrobFrobber: Cardinal;
initialization
WM_GrobFrobber := RegisterWindowMessage('Contoso.Grobber.GrobFrobber message');
La variable constante
Pero ahora ya no puedo usar la buena sintaxis:
procedure WMGrobFrobber(var Message: TMessage); message WM_GrobFrobber;
//Constant expression expected
:(
Intenté hackear una constante de tipo asignable :
{$J+}
const
WM_GrobFrobber: Cardinal = 0;
initialization
WM_GrobFrobber := RegisterWindowMessage('Contoso.Grobber.GrobFrobber message');
Pero la palabra clave message
tampoco acepta * pseudo- * constantes:
procedure WMGrobFrobber(var Message: TMessage); message WM_GrobFrobber;
//Constant expression expected
¿Hay alguna forma de salvar la encantadora y fácil sintaxis message
y no tener que subclasificar todas las ventanas que quieran manejar un mensaje?
Especialmente porque el mensaje realmente no es una constante; pero es inventado y registrado por personas que no son yo.
3 respuestas
El ID del mensaje asociado con un método de mensaje debe ser expresión constante.
Como David afirma en su respuesta, se requiere que las ID de mensajes en los manejadores de mensajes declarativos sean expresiones constantes, por lo que no hay forma de implementar un manejador para un número de mensaje variable de esta manera.
Sin embargo, todavía no necesita subclasificar todas las ventanas para poder responder a dichos mensajes. O, más bien, no necesita realizar ninguna subclasificación adicional de la que ya está haciendo declarando un formulario o clase de control.
Puede manejar su mensaje personalizado y registrado anulando el método virtual WndProc
. No podrá usar una declaración select .. case
para manejar el mensaje, ya que esto requiere expresiones constantes para los casos coincidentes, pero puede usar una simple declaración if .. then
para captar su mensaje, llamando a {{ X3}} para todo lo demás:
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;
Puede introducir un virtual
WMGrobFrabber
en una clase de formulario que luego usa de manera consistente como la clase base para todos los formularios en su (s) aplicación (es), de modo que simplemente pueda anular ese método para manejar este mensaje, en lugar de tener que regurgitar el código de controlador condicional WndProc
cada vez.
Esto no resuelve todos tus problemas. No proporciona una forma de utilizar la sintaxis del controlador de mensajes declarativos, pero sigue siendo bastante elegante (en mi humilde opinión).
Si dichos mensajes se usan exclusivamente para responder a mensajes de difusión (lo cual creo que es la única circunstancia en la que debe preocuparse por la identificación de mensajes en conflicto con los utilizados por otros), entonces podría crear un componente no visual que implemente un controlador de mensajes específicamente para responder a este mensaje disparando un evento con un controlador de eventos publicado. Entonces no necesita subclases para implementar una respuesta a tales transmisiones en un formulario, simplemente suelte un componente de controlador en el formulario e implemente un controlador para el evento del componente.
Obviamente, eso es más complicado de lo que sería apropiado tratar en una respuesta a esta pregunta, pero podría valer la pena considerarlo.
Si guarda este código en 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.
E incluya esta unidad como la última en el formulario en el que desea poder manejar múltiples manejadores para un mensaje, o manejar mensajes con un número de mensaje dependiente del tiempo de ejecución, entonces puede lograr esto fácilmente.
Úselo de la siguiente manera:
Cree una nueva aplicación VCL y coloque tres botones en el formulario, llamados RegisterBtn , UnregisterBtn y ExecBtn con los títulos apropiados. Asegúrese de que la propiedad "Habilitada" para UnregisterBtn y ExecBtn esté establecida en "FALSE" en el editor de propiedades y "TRUE" para RegisterBtn . Cree un controlador de mensajes de la siguiente manera en su formulario:
PROCEDURE TForm1.Handler(VAR MSG : TMessage);
BEGIN
ShowMessage('Handler!')
END;
Y agregue la siguiente definición de variable a su formulario también:
Token : TForm.TToken;
(es decir.
private
{ Private declarations }
Token : TForm.TToken;
PROCEDURE Handler(VAR MSG : TMessage);
)
Luego cree controladores de clic para los tres y complételos de la siguiente manera:
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;
Ejecútelo y haga clic en el botón "Registrarse", "Cancelar registro" y "Exec" para probarlo.
Nuevas preguntas
delphi
Delphi es un lenguaje para el desarrollo rápido de aplicaciones nativas de Windows, macOS, Linux, iOS y Android mediante el uso de Object Pascal. El nombre hace referencia al lenguaje Delphi, así como a sus bibliotecas, compilador e IDE, que se utilizan para ayudar a editar y depurar proyectos de Delphi.