Ir al contenido


Foto

[MULTILENGUAJE] MousePreview


  • Por favor identifícate para responder
3 respuestas en este tema

#1 escafandra

escafandra

    Advanced Member

  • Moderadores
  • PipPipPip
  • 4.000 mensajes
  • LocationMadrid - España

Escrito 28 agosto 2021 - 04:32

Estamos habituados al uso de KeyPreview que dota al Formulario de la capacidad de enterarse de las pulsaciones de las teclas que se pulsan sobre componentes que contiene pero eso no existe para el ratón.
Se me presentó la necesidad de dicho uso porque necesitaba agrandar un TMemo con el aspecto de un TEdit hasta cierto tamaño, añadirle barra de deslizamiento y que volviese a su aspecto inicial tras salir de él. Pronto me di cuenta que el evento OnExit no me servía a no ser que hubiese un cambio de foco y yo necesitaba que al hacer click en cualquier parte que no fuere el Memo lo llevara a su estado original. De esta forma era necesario controlar desde el formulario un OnMousePreview. Claro que una solución es controlar en cada componente que tenga el formulario que he pulsado el mouse, pero es una solución chapucera. 
 
Así las cosas, decidí inventarme mi propio MousePreview mediante un Hook al ratón y desarrolle una clase en C++. He de decir que para realizar un hook no se puede usar una función de tratamiento que pertenezca a una clase, o si es así, debe ser un método estático o de la clase y eso obliga a que ese método sólo use variables también static, pero no es un problema ya que el hook en sí mismo pertenece a toda la aplicación (o a toda la sesión si es a bajo nivel) con lo que casi está pidiendo un tratamiento de ese estilo y en una clase modelo singleton. Pues eso hice.
 
Le estrategia es entonces una clase con variables y métodos static, que sólo permita una instancia (singleton) con un hook al ratón que envíe un mensaje al formulario de tipo TWMMouse. El formulario tratará ese mensaje como necesite. En realidad el mensaje se envía a la ventana contenedora cuyo componente haya provocado un evento de ratón, y para ello se navega por sus ventanas padre hasta encontrar el Handle de la ventana madre de estilo no WS_CHILD. Esa ventana es el propio formulario. Como el Hook es global a la aplicación, cualquier formulario queda habilitado para recibir el mensaje OnMousePreview y esa es la razón que obliga al uso del singleton.
 
Vamos al grano con la clase en C++.


cpp
  1. #define UM_MOUSE_PREVIEW WM_USER + 1000
  2. //---------------------------------------------------------------------------
  3.  
  4. class TMousePreview
  5. {
  6. private:
  7. static TMousePreview* Instance;
  8. static HHOOK hMouseHook;
  9. static LRESULT WINAPI MouseEvent(int nCode, WPARAM wParam, LPARAM lParam);
  10.  
  11. TMousePreview();
  12. TMousePreview(const &TMousePreview);
  13. TMousePreview& operator = (const &TMousePreview);
  14.  
  15. public:
  16. ~TMousePreview();
  17. static TMousePreview* GetInstance();
  18. static void Destroy();
  19. };

Esta es la función que responde al Hook del mouse a alto nivel tipo WH_MOUSE


cpp
  1. // Un hook al mouse permite enviar un mensaje al Formulario de tipo TWMMouse
  2. // Esto actua como un MousePreview por similitud a KeyPreview
  3. // Se envía el estado del mouse y sel teclado
  4. LRESULT WINAPI TMousePreview::MouseEvent(int nCode, WPARAM wParam, LPARAM lParam)
  5. {
  6. if(nCode == HC_ACTION){
  7. MOUSEHOOKSTRUCT *MS = (PMOUSEHOOKSTRUCT)lParam;
  8. int Keys = 0;
  9. if(GetAsyncKeyState(VK_CONTROL) & 0x8000)
  10. Keys |= MK_CONTROL;
  11. if(GetAsyncKeyState(VK_SHIFT) & 0x8000)
  12. Keys |= MK_SHIFT;
  13. if(GetAsyncKeyState(VK_LBUTTON) & 0x8000)
  14. Keys |= MK_LBUTTON;
  15. if(GetAsyncKeyState(VK_MBUTTON) & 0x8000)
  16. Keys |= MK_MBUTTON;
  17. if(GetAsyncKeyState(VK_RBUTTON) & 0x8000)
  18. Keys |= MK_RBUTTON;
  19.  
  20. PostMessage(GetTopParent(MS->hwnd), UM_MOUSE_PREVIEW, Keys, MAKELPARAM(MS->pt.x, MS->pt.y));
  21. }
  22. return CallNextHookEx(hMouseHook, nCode, wParam, lParam);
  23. }

