Tutorial WinAPI C++ 3.3 (Creación del ObjetoEditBox)
Categorías : Windows, Programación, C y C++.

En este tutorial veremos cómo crear controles estándar de windows un poco en general, y en especial nos centraremos en el control EDITBOX.
Hasta ahora nos habíamos creado nuestros controles partiendo del ObjetoControl, pero ahora queremos utilizar un control de windows que ya tiene varios eventos programados, por lo que vamos a tener que enlazar a ellos. La diferencia entre ObjetoControl y ObjetoControlEstandar residirá en que ObjetoControl controla eventos que nosotros hemos programado completamente, y ObjetoControlEstandar controla eventos programados por microsoft en los cuales podemos añadir código al principio o al final según nos convenga.
Veamos la declaración de ObjetoControlEstandar :
// Clase que hereda PlantillaEventos y se especializa en controlesclass ObjetoControlEstandar : public PlantillaEventos<LRESULT, GESTOR_POR_DEFECTO> {public : //////////////////// Miembros publicos// -ConstructorObjetoControlEstandar(void);// -Destructor~ObjetoControlEstandar(void);// -Funcion que conecta el control estandar con esta claseHWND Asignar(HWND hWndParent, const int ID_Control);protected :// -Funcion que conecta el control estandar con esta clasevoid _ConectarControl(void);private : /////////////////// Miembros privados// -Gestor de mensajes estatico inicialstatic LRESULT CALLBACK _GestorMensajes( HWND nhWnd, UINT uMsg,WPARAM wParam, LPARAM lParam);// -WindowProcedure Orignal del control estándarWNDPROC _GestorMensajesOriginal;};
Podemos ver :
- La función publica Asignar, que usaremos solo para asignar controles que vengan de un dialogo a esta clase. (para controles creados dentro de una ventana se usa _ConectarControl)
- La función protegida _ConectarControl, que usaremos cuando se cree el control en una ventana.
- Un gestor de mensajes estático por el que pasaran todos los mensajes del control.
- Y un miembro WNDPROC que apunta al gestor de mensajes original del control.
Veamos… cuando creamos un control propio como por ejemplo el ObjetoBoton, nos estamos creando su WindowProcedure/GestorMensajes y lo adaptamos a nuestras necesidades. En cambio si utilizamos algún control estándar de windows, este trae su WindowProcedure por defecto.
A la hora de tratar los eventos no es lo mismo responder a un evento que hemos programado nosotros, que a un evento de un control estándar, ya que ademas de responder al evento debemos llamar también al WindowProcedure original del control para que nos procese el evento y nos actualice el control tal y como lo programo microsoft.
Veamos el _GestorMensajes de ObjetoControlEstandar :
LRESULT CALLBACK ObjetoControlEstandar::_GestorMensajes(HWND nhWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {ObjetoControlEstandar *Control = reinterpret_cast<ObjetoControlEstandar *>(LongToPtr(GetWindowLongPtr(nhWnd, GWL_USERDATA)));if (Control == NULL) return FALSE;LRESULT Ret = 0;if (uMsg == WM_PAINT) {return (Control->_GestorMensajesOriginal)(nhWnd, uMsg, wParam, lParam);}else {Ret = Control->GestorMensajes(uMsg, wParam, lParam);if (Ret == USAR_GESTOR_POR_DEFECTO) return (Control->_GestorMensajesOriginal)(nhWnd, uMsg, wParam, lParam);}return Ret;};
Exceptuando el mensaje WM_PAINT, cuando recibimos un mensaje lo procesamos primero nosotros y luego miramos si ha devuelto USAR_GESTOR_POR_DEFECTO. En caso de devolver USAR_GESTOR_POR_DEFECTO llamaremos al GestorMensajesOriginal del control estándar para que se use el código de microsoft.
Pongamos por ejemplo que tenemos un EditBox de windows bajo la clase ObjetoControlEstandar y recibimos el Evento_Teclado_TeclaPresionada. En el caso de retornar 0 lo que estariamos haciendo es decirle a ObjetoControlEstandar que no mande el mensaje al GestorMensajesOriginal del EditBox, por lo que no se mostraría la nueva tecla presionada en el control. En cambio si retornamos USAR_GESTOR_POR_DEFECTO, además de procesar nuestro código se procesara el código que pintara la tecla presionada en el EditBox.
Teniendo esto claro ya podemos empezar con el EditBox, veamos su declaración :
// Clase que hereda ObjetoControlEstandar y se centra en las funciones del editboxclass ObjetoEditBox : public ObjetoControlEstandar {public : //////////////////// Miembros publicos// -ConstructorObjetoEditBox(void);// -Destructor~ObjetoEditBox(void);// -Función para crear el EditBoxvoid CrearEditBox( HWND hWndParent, const TCHAR *nTexto, const UINT nEstilos,const int cX, const int cY, const int cAncho, const int cAlto,const UINT nID );// -Función para obtener el texto del EditBoxUINT ObtenerTexto(TCHAR *nTxt, const UINT TamTxt);// -Función para asignar el texto del EditBoxBOOL AsignarTexto(const TCHAR *nTxt);protected : ///////////////// Miembros protegidosHFONT _Fuente;};
Por el momento solo necesitamos una función para crear el EDITBOX, una función para asignar su texto y otra función para devolver su texto. Además en miembros protegidos declararemos una fuente que será la que usara para imprimir el texto dentro del EditBox.
Veamos la función CrearEditBox :
// Función para crear el EditBoxvoid ObjetoEditBox::CrearEditBox( HWND hWndParent, const TCHAR *nTexto, const UINT nEstilos,const int cX, const int cY, const int cAncho, const int cAlto,const UINT nID ) {ObjetoIniciarCommonControls::Iniciar();if (_hWnd != NULL) return;_hWnd = CreateWindowEx( NULL, TEXT("EDIT"), nTexto, nEstilos, cX, cY, cAncho, cAlto, hWndParent,reinterpret_cast<HMENU>(IntToPtr(nID)), GetModuleHandle(NULL), NULL );_ConectarControl();_Fuente = CreateFont( 13, 0, 0, 0, FW_NORMAL, false, false, false, DEFAULT_CHARSET,OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, PROOF_QUALITY, FF_ROMAN, TEXT("Tahoma") );SendMessage(_hWnd, WM_SETFONT, (WPARAM)_Fuente , 0);}
Lo primero que se hace es llamar a la función ObjetoIniciarCommonControls::Iniciar. Esta función llama a la API InitCommonControlsEx para iniciar los controles estándar de windows para nuestra aplicación. Antiguamente en VC6 esto era necesario si queríamos trabajar con controles estándar, pero ahora mismo a decir verdad ya no se si es necesario, porque en mi maquina me funcionan igual los ejemplos tanto si llamo a InitCommonControlsEx como si no… y en la MSDN no dice nada.. Por lo que prefiero mantener este codigo por si las moscas.
Lo segundo que se hace es llamar a la API CreateWindowEx con el segundo parámetro “EDIT” (que es el nombre de la clase). Esto hace que el control creado se convierta en un EditBox. Para ver más nombres de control mira este enlace de la MSDN : CreateWindow (Casi al final hay una tabla que muestra los nombres de classe mas comunes)
Lo siguiente es llamar a la función _ConectarControl, que re-emplazara el WindowProcedure del EditBox por el nuestro.
Y por último creamos una fuente con la API CreateFont para el editbox, y le mandamos el mensaje WM_SETFONT al editbox con la API SendMessage, cosa que le dirá al editbox que debe usar esa fuente.
Ya solo nos queda ver las funciones ObtenerTexto y AsignarTexto :
// Función para obtener el texto del EditBoxUINT ObjetoEditBox::ObtenerTexto(TCHAR *nTxt, const UINT TamTxt) {return GetDlgItemText(GetParent(_hWnd), GetWindowLongPtr(_hWnd, GWL_ID), nTxt, TamTxt);}// Función para asignar el texto del EditBoxBOOL ObjetoEditBox::AsignarTexto(const TCHAR *nTxt) {return SetDlgItemText(GetParent(_hWnd), GetWindowLongPtr(_hWnd, GWL_ID), nTxt);}
Para obtener el texto del editbox utilizamos la API GetDlgItemText, y para asignarlo utilizamos la API SetDlgItemText. Además utilizamos la API GetWindowLongPtr para obtener la ID del editbox que necesitamos a la hora de asignar / obtener su texto.
Con esto ya tenemos suficiente para cumplir con los requisitos del instalador y el ensamblador, pero debéis saber que para obtener eventos del editbox debemos mirar si recibimos el mensaje WM_COMMAND en la ventana padre, y a partir de allí mirar que código de notificación nos da para determinar que acción nos está reportando. De todas formas puede que necesitemos obtener un evento que no retorne el editbox por defecto, como por ejemplo cuando el usuario presiona la tecla intro. En este caso como tenemos el objeto encapsulado de tal forma que re-emplazamos su window procedure original, bastaría con crear el Evento_Teclado_TeclaPresionada dentro del EditBox, desde dentro de la función del evento mandaríamos un mensaje al objeto EventosPadre. Luego habría que añadir una función virtual nueva en el objeto EventosPadre "EditBox_Evento_Teclado_Intro" que enlazaríamos en el GestorMensajes con el mensaje que mandamos desde ObjetoEditBox::Evento_Teclado_TeclaPresionada.
Si aun no lo veis muy claro, un buen ejemplo seria el Evento_ObjetoBoron_Click (parte del GestorMensajes):
// ObjetoBoton Clickcase WM_BOTON_CLICK :return this->Evento_ObjetoBoton_Click(reinterpret_cast<ObjetoBoton *>(wParam), static_cast<UINT>(lParam));
La declaración de PlantillaEventos::Evento_ObjetoBoron_Click :
// -Función enlazada al mensaje WM_BOTON_CLICKvirtual TIPO_DEVUELTO Evento_ObjetoBoton_Click( ObjetoBoton *BtnPresionado,const UINT nBoton ) {return VALOR_DEVUELTO;};
Y como mandamos el mensaje desde el ObjetoBoton utilizando las API's PostMessage y GetParent :
LRESULT ObjetoBoton::Evento_Mouse_BotonSoltado(const UINT Boton, const int cX, const int cY, const UINT Params) {ReleaseCapture();if (_Presionado == true) {RECT RectaControl;GetClientRect(_hWnd, &RectaControl);POINT Pt = { cX, cY };_Presionado = false;_Estado = ObjetoBoton_EstadoNormal;// Si el mouse esta dentro del control mandamos el mensaje WM_BOTON_CLICK a la ventana padre del botón.if (PtInRect(&RectaControl, Pt) != 0) {UINT IDBoton = ID();PostMessage(GetParent(_hWnd), WM_BOTON_CLICK, reinterpret_cast<WPARAM>(this), static_cast<LPARAM>(Boton));}}Repintar(true);return 0;}
Para el caso del evento TeclaPresionada_Intro, con que incluyamos la ID del EditBox en el WPARAM, no hace falta mucho mas, por lo que podríamos dejar el LPARAM vacio. La función virtual EditBox_Evento_Teclado_Intro solo recibiria un UINT como parametro que seria la ID que mandamos antes en el WPARAM.
Para mas información referente al control EditBox consulta el siguiente enlace de la MSDN : Edit Control.
Siguiente tutorial : 3.4 Creación del ObjetoButton.