Tutorial WINAPI C++ 1.5 (Creación de nuestro ObjetoBoton)
Categorías : Windows, Programación, C y C++.

En este tutorial vamos a aplicar varios conceptos anteriores que son la creacion de ventanas y utilizar el GDI de windows para dar paso a la creacion de nuestro primer control desde cero.
Aunque windows ya nos ofrece toda una gama de controles listos para utilizar, siempre es interesante ver y comprender como podemos crear nosotros mismos nuestro propio control. Ademas hay muchos casos en los que podemos necesitar algo que no sea 'estandar' por lo que al final nos sera mas conveniente crear este tipo de cosas partiendo de cero.
Diría que el 90% de la gente que ha programado controles, programaron un botón la primera vez, y yo no fui una excepción. Por suerte vosotros vais a tener una orientación bastante mas enfocada de la que obtuve yo en su tiempo, lo que os permitirá hacer un botón chulo utilizando funciones de pintado del GDI de Windows junto con la clase ObjetoHWND que creamos anteriormente.
A la hora de hacer cualquier control hay que tener muy claras sus funciones y que tipo de comportamiento seguirá. En este caso sabemos que queremos un botón con el borde redondeado, el cual tenga todos los colores configurables, y podamos cambiar su fuente. Además este botón tendrá un efecto de resaltado cuando el mouse pase por encima, y un efecto de presión cuando pulsemos con el mouse encima de este.
Lo primero de todo va a ser hacernos nuestra clase base para controles, de la misma forma que tenemos la clase ObjetoVentana ahora vamos a hacer una clase ObjetoControl enfocada a controles que tienen una ventana padre.
// Clase que hereda ObjetoHWND y se especializa en controlesclass ObjetoControl : public PlantillaEventos<LRESULT, 0> {public : //////////////////// Miembros publicos// -ConstructorObjetoControl(void);// -Destructor~ObjetoControl(void);// Función para crear el controlHWND CrearControl( HWND hWndParent, const TCHAR *nNombre,UINT Estilos, const TCHAR *nTexto,const int cX, const int cY,const int cAncho, const int cAlto,const int nID,DWORD nEstiloExtendido = NULL );private : /////////////////// Miembros privados// -Gestor de mensajes estatico inicialstatic LRESULT CALLBACK _GestorMensajes( HWND nhWnd, UINT uMsg,WPARAM wParam, LPARAM lParam );};
Vamos a tener que modificar el mensaje WM_MOUSEMOVE para llamar a la API TrackMouseEvent. Esta API lo que hace es informarnos cuando el mouse sale del control mediante el mensaje WM_MOUSELEAVE. En el mensaje WM_MOUSELEAVE hay que poner el miembro _MouseDentro a false, para que cuando vuelva a entrar el mouse en el control se use la API TrackMouseEvent. (Todo esto se hace en la plantilla PlantillaEventos.
// Mouse move y leavecase WM_MOUSEMOVE :if (_MouseDentro == false) {TRACKMOUSEEVENT Trk;Trk.cbSize = sizeof(TRACKMOUSEEVENT);Trk.dwFlags = TME_LEAVE;Trk.hwndTrack = _hWnd;TrackMouseEvent(&Trk);_MouseDentro = true;}return this->Evento_Mouse_Movimiento( GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), static_cast<UINT>(wParam) );case WM_MOUSELEAVE :_MouseDentro = false;return this->Evento_Mouse_Saliendo();
Como cada control tendrá sus colores, y la verdad es un engorro ir creando una función para asignar y otra para obtener el color, crearemos una macro que nos ahorrara tener que escribir dichas funciones. La idea es que la macro reciba un parametro, que será el nombre de las funciones asignar y obtener, ademas también creara una variable del tipo COLORREF que tendrá el mismo nombre con el carácter '_' delante.
// Macro que crea un COLORREF con el nombre especificado en la seccion protected// y funciones publicas para asignar y obtener el color.#define AGREGAR_COLOR(NOMBRE) \public : \/* Función que devuelve el color _NOMBRE */ \inline COLORREF NOMBRE (void) { \return _ ## NOMBRE ; \}; \/* Función que asigna el color _NOMBRE */ \void NOMBRE (COLORREF nCol, const bool nRepintar) { \_ ## NOMBRE = nCol; \if (nRepintar == true) Repintar(); \}; \protected : \/* Color NOMBRE */ \COLORREF _ ## NOMBRE ;
Fijaros que se utiliza una función repintar dentro de la función que asigna el color, por ello solo podremos utilizar esta macro dentro de una clase que tenga dicha funcion Repintar implementada.
Ahora vamos a hacer una macro del mismo estilo que los colores pero para las fuentes, y en este caso solo tendrá una función. Dicha función será la encargada de crear la fuente en memoria, pero OJO esto no nos libra de borrar el contenido de esa variable, así que vamos a tener que acordarnos de ella en el destructor de la clase ObjetoBoton.
// Macro que crea un HFONT con el nombre especificado en la seccion protected// y una funcion publica para crear una fuente dentro de la variable.#define AGREGAR_FUENTE(NOMBRE) \public : \/* Función que crea una fuente con los parametros especificados en la variable _NOMBRE */ \void NOMBRE ( const TCHAR *Nombre, const int Tam, const bool Negrita, const bool Cursiva, const bool Subrayado ) { \if (_ ## NOMBRE != NULL) DeleteObject(_ ## NOMBRE ); \_ ## NOMBRE = CreateFont( Tam, 0, 0, 0, (Negrita) ? FW_BOLD : FW_NORMAL, Cursiva, Subrayado, false, DEFAULT_CHARSET, \OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, PROOF_QUALITY, FF_ROMAN, Nombre); \}; \protected : \/* Fuente NOMBRE */ \HFONT _ ## NOMBRE ;
Fijaros que dentro de la macro se usa la API CreateFont. esta API es la encargada de crear fuentes, y toda fuente que se cree con esta api, debe ser eliminada con la API DeleteObject cuando ya no sea necesaria.
Con esto ya tenemos una base para hacer controles que seran capaces de saber si el mouse esta dentro de ellos, ahora vamos a por el ObjetoBoton, empezaremos por definir todos sus estados :
// Estados posibles del ObjetoBotonenum ObjetoBoton_Estados {ObjetoBoton_EstadoNormal = 0,ObjetoBoton_EstadoResaltado = 1,ObjetoBoton_EstadoPresionado = 2,ObjetoBoton_EstadoDesactivado = 3};
Siempre que el control no esté desactivado, y el mouse no esté encima, el botón tendrá el estado normal. Si el mouse esta encima, y no esta desactivado, el botón tendrá el estado resaltado. Y si se ha presionado el control, y no esta desactivado, el botón tendrá el estado presionado. Si el control esta desactivado el botón no podrá tener otro estado que el desactivado.
Con esto queda bastante claro el comportamiento que deberá seguir el botón, asi que ya es hora de empezar con la declaración del ObjetoBoton :
// Clase que hereda ObjetoControl para hacer botonesclass ObjetoBoton : public ObjetoControl {public : ////////////////////// Miembros publicos// -ConstructorObjetoBoton(void);// -Destructor~ObjetoBoton(void);// -Función para crear el botonHWND CrearBoton( HWND hWndParent, const TCHAR *nTexto,const int cX, const int cY, const int cAncho, const int cAlto, const int nID,COLORREF nColor_TextoNormal = RGB(255, 255, 255),COLORREF nColor_TextoResaltado = RGB(255, 255, 0),COLORREF nColor_TextoPresionado = RGB(210, 210, 0),COLORREF nColor_TextoDesactivado = RGB(128, 128, 128),COLORREF nColor_DegradadoSuperior = RGB(200, 200, 200),COLORREF nColor_DegradadoInferior = RGB(100, 100, 100),COLORREF nColor_DegradadoResaltado = RGB(210, 210, 210),COLORREF nColor_BordeExteno = RGB(128, 128, 128),COLORREF nColor_BordeInterno = RGB(190, 190, 190),COLORREF nColor_BordeInternoResaltado = RGB(220, 220, 220) );// -Función para cambiar el texto del botónvoid Texto(const TCHAR *nTexto, const bool nRepintar = true);// -Función que retorna el texto del botónconst TCHAR *Texto(void);// -Función que pinta todo el botónvoid Pintar(HDC hDC);// -Función que repinta todo el botón si es necesariovoid Repintar(const bool Forzar = false);// -Función para activar / desactivar el botónBOOL Activado(const bool nActivar);////////////////////////////// Eventos re-emplazados// -Función enlazada al mensaje WM_ERASEBKGNDvirtual LRESULT Evento_BorrarFondo(HDC hDC);// -Función enlazada al mensaje WM_PAINTvirtual LRESULT Evento_Pintar(HDC hDC, PAINTSTRUCT &PS);// -Función enlazada al mensaje WM_MOUSEMOVEvirtual LRESULT Evento_Mouse_Movimiento(const int cX, const int cY, const UINT Params);// -Función enlazada al mensaje WM_MOUSELEAVEvirtual LRESULT Evento_Mouse_Saliendo(void);// -Función enlazada a todos los mensajes WM_?BUTTONDOWNvirtual LRESULT Evento_Mouse_BotonPresionado(const UINT Boton, const int cX, const int cY, const UINT Params);// -Función enlazada a todos los mensajes WM_?BUTTONUPvirtual LRESULT Evento_Mouse_BotonSoltado(const UINT Boton, const int cX, const int cY, const UINT Params);// -Función enlazada al mensaje WM_KEYDOWNvirtual LRESULT Evento_Teclado_TeclaPresionada(const UINT Caracter, const UINT Repeticion, const UINT Params);// -Función enlazada al mensaje WM_KEYUPvirtual LRESULT Evento_Teclado_TeclaSoltada(const UINT Caracter, const UINT Repeticion, const UINT Params);/////////////////////////////// Funciones para el aspecto grafico// -Macro que crea funciones para asignar y obtener el COLORREF _Color_TextoNormalAGREGAR_COLOR(Color_TextoNormal);// -Macro que crea funciones para asignar y obtener el COLORREF _Color_TextoResaltadoAGREGAR_COLOR(Color_TextoResaltado);// -Macro que crea funciones para asignar y obtener el COLORREF _Color_TextoPresionadoAGREGAR_COLOR(Color_TextoPresionado);// -Macro que crea funciones para asignar y obtener el COLORREF _Color_TextoDesactivadoAGREGAR_COLOR(Color_TextoDesactivado);// -Macro que crea funciones para asignar y obtener el COLORREF _Color_DegradadoSuperiorAGREGAR_COLOR(Color_DegradadoSuperior);// -Macro que crea funciones para asignar y obtener el COLORREF _Color_DegradadoInferiorAGREGAR_COLOR(Color_DegradadoInferior);// -Macro que crea funciones para asignar y obtener el COLORREF _Color_DegradadoResaltadoAGREGAR_COLOR(Color_DegradadoResaltado);// -Macro que crea funciones para asignar y obtener el COLORREF _Color_BordeExternoAGREGAR_COLOR(Color_BordeExterno);// -Macro que crea funciones para asignar y obtener el COLORREF _Color_BordeInternoAGREGAR_COLOR(Color_BordeInterno);// -Macro que crea funciones para asignar y obtener el COLORREF _Color_BordeInternoResaltadoAGREGAR_COLOR(Color_BordeInternoResaltado);// -Macro que crea la función Fuente_Desactivada, y la variable HFONT _Fuente_NormalAGREGAR_FUENTE(Fuente_Normal);// -Macro que crea la función Fuente_Desactivada, y la variable HFONT _Fuente_ResaltadaAGREGAR_FUENTE(Fuente_Resaltada);// -Macro que crea la función Fuente_Desactivada, y la variable HFONT _Fuente_PresionadaAGREGAR_FUENTE(Fuente_Presionada);// -Macro que crea la función Fuente_Desactivada, y la variable HFONT _Fuente_DesactivadaAGREGAR_FUENTE(Fuente_Desactivada);protected : /////////////////// Miembros privados// -Texto del botónTCHAR *_Texto;// -Estado actual del botónObjetoBoton_Estados _Estado;// -Ultimo estado del botónObjetoBoton_Estados _UEstado;// -Valor para saber si el boton esta presionadobool _Presionado;};
En la declaración se puede observar por encima que se van a re-emplazar los eventos del mouse y algunos del teclado, además también se ha añadido unas funciones para asignar y obtener el texto, una función para crear el botón, y un par de funciones para el pintado.
Una vez tenemos declarada la clase nos tocara empezar a programar sus funciones, empezaremos por la función CrearBoton :
// Factor de redondeamiento que se usara en el botón#define REDONDEAMIENTO 2// Función para crear el botónHWND ObjetoBoton::CrearBoton( HWND hWndParent, const TCHAR *nTexto,const int cX, const int cY, const int cAncho, const int cAlto, const int nID,COLORREF nColor_TextoNormal, COLORREF nColor_TextoResaltado, COLORREF nColor_TextoPresionado,COLORREF nColor_TextoDesactivado, COLORREF nColor_DegradadoSuperior, COLORREF nColor_DegradadoInferior,COLORREF nColor_DegradadoResaltado, COLORREF nColor_BordeExterno, COLORREF nColor_BordeInterno,COLORREF nColor_BordeInternoResaltado ) {// Asigno los colores_Color_TextoNormal = nColor_TextoNormal;_Color_TextoResaltado = nColor_TextoResaltado;_Color_TextoPresionado = nColor_TextoPresionado;_Color_TextoDesactivado = nColor_TextoDesactivado;_Color_DegradadoSuperior = nColor_DegradadoSuperior;_Color_DegradadoInferior = nColor_DegradadoInferior;_Color_DegradadoResaltado = nColor_DegradadoResaltado;_Color_BordeExterno = nColor_BordeExterno;_Color_BordeInterno = nColor_BordeInterno;_Color_BordeInternoResaltado = nColor_BordeInternoResaltado;// Creo las fuentes_Fuente_Normal = CreateFont( 13, 0, 0, 0, FW_NORMAL, false, false, false, DEFAULT_CHARSET,OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, PROOF_QUALITY, FF_ROMAN, TEXT("Tahoma"));_Fuente_Resaltada = CreateFont( 13, 0, 0, 0, FW_NORMAL, false, true, false, DEFAULT_CHARSET,OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, PROOF_QUALITY, FF_ROMAN, TEXT("Tahoma"));_Fuente_Presionada = CreateFont( 13, 0, 0, 0, FW_NORMAL, false, true, false, DEFAULT_CHARSET,OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, PROOF_QUALITY, FF_ROMAN, TEXT("Tahoma"));_Fuente_Desactivada = CreateFont( 13, 0, 0, 0, FW_NORMAL, false, false, false, DEFAULT_CHARSET,OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, PROOF_QUALITY, FF_ROMAN, TEXT("Tahoma"));// Asigno los estados_Estado = ObjetoBoton_EstadoNormal;_UEstado = ObjetoBoton_EstadoDesactivado;_Presionado = false;Texto(nTexto, false);// Creo el botónCrearControl(hWndParent, TEXT("ObjetoBoton"), WS_CHILD, NULL, cX, cY, cAncho, cAlto, nID, NULL);// Asigno una region redondeada al botónHRGN Region = CreateRoundRectRgn(0, 0, cAncho, cAlto, REDONDEAMIENTO, REDONDEAMIENTO);SetWindowRgn(_hWnd, Region, FALSE);// Hago visible el botónVisible(true);return _hWnd;}
Lo primero que se hace es asignar los todos los colores y crear todas las fuentes, para crear las fuentes utilizamos la API CreateFont. Luego creamos la ventana que representa el botón, y por último como necesitamos que el botón tenga las puntas redondeadas utilizamos la API CreateRoundRectRgn que crea una región recta redondeada con el área especificada, finalmente utilizamos la API SetWindowRgn para asignar dicha región a nuestro control. Al final de todo hacemos visible el control, de esta forma evitamos posibles repintados antes de tener perfilado el control.
Ahora veamos el Evento_Mouse_Movimiento :
LRESULT ObjetoBoton::Evento_Mouse_Movimiento(const int cX, const int cY, const UINT Params) {POINT Pt = { cX, cY };RECT RectaControl;GetClientRect(_hWnd, &RectaControl);BOOL DentroControl = PtInRect(&RectaControl, Pt);if (_Presionado == true) {if (DentroControl == TRUE) _Estado = ObjetoBoton_EstadoPresionado;else _Estado = ObjetoBoton_EstadoNormal;}else {if (DentroControl == TRUE) _Estado = ObjetoBoton_EstadoResaltado;else _Estado = ObjetoBoton_EstadoNormal;}Repintar();return ObjetoControl::Evento_Mouse_Movimiento(cX, cY, Params);}
En primer lugar se utiliza la API GetClientRect para obtener el ancho y el alto de nuestro botón, luego utilizamos la API PtInRect para saber si las coordenadas del mouse están dentro del control. Sabiendo si el mouse esta dentro del control y si el control esta presionado o no, elegimos el estado correcto para poder pintar el botón. Y por ultimo retornamos con la función ObjetoControl::MouseMove, ya que esta tiene implementada la rutina para que nos funcione el Evento_Mouse_Saliendo.
Hay que destacar que en principio no deberíamos controlar si el mouse esta fuera del control en el Evento_Mouse_Movimiento, pero en los Eventos_Mouse_BotonPresionado y Eventos_Mouse_BotonSoltado se usan las API's SetCapture y ReleaseCapture, hay un momento en el que el Evento_Mouse_Movimiento salta cuando el mouse esta fuera del control. Ese momento es desde después de presionar el botón del mouse, hasta que soltamos el botón.
Ahora veamos la función Eventos_Mouse_BotonPresionado :
LRESULT ObjetoBoton::Evento_Mouse_BotonPresionado(const UINT Boton, const int cX, const int cY, const UINT Params) {POINT Pt = { cX, cY };RECT RectaControl;GetClientRect(_hWnd, &RectaControl);if (PtInRect(&RectaControl, Pt) == TRUE) {SetCapture(_hWnd);_Estado = ObjetoBoton_EstadoPresionado;_Presionado = true;}Repintar(true);return 0;}
Como en el Evento_Mouse_Movimiento, miramos si el mouse esta realmente dentro de nuestro botón, si es asi llamamos a la API SetCapture para tener capturado el mouse de forma que recibamos sus eventos incluso fuera del control. Esto se hace así porque tu puedes presionar encima del botón 1 y acabar soltando el mouse en el botón 2, si no tubieramos esto controlado el boton 2 se ejecutaría, y no me parece muy correcto, más bien parece que el usuario se ha arrepentido de presionar el boton y lo ha intentado evitar. Hay que remarcar que mientras SetCapture este activo, ninguna otra ventana recibirá eventos del mouse, así que hay que tener mucho cuidado.
Y para terminar con las tareas del mouse veamos la función Eventos_Mouse_BotonSoltado :
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;}
Lo primero que hacemos es llamar a la API ReleaseCapture sin miedo para liberar cualquier captura pendiente. Luego miramos si el mouse esta dentro del control para aplicar su estado correspondiente y para saber si hay que mandar un mensaje a su ventana padre informando de que el usuario ha hecho un click en el botón. Para averiguar el HWND de la ventana padre que contiene nuestro botón se utilizara la API GetParent, sabiendo el HWND podemos mandar el mensaje WM_BOTON_CLICK que se ha definido en esta misma clase. Esto nos lleva a tener que crear un nuevo evento en la clase ObjetoHWND para enlazar ese evento y tenerlo más a mano.
Ya hemos terminado con la parte de controlar los eventos de nuestro control, ahora nos queda hacer las funciones de pintado así que nos liaremos con el GDI de nuevo. Para empezar vamos a tratar los con buffers graficos, que en esencia son una porcion de memoria que podemos usar para pintar nuestros remiendos sin que se vea por pantalla. Para pintar cosas complejas lo mejor es siempre tener un buffer o mas para gráficos, una de las razones es que si empezamos a pintar directamente en el DC de la ventana podemos provocar efectos gráficos no deseados. Por ejemplo pongamos que quiero una ventana en fondo blanco con una redonda verde en medio, necesitare pintar el fondo entero de blanco y luego la redonda encima, al hacerlo directamente en una ventana veríamos como la redonda de vez en cuando parpadea, y eso sucede porque también nos esta mostrando el fondo blanco que pintamos primero. En cambio si hacemos ese trabajo en un bufer, y luego pintamos el buffer acabado en la ventana quedara perfectamente.
Para crear un buffer grafico hay que usar las siguientes API's : CreateCompatibleDC, CreateCompatibleBitmap y SelectObject. Con CreateCompatibleDC estamos reservando un espacio para nuestro buffer que será compatible con el DC especificado, CreateCompatibleBitmap crea el espacio en memoria necesario para guardar nuestro grafico, y SelectObject se utiliza entre otras cosas para asignar nuestro Bitmap compatible a nuestro DC compatible.
Una cosa muy importante cuando usamos SelectObject para asignar un objeto al DC, debemos guardar la dirección del objeto viejo, ya que a la hora de destruir el DC solo puede ser destruido si tiene seleccionados sus objetos originales. Es decir si le asigno un nuevo Bitmap al DC tenemos que almacenar la dirección del Bitmap original, y luego cuando no necesitemos mas el nuevo Bitmap habra que volver a asignar el Bitmap original con la API SelectObject. Para ver esto con mas detalle fíjate en el principio y el final del siguiente código :
void ObjetoBoton::Pintar(HDC hDC) {RECT RC;GetClientRect(_hWnd, &RC);// 1 - Creación de un buffer para el pintadoHDC Buffer = CreateCompatibleDC(hDC);HBITMAP Bmp = CreateCompatibleBitmap(hDC, RC.right, RC.bottom);HBITMAP Viejo = static_cast<HBITMAP>(SelectObject(Buffer, Bmp));HRGN Region = CreateRoundRectRgn(0, 0, RC.right, RC.bottom, REDONDEAMIENTO, REDONDEAMIENTO);HFONT VFuente = NULL;// 2 - Seleccionamos los colores según el estadoCOLORREF Degradado1;COLORREF Degradado2;COLORREF ColorBorde;switch (_Estado) {case ObjetoBoton_EstadoNormal :Degradado1 = _Color_DegradadoSuperior;Degradado2 = _Color_DegradadoInferior;SetTextColor(Buffer, _Color_TextoNormal);VFuente = static_cast<HFONT>(SelectObject(Buffer, _Fuente_Normal));if (GetFocus() == _hWnd) ColorBorde = _Color_BordeInternoResaltado;else ColorBorde = _Color_BordeInterno;break;case ObjetoBoton_EstadoResaltado :Degradado1 = _Color_DegradadoResaltado;Degradado2 = _Color_DegradadoInferior;SetTextColor(Buffer, _Color_TextoResaltado);VFuente = static_cast<HFONT>(SelectObject(Buffer, _Fuente_Resaltada));ColorBorde = _Color_BordeInternoResaltado;break;case ObjetoBoton_EstadoPresionado :Degradado1 = _Color_DegradadoInferior;Degradado2 = _Color_DegradadoResaltado;SetTextColor(Buffer, _Color_TextoPresionado);VFuente = static_cast<HFONT>(SelectObject(Buffer, _Fuente_Presionada));ColorBorde = _Color_BordeInternoResaltado;break;case ObjetoBoton_EstadoDesactivado :Degradado1 = _Color_DegradadoSuperior;Degradado2 = _Color_DegradadoInferior;SetTextColor(Buffer, _Color_TextoDesactivado);VFuente = static_cast<HFONT>(SelectObject(Buffer, _Fuente_Desactivada));ColorBorde = _Color_BordeInterno;break;}// 3 - Pintamos el degradado del fondoTRIVERTEX GCVertex[2];GRADIENT_RECT tGRect;GCVertex[0].Red = RGB_OBTENER_R(Degradado1);GCVertex[0].Green = RGB_OBTENER_G(Degradado1);GCVertex[0].Blue = RGB_OBTENER_B(Degradado1);GCVertex[0].x = 2;GCVertex[0].y = 2;GCVertex[1].Red = RGB_OBTENER_R(Degradado2);GCVertex[1].Green = RGB_OBTENER_G(Degradado2);GCVertex[1].Blue = RGB_OBTENER_B(Degradado2);GCVertex[1].x = RC.right -2;GCVertex[1].y = RC.bottom -2;tGRect.UpperLeft = 0;tGRect.LowerRight = 1;GradientFill(Buffer, GCVertex, 2, &tGRect, 1, GRADIENT_FILL_RECT_V);// 4 - Pintamos el bordeHBRUSH BordeInterno = CreateSolidBrush(ColorBorde);HBRUSH BordeExterno = CreateSolidBrush(_Color_BordeExterno);FrameRgn(Buffer, Region, BordeInterno, 2, 2);FrameRgn(Buffer, Region, BordeExterno, 1, 1);DeleteObject(BordeInterno);DeleteObject(BordeExterno);// 5 - Pintamos el textoSetBkMode(Buffer, TRANSPARENT);RECT RC2 = RC;// Si el estado es presionado sumamos 1 a todos los lados del RECT para simular el efecto de presión.if (_Estado == ObjetoBoton_EstadoPresionado) {RC2.left ++; RC2.top ++; RC2.right ++; RC2.bottom ++;}DrawText(Buffer, _Texto, wcslen(_Texto), &RC2, DT_CENTER | DT_VCENTER | DT_SINGLELINE);// 6 - Pintamos el Buffer en el DC del botónBitBlt(hDC, 0, 0, RC.right, RC.bottom, Buffer, 0, 0, SRCCOPY);// 7 - Selecciono los objetos originales del BufferSelectObject(Buffer, VFuente);SelectObject(Buffer, Viejo);// 8 - Elimino objetos gdi de la memoriaDeleteObject(Region);DeleteObject(Bmp);DeleteDC(Buffer);}
- Creamos el Buffer para pintar el botón (explicado anteriormente).
- Seleccionamos los colores y fuentes necesarios para el botón según su estado. Para ello utilizamos la API SetTextColor para poner el color del texto, y la API SelectObject para seleccionar la fuente (recordad que luego vamos a tener que volver a seleccionar la fuente original antes de borrar el buffer)
- Pintamos el degradado de fondo. Para ello utilizamos la API GradientFill rellenando correctamente las estructuras TRIVERTEX y GRADIENT_RECT. Hay que remarcar que se han utilizado 3 macros para extraer los canales RGB de los COLORREF especificados.
- Pintamos el borde del control, que tiene 2 colores. Para ello creamos unas brochas con la API CreateSolidBrush y luego pintamos con la API FrameRgn la región del control con la brocha especificada. Esta operación la hacemos con el borde interno primero y lo pintamos de dos pixeles de ancho/alto. Luego pintamos encima el borde externo con solo un pixel de ancho/alto.
- Pintamos el texto del botón. Para ello primero vamos a poner el fondo del texto transparente con la API SetBkMode especificando TRANSPARENT. Por último pintamos el texto utilizando la API DrawText.
- Pintamos nuestro buffer en el DC final. Para ello utilizamos la API BitBlt especificando las coordenadas y los DC's que intervienen en la operación.
- Des-seleccionamos nuestros objetos GDI y volvemos a seleccionar los objetos originales para poder borrar nuestro DC que contiene el buffer.
- Por último eliminamos los objetos GDI utilizados para el buffer.
Llegados a este punto ya hemos visto lo más importante a la hora de crear nuestro botón. Si deseáis ver el botón funcionando podéis descargar los ejemplos de este tutorial y echar un vistazo al ejemplo numero 5 :
En la siguiente parte del tutorial veremos como crear nuestro control marcador que mostrara las operaciones y el resultado de nuestra calculadora : 1.6 - Creación de nuestro objeto Marcador.
Descargar tutorial WinAPI completo | Calculadora compilada |