Como se puede observar el hook envía un mensaje sin retorno a la ventana TopParent informando del Estado del mouse con la pulsación de las teclas Control, Shift y la posición absoluta en la pantalla del cursor. Es importante recalcar que se trata de la posición absoluta y no relativa a la ventana TopParent (Formulario) decidí esto por la facilidad de localizar luego la ventana que generó el evento.
 
Para terminar de ilustrar el asunto, el siguiente código muestra como usar TMousePreview agrandar o encoger un TMemo:


cpp
  1. __fastcall TForm1::TForm1(TComponent* Owner)
  2.   : TForm(Owner)
  3. {
  4.   TMousePreview* MousePreview = MousePreview->GetInstance();
  5. }
  6. //---------------------------------------------------------------------------
  7.  
  8. // Este mensaje recibe del hook los datos del mouse
  9. // Aquí lo uso para detectar que se entra o sale del Memo1.
  10. void  __fastcall TForm1::OnMousePreview(TWMMouse &MMouse)
  11. {
  12.   if(MMouse.Keys & MK_LBUTTON){
  13.     TPoint P;
  14.     P.x = MMouse.XPos;
  15.     P.y = MMouse.YPos;
  16.     if(WindowFromPoint(P) == Memo1->Handle){
  17.       Memo1->Height = 200;
  18.       Memo1->ScrollBars = ssVertical; //ssBoth;
  19.     }
  20.     else{
  21.       Memo1->Height = 21;
  22.       Memo1->ScrollBars = ssNone;
  23.     }
  24.   }
  25. }
  26. //---------------------------------------------------------------------------
  27.  
  28. void __fastcall TForm1::Button1Click(TObject *Sender)
  29. {
  30.   TMousePreview::Destroy();
  31. }

Espero que sea de utilidad. Subo un proyecto completo en BCB5 de ejemplo.
 
Saludos.

Archivos adjuntos


  • 0

#2 escafandra

escafandra

    Advanced Member

  • Moderadores
  • PipPipPip
  • 4.000 mensajes
  • LocationMadrid - España

Escrito 28 agosto 2021 - 04:38

Para el caso de delphi toda la explicación y filosofía es la misma cambiando única y exclusivamente el lenguaje.

Esta es la clase TMousePreview:


