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 vs TForm2 vs TVirtualTreeHintWorkerThread)
  • 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.

9
Ian Boyd 9 may. 2019 a las 23:59

3 respuestas

La mejor respuesta

El ID del mensaje asociado con un método de mensaje debe ser expresión constante.

8
David Heffernan 9 may. 2019 a las 21:31

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.

11
Deltics 10 may. 2019 a las 03:59

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.

0
HeartWare 10 may. 2019 a las 09:10