| |||||||||||||||||||
Subclasificación Este capítulo supone que tienes conocimeintos previos en. . . 7.1 SubclasificaciónExisten muchos controles estándares de Windows disponibles para que el programador pueda reutilizarlos tales como etiquetas, cajas de texto o botones. Cada uno de ellos tiene un procedimiento de ventana distinto que procesa los mensajes y efectua los comportamientos adecuados del control. El procedimiento de ventana para los controles está en algún sitio dentro de Windows. Así, por ejemplo, los mensajes que recibe una caja de texto son procesados por este procedimiento de ventana predeterminado del que hablamos y el programador no debe preocuparse por codificar las acciones cuando el usuario selecciona el texto, lo modifica, lo inserta entre dos letras o hace un Copy&Paste. ¿Pero qué sucede si deseamos todas las características de una caja de texto pero que solo acepte números ignorando cualquier otro caracter? Es posible obtener la dirección de este procedimiento de ventana mediante una llamada La técnica de cambiar la dirección del procedimiento de ventana se llama subclasificación de ventana y es muy necesario. Permite monitorear y procesar los mensajes que recibe la ventana o control y pasar los restantes al procedimiento de ventana antiguo.
//1. Obtener el procedimiento actual de la ventana WNDPROC ViejoProc; ViejoProc = (WNDPROC) GetWindowLongPtr(hCONTROL, GWLP_WNDPROC); //2. Cambiar el procedimiento por NuevoProcedimiento() SetWindowLongPtr(hCONTROL, GWLP_WNDPROC, (LONG_PTR) NuevoProcedimiento); //3. Manejar los mensajes de interés LRESULT CALLBACK NuevoProcedimiento(HWND hwnd, UINT iMensaje, WPARAM wParam, LPARAM lParam) { switch (iMensaje) { //Manipulación de los eventos de hCONTROL } return CallWindowProc (ViejoProc, hwnd, iMensaje, wParam, lParam); } En el capítulo anterior, se utilizó la función 7.2 Subclasificación en C++Si desea utilizar las bondades de la programación orientada a objetos en C++ para crear controles a la medida, la técnica de subclasificación es un poco más laboriosa. Una ventaja de utilizar objetos que tienen constructores y destructores, es la posibilidad de olvidarse de liberar recursos innecesarios si se programa una librería lo suficientemente robusta. Otra ventaja es que al utilizar objetos, la programación se torna mucho más intuitiva. Esto se refleja en un ejecutable más sólido. Como probablemente sabe, el puntero Quizás ya tenga en mente la manera de lidiar con esto, existen diversos caminos que se pueden tomar. Una forma sería utilizando la función miembro
class CONTROL_SUBCLASIFICADO { private: //Aquí se guardará el procedimiento de ventana por defecto de Windows WNDPROC ViejoProc; public: CONTROL_SUBCLASIFICADO() { ... } //Constructor ~CONTROL_SUBCLASIFICADO() { ... }; //Destructor void Create(HWND hPADRE, LPCTSTR lpTexto, UINT x, UINT y, UINT w, UINT h) { ... hCONTROL = CreateWindowEx(...); ... /* 1. Guardar el puntero this en los datos de usuario del control (GWLP_USERDATA) SetWindowLongPtr(hCONTROL, GWLP_USERDATA, (LONG_PTR) this);============================================================================== */ /* 2. Subclasificar a una función estática despachadora (sin puntero this) ViejoProc = (WNDPROC) SetWindowLongPtr(hCONTROL, GWLP_WNDPROC,======================================================================= */ (LONG_PTR) Despachador); } /* 3. Función estática que obtiene el puntero this previamente almacenado en los static LRESULT CALLBACK Despachador (HWND hwnd, UINT iMensaje,datos de usuario del control y llama al Procedimiento de Mensajes adecuado ============================================================================= */ WPARAM wParam, LPARAM lParam) { CONTROL_SUBCLASIFICADO* pControl; pControl = (CONTROL_SUBCLASIFICADO *) GetWindowLongPtr(hwnd, GWLP_USERDATA); //Despachar a procedimiento correspondiente al control return pControl->Procedimiento (hwnd, iMensaje, wParam, lParam); } /* 4. Procedimiento de Ventana LRESULT CALLBACK Procedimiento(HWND hwnd, UINT iMensaje,=========================== */ WPARAM wParam, LPARAM lParam) { switch (iMensaje) { ... } /* 5. Procesar mensajes en procedimiento de ventana por defecto return CallWindowProc (ViejoProc, hwnd, iMensaje, wParam, lParam);============================================================ */ } }; 7.3 De la teoría a la prácticaLa finalidad de este ejercicio es modificar el comportamiento del control de caja de texto (EDIT) para que solo permita introducir números al usuario. Cualquier otro caracter, deberá ser ignorado por el control. Es una modificación básica que tiene fines didácticos. Este nuevo control no permitirá escribir números negativos (signo -) ni indicar comas o puntos de separación; se deja el reto al lector para añadir el código necesario para hacer posible esto último.
#include <windows.h> class EDIT_NUMERICO { public: EDIT_NUMERICO() { hPADRE = hCONTROL = 0; } //Constructor ~EDIT_NUMERICO() {}; //Destructor void Create(HWND hWindow, LPCTSTR lpText, UINT x, UINT y, UINT w, UINT h) { hPADRE = hWindow; if (!hCONTROL) hCONTROL = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", lpText, WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL, x, y, w, h, hPADRE, NULL, NULL, NULL); if (hCONTROL == NULL) MessageBox(hPADRE, "Ocurrió un error al crear el control.", "¡Error!", MB_OK | MB_ICONERROR); else { //Cambiar fuente del control SendMessage(hCONTROL, WM_SETFONT, (WPARAM) GetStockObject(DEFAULT_GUI_FONT), MAKELPARAM(FALSE, 0)); /* 1. Guardar el puntero this en los datos de usuario del control (GWLP_USERDATA) SetWindowLongPtr(hCONTROL, GWLP_USERDATA, (LONG_PTR) this);============================================================================== */ /* 2. Subclasificar a una función estática despachadora (sin puntero this) ViejoProc = (WNDPROC) SetWindowLongPtr(hCONTROL, GWLP_WNDPROC,======================================================================= */ (LONG_PTR) DispatchProc); } } /* 3. Función estática que obtiene el puntero this previamente almacenado en los static LRESULT CALLBACK DispatchProc (HWND hwnd, UINT iMensaje,datos de usuario del control y llama al Procedimiento de Mensajes adecuado ============================================================================= */ WPARAM wParam, LPARAM lParam) { EDIT_NUMERICO* pControl; pControl = (EDIT_NUMERICO *) GetWindowLongPtr(hwnd, GWLP_USERDATA); //Despachar a procedimiento correspondiente al control return pControl->Procedimiento (hwnd, iMensaje, wParam, lParam); } /* 4. Procedimiento de Ventana LRESULT CALLBACK Procedimiento(HWND hwnd, UINT iMensaje,=========================== */ WPARAM wParam, LPARAM lParam) { if (iMensaje == WM_PASTE) return 0; //prohibimos pegar pues podría contener texto if (iMensaje == WM_CHAR) { //Si no es la tecla de retroceso ni número del 0 al 9 ignorar pulsación if (((wParam < '0') && (wParam != 0x08)) || (wParam > '9')) return 0; } /* 5. Procesar mensajes en procedimiento de ventana por defecto return CallWindowProc (ViejoProc, hwnd, iMensaje, wParam, lParam);============================================================ */ } private: HWND hCONTROL, hPADRE; WNDPROC ViejoProc; };
#include "ventana.h" #include "edit_num.h" EDIT_NUMERICO Numerico; LRESULT CALLBACK ProcedimientoVentana(HWND hPadre, UINT mensaje, WPARAM wParam, LPARAM lParam) { switch (mensaje) { case WM_CREATE: { //Se crea el control Numerico.Create(hPadre, "", 10, 10, 180, 20); break; } case WM_DESTROY: { PostQuitMessage (0); return 0; } } return DefWindowProc (hPadre, mensaje, wParam, lParam); } int WINAPI WinMain(HINSTANCE hInstancia, HINSTANCE hInstanciaPrev, LPSTR lpLineaCmd, int nEstadoVentana) { CreaVentana("Programa 6", 208, 80, hInstancia, ProcedimientoVentana); // Bucle de mensajes: MSG mensaje; while(GetMessage(&mensaje, 0, 0, 0) > 0) { TranslateMessage(&mensaje); DispatchMessage(&mensaje); } return (int) mensaje.wParam; }
Observe que el código utilizado en Programa6.cpp consta de unas cuantas líneas. Eso si consideramos que los archivos de encabezado ventana.h y edit_num.h podrán re-utilizarse en proyectos futuros. La ventana generada deberá verse así:
Utilice el programa generado. Intente introducir información numérica y no numérica para observar el comportamiento del control. Compruebe que se ha bloqueado la acción de pegar texto en este control modificado con la finalidad de evitar que se introduzcan caracteres no deseados (ej. letras). Analizar la información del portapapeles para saber si se trata de información numérica o de texto queda fuera de las intenciones didácticas de este ejercicio. 7.4 Subclasificación de ventanas en C++El método para subclasificar en C++ visto en el tema anterior es igualmente válido, aunque con ciertas consideraciones, para ventanas en las que definimos una clase de ventana A diferencia de los controles (con clases de ventana existentes), en las ventanas con clase de ventana propia se debe considerar que al terminar de llamar a la función Recuerde que el procedimiento de ventana es indicado en el campo Bien, es posible modificar el procedimiento de ventana ¿Qué sucede si se desea procesar alguno de estos mensajes que no pasan por el bucle de mensajes? Existe una técnica para almacenar al puntero
class VENTANA { public: ... int Crear( ... ); //1. Crear un procedimiento static (sin puntero this) static LRESULT CALLBACK Despachador( ... ); LRESULT CALLBACK Procedimiento( ... ); ... }; int VENTANA::Crear( ... ) { ... WNDCLASSEX clsventana; clsventana.cbSize = ... ... //2. Asignar el procedimiento de ventana STATIC clsventana.lpfnWndProc = Despachador; ... //3. Indicar el puntero this en el último parámetro hWND = CreateWindowEx( ... , this); ... } LRESULT CALLBACK VENTANA::Despachador(HWND hwnd, UINT iMensaje, WPARAM wParam, LPARAM lParam) { VENTANA* pVentana; //4. Identificar el mensaje WM_NCCREATE if (iMensaje == WM_NCCREATE) { CREATESTRUCT* c; //5. Extraer el puntero this del parámetro lpParam de CreateWindowEx() pVentana = (VENTANA*) ((CREATESTRUCT *) lParam)->lpCreateParams; //6. Almacenar el puntero this en la información de usuario como se vio en el // tema anterior SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR) pVentana); } //7. Obtener el puntero this de la información de usuario como se vio en el // tema anterior pVentana = (VENTANA*) GetWindowLongPtr(hwnd, GWLP_USERDATA); //Despachador al procedimiento de ventana correspondiente //======================================================= if (pVentana == NULL) //Mensaje WM_GETMINMAXINFO, procesado por Windows return DefWindowProc (hwnd, iMensaje, wParam, lParam); else //8. Despachar al procedimiento return pVentana->Procedimiento (hwnd, iMensaje, wParam, lParam); } LRESULT CALLBACK VENTANA::Procedimiento(HWND hwnd, UINT iMensaje, WPARAM wParam, LPARAM lParam) { //9. Procesar mensajes switch (iMensaje) { case WM_CREATE: { //Inicia variables, crea controles, etc } break; ... case WM_DESTROY: PostQuitMessage (0); return 0; break; } //10. Procesar mensajes en procedimiento de ventana por defecto return CallWindowProc (ViejoProc, hwnd, iMensaje, wParam, lParam); } Observe que no se subclasifica a una función estática despachadora (sin puntero this) ya que esto no es necesario. La función despachadora se indica en el campo De esta manera, el procedimiento de ventana 7.5 Crear su propia libreríaSi está pensando en crear su propia librería para realizar proyectos, bien convendría dar un vistazo a proyectos del mismo índole. Se trata de no re-inventar la rueda. Recomiendo el proyecto SmartWin++ cuya librería es completamente gratuita y distribuye el código fuente con comentarios. 7.6 Ejercicios1. Crear una control EDIT numérico que acepte números positivos, negativos y con decimales. El resto de los caracteres deberán ser ignorados. Deberá ser válido pulsar la tecla de retroceso (backspace). Debe impedirse poner doble separación decimal o escribir un número incorrectamente (ej. "-100.1-2---3.10")
| |||||||||||||||||||
|