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++.
#define UM_MOUSE_PREVIEW WM_USER + 1000 //--------------------------------------------------------------------------- class TMousePreview { private: static TMousePreview* Instance; static HHOOK hMouseHook; static LRESULT WINAPI MouseEvent(int nCode, WPARAM wParam, LPARAM lParam); TMousePreview(); TMousePreview(const &TMousePreview); TMousePreview& operator = (const &TMousePreview); public: ~TMousePreview(); static TMousePreview* GetInstance(); static void Destroy(); };
Esta es la función que responde al Hook del mouse a alto nivel tipo WH_MOUSE
// Un hook al mouse permite enviar un mensaje al Formulario de tipo TWMMouse // Esto actua como un MousePreview por similitud a KeyPreview // Se envía el estado del mouse y sel teclado LRESULT WINAPI TMousePreview::MouseEvent(int nCode, WPARAM wParam, LPARAM lParam) { if(nCode == HC_ACTION){ MOUSEHOOKSTRUCT *MS = (PMOUSEHOOKSTRUCT)lParam; int Keys = 0; if(GetAsyncKeyState(VK_CONTROL) & 0x8000) Keys |= MK_CONTROL; if(GetAsyncKeyState(VK_SHIFT) & 0x8000) Keys |= MK_SHIFT; if(GetAsyncKeyState(VK_LBUTTON) & 0x8000) Keys |= MK_LBUTTON; if(GetAsyncKeyState(VK_MBUTTON) & 0x8000) Keys |= MK_MBUTTON; if(GetAsyncKeyState(VK_RBUTTON) & 0x8000) Keys |= MK_RBUTTON; PostMessage(GetTopParent(MS->hwnd), UM_MOUSE_PREVIEW, Keys, MAKELPARAM(MS->pt.x, MS->pt.y)); } return CallNextHookEx(hMouseHook, nCode, wParam, lParam); }
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:
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { TMousePreview* MousePreview = MousePreview->GetInstance(); } //--------------------------------------------------------------------------- // Este mensaje recibe del hook los datos del mouse // Aquí lo uso para detectar que se entra o sale del Memo1. void __fastcall TForm1::OnMousePreview(TWMMouse &MMouse) { if(MMouse.Keys & MK_LBUTTON){ TPoint P; P.x = MMouse.XPos; P.y = MMouse.YPos; if(WindowFromPoint(P) == Memo1->Handle){ Memo1->Height = 200; Memo1->ScrollBars = ssVertical; //ssBoth; } else{ Memo1->Height = 21; Memo1->ScrollBars = ssNone; } } } //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { TMousePreview::Destroy(); }
Espero que sea de utilidad. Subo un proyecto completo en BCB5 de ejemplo.
Saludos.