delphi
  1. unit MousePreview;
  2.  
  3. interface
  4.  
  5. uses
  6.   Windows, Messages, Forms;
  7.  
  8. const
  9.   UM_MOUSE_PREVIEW = WM_USER + 1000;
  10.  
  11.  
  12. type
  13.   TMousePreview = class
  14.   private
  15.     constructor Create;
  16.   public
  17.     procedure FreeInstance; override;
  18.     class function GetInstance: TMousePreview;
  19.     class function NewInstance: TObject; override;
  20.   end;
  21.  
  22. implementation
  23.  
  24. var
  25. Instance: TObject = nil;
  26. hMouseHook: HHOOK = 0;
  27.  
  28. function GetTopParent(Wnd: HWND): HWND;
  29. begin
  30.   repeat
  31.     Result:= Wnd;
  32.     Wnd:= GetParent(Result);
  33.   until (Wnd = 0) or (GetWindowLongA(Result, GWL_STYLE) and WS_CHILD = 0);
  34. end;
  35.  
  36. function MouseEvent(Code, wParam, lParam: Integer): Integer; stdcall;
  37. var
  38.   MS:   PMOUSEHOOKSTRUCT;
  39.   Keys: integer;
  40. begin
  41.   if(Code = HC_ACTION) then
  42.   begin
  43.     MS:= PMOUSEHOOKSTRUCT(lParam);
  44.     Keys:= 0;
  45.     if(GetAsyncKeyState(VK_CONTROL) and $8000 <> 0) then
  46.       Keys:= Keys or MK_CONTROL;
  47.     if(GetAsyncKeyState(VK_SHIFT) and $8000 <> 0) then
  48.       Keys:= Keys or MK_SHIFT;
  49.     if(GetAsyncKeyState(VK_LBUTTON) and $8000 <> 0) then
  50.       Keys:= Keys or MK_LBUTTON;
  51.     if(GetAsyncKeyState(VK_MBUTTON) and $8000 <> 0) then
  52.       Keys:= Keys or MK_MBUTTON;
  53.     if(GetAsyncKeyState(VK_RBUTTON) and $8000 <> 0) then
  54.       Keys:= Keys or MK_RBUTTON;
  55.     PostMessage(GetTopParent(MS.hwnd), UM_MOUSE_PREVIEW, Keys, MAKELPARAM(MS.pt.x, MS.pt.y));
  56.   end;
  57.  
  58.   Result:= CallNextHookEx(hMouseHook, Code, wParam, lParam);
  59. end;
  60.  
  61. constructor TMousePreview.Create;
  62. begin
  63.   inherited;
  64. end;
  65.  
  66. procedure TMousePreview.FreeInstance;
  67. begin
  68.   if(Assigned(Instance)) then
  69.   begin
  70.     if(hMouseHook <> 0) then UnhookWindowsHookEx(hMouseHook);
  71.     hMouseHook:= 0;
  72.     Instance:= nil;
  73.     inherited FreeInstance;
  74.   end;
  75. end;
  76.  
  77. class function TMousePreview.NewInstance: TObject;
  78. begin
  79.   if(not Assigned(Instance)) then
  80.   begin
  81.     Instance:= inherited NewInstance;
  82.     if hMouseHook = 0 then
  83.       hMouseHook:= SetWindowsHookEx(WH_MOUSE, MouseEvent, HInstance, GetCurrentThreadId());
  84.   end;
  85.   Result:= Instance
  86. end;
  87.  
  88. class function TMousePreview.GetInstance: TMousePreview;
  89. begin
  90.   Result:= NewInstance as TMousePreview;
  91. end;
  92.  
  93. end.

Y el siguiente código muestra como usarlo:


delphi
  1. procedure TForm1.FormCreate(Sender: TObject);
  2. begin
  3.   TMousePreview.GetInstance;
  4. end;
  5.  
  6. procedure TForm1.OnMousePreview(var MMouse: TWMMouse);
  7. var
  8.   P: TPoint;
  9. begin
  10.   if(MMouse.Keys and MK_LBUTTON <> 0) then
  11.   begin
  12.     P.x:= MMouse.XPos;
  13.     P.y:= MMouse.YPos;
  14.     if(WindowFromPoint(P) = Memo1.Handle) then
  15.     begin
  16.       Memo1.Height:= 200;
  17.       Memo1.ScrollBars:= ssVertical; //ssBoth;
  18.     end
  19.     else
  20.     begin
  21.       Memo1.Height:= 21;
  22.       Memo1.ScrollBars:= ssNone;
  23.     end;
  24.   end;
  25. end;
  26.  
  27. procedure TForm1.Button1Click(Sender: TObject);
  28. begin
  29.   TMousePreview.GetInstance.Free;
  30. end;

 

También subo un ejemplo completo en delphi 7


Saludos.

Archivos adjuntos


  • 0

#3 egostar

egostar

    missing my father, I love my mother.

  • Administrador
  • 14.261 mensajes
  • LocationMéxico

Escrito 29 agosto 2021 - 02:23

Muchas gracias amigo escafandra, una manera muy interesante de utilizar el concepto de singleton. (y)

 

Saludos


  • 0

#4 ELKurgan

ELKurgan

    Advanced Member

  • Miembro Platino
  • PipPipPip
  • 560 mensajes
  • LocationEspaña

Escrito 29 agosto 2021 - 10:43

Como siempre, amigo escafnadra, un gran trabajo

 

Muchas gracias por tu aporte y un saludo

 


  • 0