Manekaze Site Admin


Joined: Dec 02, 2005 Posts: 358
|
Posted: Thu Apr 26, 2007 10:59 am Post subject: Capturando el WindowProc desde Axapta |
|
|
/**********************************************************
// Tipo : Desarrollo
// Titulo : Capturando el WindowProc desde Axapta
// Codigo : TD0017
// Keywords : Programacion, Axapta 3.0
// Sub-keywords : installMessageProc, WndProc, WindowProc, wm_char
//
// Para descargar el ejemplo (incluye las clases y 2 formularios de ejemplo) clic aquí (DOWNLOAD)
//
//**********************************************************
Introducción
El otro día estaba visitando algunas webs sobre Axapta (Dynamics Ax), es una cosa que intento
hacer a menudo para conocer de primera mano las nuevas noticias, consejos, trucos, webcasts...
En una de las webs que suelo visitar (la comunidad oficial de Microsoft Dynamics Ax), en el
apartado de newsgroup estuve leyendo un hilo de mensaje que despertó mi curiosidad.
Link al post
Consistía en que un programador lanzaba la pregunta de como podía capturar el WindowProc
de un control en Axapta (Dynamics Ax).
El WindowProc, explicado de forma muy básica, es una rutina que se encarga de procesar los
mensajes de Windows asociados a una ventana determinada. Los mensajes que envía Windows
a la ventana pueden ser de muchos tipos, WM_KeyDown (se ha pulsado una tecla), WM_Paint (Debe pintarse la ventana)
, WM_KeyUp (se ha soltado una tecla) ... y muchisimos más.
En fin, el caso es que, como se comentaba en dicho hilo, Axapta incorpora una función a nivel de "element" (FormRun)
que nos permite "instalar" un proceso que atiende a un tipo determinado de mensajes.
Esta función se llama "InstallMessageProc". Se le pasa por parámetro un código de mensajes a atender, un
identificador de ventana sobre la cual "escucharemos" dichos mensajes y un nombre de método de nuestro form
que se ejecutará cada vez que llegue un mensaje del tipo especificado a la ventana especificada.
Esto sería genial. ¿ Porque digo "sería" ? pues porque parece que no está terminado del todo y tiene algunos
problemas como por ejemplo : No recibimos los parámetros asociados a los mensajes que atendemos, por tanto
si por ejemplo nos llega un mensaje de tipo keydown ... no sabemos de que tecla se trata (o almenos no
tengo constancia de que alguien lo haya conseguido)
Hay formas alternativas de conseguir capturar el WindowProc, creando una DLL externa que se encarga
de procesar los mensajes y luego estableciendo algún canal de comunicación entre nuestro form
en Axapta y dicha DLL para poder actuar de una forma u otra dependiendo del mensaje
recibido ... pero son formas cuando menos incómodas.
Dicen que hay gente a la que para conseguir que realicen una acción no hay más que decirle que es incapaz
de hacerla o que es algo imposible. Debe ser mi caso
Objetivo
Vamos a intentar capturar el WindowProc asociado a una ventana (ventana en el término más
amplio abarca controles, formularios ... ) desde Axapta sin usar DLLs externas, sin anestesia ni ná.
Idea
Bien, después de realizar varias pruebas, comprobar hasta donde me deja llegar Axapta ... etc.
Llego a varias conclusiones :
- No puedo enlazar directamente una función de Axapta a una función de tipo callback de la API de Windows.
El motivo principal es que Axapta es un código interpretado y por tanto no se compila de verdad como otros lenguajes.
- Dispongo de una gestión de punteros y buffers de datos mediante la clase "binary" que me puede ser muy útil.
- Axapta 3.0 se ejecuta en un entorno MDI y esto puede ser fuente de problemas si no lo tenemos en cuenta.
- Voy a tener que tirar de viejos trucos de hacking para suplir la falta de un lenguaje compilable (Todos tenemos un pasado )
- ¿ Que representan unos milisegundos en la inmensidad del espacio tiempo ?
Resumiendo ...
Vamos a usar un buffer en el que cargaremos código ensamblador compilado con el fin de que
esa rutina en assembler nos guarde en una lista los mensajes que queremos procesar y sea inocuo
para el resto de mensajes.
Luego desde Axapta tendremos un timer puesto a pocos milisegundos que se encargará de procesar la lista
de mensajes y ejecutar las acciones pertinentes pero ya siempre desde la sencillez y facilidad que nos
proporciona el lenguaje X++.
Todo esto lo encapsularemos en una clase a la que solo le diremos algo así como ... Captura los mensajes
de este Hwnd y ejecuta este método si llega un mensaje de tipo "WM_LBUTTONDOWN" (por ejemplo).
Desarrollo
Creamos una clase abstracta base que proporcionará la estructura necesaria para gestionar la cola
de mensajes, el bucle interno temporizado y ofrecer la interface de determinadas funciones de la API
de Windows que vamos a necesitar.
Luego creamos la clase que, heredando de la anterior, instalará la rutina WindowProc y gestionará el sistema
de captura de mensajes desde el form especificado.
El método installMainProc es la clave de todo este sistema, en el llenamos el buffer con datos, punteros a
funciones y código assembler compilado.
| x++: |
public void installMainProc ()
{
HWnd OldWndProc;
HWnd _CallWndProc;
#WINAPI
#macro. conadd
% 1 = conins(% 1, conlen(% 1)+ 1,% 2);
#ifnot. empty(% 3)
% 1 = conins(% 1, conlen(% 1)+ 1,% 3);
#endif
#ifnot. empty(% 4)
% 1 = conins(% 1, conlen(% 1)+ 1,% 4);
#endif
#ifnot. empty(% 5)
% 1 = conins(% 1, conlen(% 1)+ 1,% 5);
#endif
#ifnot. empty(% 6)
% 1 = conins(% 1, conlen(% 1)+ 1,% 6);
#endif
#ifnot. empty(% 7)
% 1 = conins(% 1, conlen(% 1)+ 1,% 7);
#endif
#ifnot. empty(% 8)
% 1 = conins(% 1, conlen(% 1)+ 1,% 8);
#endif
#ifnot. empty(% 9)
% 1 = conins(% 1, conlen(% 1)+ 1,% 9);
#endif
#endmacro
void PrepareBuffer ( int _oldProc )
{
container Fnc;
int i;
// Vamos a jugar un poco con ensamblador
// Ok, let's play a bit with assembler
i = #CodeSize+#InternalDataSize + (#DataSize * #MaxDataelements );
MainBuffer = new Binary (i );
#conadd (Fnc,0x55 ) // push ebp
#conadd (Fnc,0x8B, 0xEC ) // mov ebp,esp
#conadd (fnc,0x51 ) // push ecx
#ConAdd (fnc,0x8B, 0x05 ) // mov eax, [num.elementos]
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(HooksBuffer, 0));
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(HooksBuffer, 1));
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(hooksBuffer, 2));
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(hooksBuffer, 3));
#ConAdd (Fnc,0xB9,0x00,0x00,0x00,0x00 ) // mov ecx, 0
#ConAdd (Fnc,0x83, 0xC1, 0x04 ) // add ecx,4
#ConAdd (Fnc,0x48 ) // dec eax
#ConAdd (Fnc,0x8B,0x91 ) // mov edx, [ecx + HooksBuffer]
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(HooksBuffer, 0));
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(HooksBuffer, 1));
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(hooksBuffer, 2));
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(hooksBuffer, 3));
#ConAdd (Fnc,0x3b, 0x55, 0x0C ) // cmp edx,[ebp + 0Ch]
#ConAdd (Fnc,0x74,0x2B ) // je + 2Bh
#ConAdd (Fnc, 0x85, 0xC0 ) // test eax, eax
#ConAdd (Fnc, 0x75, 0xED ) // jnz - 13h
// Vamos a llamar a CallWindowProc(oldProc)
#ConAdd (Fnc,0x8B, 0x45, 0x14 ) // mov eax, [ebp+14h] (Lparam -> eax)
#ConAdd (Fnc,0x50 ) // push eax
#ConAdd (Fnc,0x8B, 0x45, 0x10 ) // mov eax, [ebp+10h] (WParam -> eax)
#ConAdd (Fnc,0x50 ) // push eax
#ConAdd (Fnc,0x8B, 0x45, 0x0C ) // mov eax, [ebp+0Ch] (MSG -> eax)
#ConAdd (Fnc,0x50 ) // push eax
#ConAdd (Fnc,0x8B, 0x45, 0x08 ) // mov eax, [ebp+08h] (HWND -> eax)
#ConAdd (Fnc,0x50 ) // push eax
#ConAdd (Fnc,0xA1 ) // mov eax, [oldproc]
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(MainBuffer, 0,#CodeSize ));
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(MainBuffer, 1,#CodeSize ));
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(MainBuffer, 2,#CodeSize ));
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(MainBuffer, 3,#CodeSize ));
#ConAdd (Fnc,0x50 ) // push eax
#ConAdd (Fnc,0xFF,0x15 ) // call dword ptr [xxxxx]
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(MainBuffer, 0,#offsetCallwndproc ));
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(MainBuffer, 1,#offsetCallwndproc ));
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(MainBuffer, 2,#offsetCallwndproc ));
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(MainBuffer, 3,#offsetCallwndproc ));
#ConAdd (Fnc,0x89,0x45,0xFC ) // mov [ebp-04h], eax (retorno del valor devuelto)
#ConAdd (fnc,0x8B,0x45,0xFC ) // mov eax, [ebp-04h]
#Conadd (Fnc,0x59 ) // pop ecx
#Conadd (Fnc,0x5D ) // pop ebp
#ConAdd (Fnc,0xC2,0x10,0x00 ) // ret 010h
//Guardamos el mensaje en el buffer para procesarlo más tarde
#conadd (fnc,0x8B, 0x0D ) // mov ecx, [xxxxx] //contador
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(MainBuffer, 0,#offsetCounter ));
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(MainBuffer, 1,#offsetCounter ));
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(MainBuffer, 2,#offsetCounter ));
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(MainBuffer, 3,#offsetCounter ));
#conadd (fnc,0x8B, 0x45, 0x08 ) // mov eax, [ebp+08h] (HWND)
#conadd (Fnc,0x89,0x81 ) // mov [ecx + HWnd], eax (dirección destino 4 bytes)
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(MainBuffer, 0,#CodeSize + #InternalDataSize ));
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(MainBuffer, 1,#CodeSize + #InternalDataSize ));
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(MainBuffer, 2,#CodeSize + #InternalDataSize ));
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(MainBuffer, 3,#CodeSize + #InternalDataSize ));
#conadd (Fnc,0x8B, 0x45, 0x0C ) // mov eax, [ebp+0Ch] (MSG)
#conadd (Fnc,0x89,0x81 ) // mov [ecx + Msg], eax (dirección destino 4 bytes)
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(MainBuffer, 0,#CodeSize + #InternalDataSize + 4));
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(MainBuffer, 1,#CodeSize + #InternalDataSize + 4));
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(MainBuffer, 2,#CodeSize + #InternalDataSize + 4));
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(MainBuffer, 3,#CodeSize + #InternalDataSize + 4));
#conadd (Fnc,0x8B, 0x45, 0x10 ) // mov eax, [ebp+10h] (WParam)
#ConAdd (Fnc,0x89,0x81 ) // mov [ecx + wParam], eax (dirección destino 4 bytes)
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(MainBuffer, 0,#CodeSize + #InternalDataSize + 8));
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(MainBuffer, 1,#CodeSize + #InternalDataSize + 8));
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(MainBuffer, 2,#CodeSize + #InternalDataSize + 8));
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(MainBuffer, 3,#CodeSize + #InternalDataSize + 8));
#conadd (Fnc,0x8B, 0x45, 0x14 ) // mov eax, [ebp+14h] (LParam)
#Conadd (Fnc,0x89,0x81 ) // mov [ecx + Lparam], eax (dirección destino 4 bytes)
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(MainBuffer, 0,#CodeSize + #InternalDataSize + 12));
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(MainBuffer, 1,#CodeSize + #InternalDataSize + 12));
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(MainBuffer, 2,#CodeSize + #InternalDataSize + 12));
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(MainBuffer, 3,#CodeSize + #InternalDataSize + 12));
// Guardamos la posición del último msg
#conAdd (Fnc,0x89,0x0D ) // mov [xxxxx], ecx
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(MainBuffer, 0,#offsetlastmsg ));
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(MainBuffer, 1,#offsetlastmsg ));
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(MainBuffer, 2,#offsetlastmsg ));
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(MainBuffer, 3,#offsetlastmsg ));
// Ahora a incrementar el contador del buffer
#ConAdd (Fnc,0x83, 0xC1, 0x10 ) // add ecx, 10h (10h es DataSize)
#ConAdd (Fnc,0x81,0xF9,0x40,0x06,0x00,0x00 ) // cmp ecx, 280h (100 elementos de 10h)
#Conadd (Fnc,0x7C,0x05 ) // jl +05h
#conAdd (Fnc,0xB9,0x00,0x00,0x00,0x00 ) // mov ecx, 0 (Llega al máximo, volvemos al cero)
// Guardamos el contador
#conAdd (Fnc,0x89,0x0D ) // mov [xxxxx], ecx
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(MainBuffer, 0,#offsetCounter ));
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(MainBuffer, 1,#offsetCounter ));
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(MainBuffer, 2,#offsetCounter ));
Fnc = Conins(Fnc, Conlen(Fnc )+ 1,this. BufferAddr(MainBuffer, 3,#offsetCounter ));
#ConAdd (Fnc,0xB8,0x00,0x00,0x00,0x00 ) // mov eax, 0 (Demomento nos quedamos el mensaje)
#ConAdd (Fnc,0x89,0x45,0xFC ) // mov [ebp-04h], eax (retorno del valor devuelto)
#ConAdd (fnc,0x8B,0x45,0xFC ) // mov eax, [ebp-04h]
#Conadd (Fnc,0x59 ) // pop ecx
#Conadd (Fnc,0x5D ) // pop ebp
#ConAdd (Fnc,0xC2,0x10,0x00 ) // ret 010h
// Datos Internos (InternalData) (para ejemplo de codigo de 54 bytes)
// 54..57 oldproc
// 58..61 contador buffer (el próximo hueco en el buffer para escribir)
// 62..65 lastread (el último leído de forma fiable)
// Estructura del Databuffer primer elemento, luego multiplos
// 66..69 hwnd
// 70..73 msg
// 74..77 wparam
// 78..81 lparam
for (i= 1; i <= conlen(Fnc ); i++ )
{
MainBuffer. byte(i- 1, conpeek(Fnc,i ));
}
MainBuffer. dWord(#CodeSize,_oldProc );
MainBuffer. dWord(#OffsetCounter, 0);
_CallWndProc = this. GetProcAddress(User32Handle, 'CallWindowProcA');
MainBuffer. dWord(#OffsetCallWndProc,_CallWndProc );
}
OldWndProc = Winapi:: getWindowLong(xhWnd,- 4);
PrepareBuffer (OldWndProc );
if (! this. SetWindowLong(xhWnd, - 4, this. BufferAddr(MainBuffer )))
throw error (StrFmt ('Error en setwindowlong %1',winapi:: getLastError()));
else
{
installed = true;
this. InternalLoop();
}
}
|
La función en assembler que almacenamos en el buffer hace lo siguiente :
1.- Comprueba si el mensaje que nos llega se encuentra en la lista de hooks solicitados desde el Form.
2.- Si no es uno de los mensajes que debamos capturar, finaliza llamando al siguiente WindowProc de la
cadena de proceso de esta ventana.
3.- En caso de que sea uno de los mensajes que debemos capturar, almacena en la cola de mensajes
a procesar el mensaje actual y retorna el control al sistema indicando que hemos atendido al mensaje.
Realmente la rutina asm no añade mensajes a la cola de mensajes directamente sino que va llenando
un buffer de 100 elementos que se encuentra al final del mainBuffer. Luego una función de Axapta se
encarga de ir leyendo este buffer y añadir mensajes a la cola de mensajes pendientes.
Veamos la función que se encarga de actualizar la cola y procesar los mensajes (se encuentra en la
clase base) cada cierto tiempo (pocos milisegundos).
| x++: |
protected void InternalLoop()
{
map MMsg;
;
While (ReadPointer != (MainBuffer.dWord(this.ConstOffsetCounter()) div this.ConstDataSize()) )
{
MMsg = this.InternalGetMsg();
msgQueue.addEnd(MMsg);
ReadPointer++;
ReadPointer = ReadPointer mod this.ConstMaxDataElements();
}
this.ProcessMessages();
this.setTimeOut('Internalloop',5);
}
|
Este método usa la función InternalGetMsg que se encarga de leer el siguiente mensaje almacenado en
el buffer y nos lo devuelve en forma de mapa donde tenemos :
- 'HWND' : El identificador de la ventana a la que iba dirigido el mensaje
- 'MSG' : El código de mensaje
- 'WPARAM' : El parametro wparam
- 'LPARAM' : El parametro lparam
A continuación añade el mensaje en la cola (msgQueue) y llama a la función ProcessMessages()
que se encargará de procesar los mensajes pendientes de la cola.
Bien, no vamos a dedicar más tiempo a ver como se ha desarrollado la clase y vamos a ver como se usa
De todas formas, cuando descargueis el proyecto podéis echarle un vistazo a todo y si surge alguna
duda ... estaré encantado de aclararla.
¿ Cómo se usa ?
La verdad es que no me resulta fácil imaginar casos en los que necesitemos llegar a este nivel
de control sobre una ventana concreta, ya que con las herramientas de que disponemos en Axapta es
más que suficiente para la mayoría de los casos que se nos puedan plantear. Hay que recordar que
se trata de una aplicación de gestión y por tanto los requerimientos no llegan casi nunca a estos niveles de "filigrana".
Un ejemplo muy simple : Vamos a crear un StringEdit en el que solo podremos escribir vocales.
Creamos un form, añadimos un StringEdit al que llamaremos "aeiouOnlyEdit" y establecemos
su propiedad AutoDeclaration a true.
En el classdeclaration :
| x++: |
public class FormRun extends ObjectRun
{
TrucosAx_TrapperWndProc WndProcTrap;
}
|
En el Run :
| x++: |
public void run()
{
#WINAPI
super();
WndProcTrap = new TrucosAx_TrapperwndProc(element, aeiouOnlyEdit.hWnd(),true);
WndProcTrap.InstallHook(#WM_CHAR, 'CharWrited');
}
|
Simplemente creamos el Trapper indicando el Hwnd que queremos controlar e instalamos un "hook"
para que cuando llegue un mensaje de tipo WM_CHAR se ejecute nuestro método CharWrited del formulario.
Creamos un método en el form llamado CharWrited :
| x++: |
void CharWrited()
{
map MMsg;
str _Char;
;
MMsg = WndProcTrap.GetMsg();
_Char = num2char(MMsg.lookup('WPARAM'));
if ( (_Char == 'A' || _Char == 'E' || _Char == 'I' || _Char == 'O' || _Char == 'U')
|| _Char == num2char(8)) // Tecla borrar - BackSpace key
{
// Correcto - Correct
}
else
{
MMsg.insert('RESULT','0');
}
}
|
Bien, esta función lo único que hace es comprobar si el código de caracter que se ha introducido en nuestro edit
se corresponde con alguna vocal o bien si es el caracter que se genera al pulsar la tecla "BackSpace" (borrar).
Si no es así, añade 'RESULT = 0' en el Mensaje para que la clase WndProc trate el mensaje como atendido y
rompa la cadena de WindowProcs. Es decir, si ponemos un 'RESULT' en el mensaje, este mensaje no llegará nunca
a la ventana destino y por tanto será como si no hubiesen pulsado la tecla.
Por último en el método close :
| x++: |
public void close()
{
WndProcTrap.RemoveMainProc();
super();
}
|
Es importante restaurar todo lo que hemos tocado
Este es solo un pequeño ejemplo de lo que se puede llegar a hacer controlando el WindowProc de una
ventana, pero estoy seguro de que se os ocurriran muchisimas más utilidades
Solución a otro problema (KeyboardProc)
Existe una funcionalidad muy solicitada que consiste en poder capturar las teclas de función (F1, F2, F3...F12)
para que, estando situados en un formulario (por ejemplo, un form de terminal punto de venta) podamos
asociar acciones diversas a cada una de esas teclas de función.
El problema es que cuando tenemos el foco de teclado en un StringEdit concreto y pulsamos una tecla,
el mensaje de KeyDown / WM_Char y KeyUp se envia a la ventana del StringEdit. Por tanto si capturamos
el WindowProc del form con la intención de capturar las pulsaciones de teclado que se realizan en sus controles
hijos....no recibiremos lo que andamos buscando.
En muchos lenguajes de programación (Delphi, Visual Basic ...) existe una propiedad en el form llamada algo
así como 'KeyPreview". En estos casos, los mensajes que llegan a los controles del interior del form, son
procesados por dichos controles que los envían también al formulario. De esta forma es posible capturar los
mensajes de teclado que se originen en cada uno de los controles hijos.
En nuestro caso, para poder hacer esto, debido a que no me pareció adecuado capturar los WndProc de
todos y cada uno de los controles que se encuentren dentro del formulario ... hemos definido otra clase que hereda
de TrucosAx_TrapperBase llamada TrucosAx_TrapperFormKeybProc.
Lo que hace esta clase es colgar una rutina en un Hook del sistema (Función SetWindowsHookEx) para
los eventos de teclado (KeyboardProc). Cuando le llega un evento de teclado, comprueba si el form activo
en ese momento es el Form sobre el que queríamos capturar el teclado y en caso afirmativo actua de forma
similar a la clase del WndProc. Es decir, guarda en la cola el mensaje, retorna al sistema como atendido
y luego mediante un timer procesa los mensajes pendientes.
Ejemplo sencillo de KeyboardProc : En pedidos de venta, si pulsamos F6 nos lleva a contabilizar el pedido.
Este ejemplo no está incluido en el download del truco, ya que no quiero romper el formulario SalesTable
de nadie en su lugar encontraréis un formulario llamado TrucosAx_TestKeybProc que contiene
un ejemplo muy sencillo de uso y además nos puede servir para reconocer los códigos de las teclas
que llegan en los mensajes de KeyboardProc.
Vamos a editar el form SalesTable :
En el classdeclaration :
Añadimos la línea :
| x++: |
TrucosAX_TrapperFormKeybProc KeyTrapper;
|
En el Run :
| x++: |
void run()
{
super();
KeyTrapper = new TrucosAx_TrapperFormKeybProc(element, 'KeyPress');
...
...
...
..
}
|
Y Creamos un método llamado KeyPress :
| x++: |
void KeyPress()
{
map MMsg = KeyTrapper.GetMsg();
MenuFunction MFu;
args parameters = new args();
int lpflag = 0x80000000;
#Define.keyF6(117)
parameters.record(Salestable);
parameters.caller(element);
if ((MMsg.lookup('LPARAM') & lpflag) == lpflag) // Keyup ?
{
if (MMsg.lookup('WPARAM') == #KeyF6 && ButtonUpdateInvoice.enabled())
{
MFu = new MenuFunction('SalesFormLetter_Invoice',MenuItemType::Action);
MFu.run(parameters);
}
}
}
|
Y por supuesto, no nos olvidemos del método close :
(si no ponemos esto ... puede quedar el sistema inestable)
Añadimos la línea antes del super() ...
| x++: |
KeyTrapper.RemoveMainProc();
|
NOTA : Es Importante señalar que tanto en un caso como en otro, si añadimos el item 'RESULT' con cualquier
valor en el mapa del mensaje desde nuestro método en el Form, no se dará continuidad al mensaje y este
se considerará como atendido.
Conclusión
Bueno, la conclusión es que .... quizá se me ha ido un poco la mano
Bien, ante todo aclarar que este es un truco experimental que está hecho con ánimo de investigar y divertirse
y quizá no funcione en todos los sistemas ni entornos.De todas formas estas clases nos brindan la posibilidad
de hacer algunas cosas que de otra forma eran imposibles o muy engorrosas.
El tema en sí era muy extenso y para no aburrir he preferido publicar solo un poco la idea y pasar a la parte
práctica del asunto, pero para cualquier aclaración o consulta ... nos vemos en los Foros
Para obtener más información acerca de la interpretación que se debe hacer de los parámetros que llegan
para uno u otro mensaje... etc, tenemos estos links :
-para el tema de "Window Proc"
http://msdn2.microsoft.com/en-us/library/ms632593
-Para el tema del "KeyboardProc"
http://msdn2.microsoft.com/en-us/library/ms644984
-Mensajes de Windows en general
http://msdn2.microsoft.com/en-us/library/ms644927.aspx
Para descargar el ejemplo (incluye las clases y 2 formularios de ejemplo) clic aquí (DOWNLOAD)
Saludos,
Mkz. |
|