NovaMonoFix
Errores PHP
X
Usuario
Password
0 FPS

Tutorial WINAPI C++ 1.5 (Creación de nuestro ObjetoBoton)

11 de Abril del 2010 por Josep Antoni Bover, 0 visitas, 0 comentarios, 0 votos
Categorías : Windows, Programación, C y C++.
La web está en segundo plano, animación en pausa.
Cargando animación...
Tutorial WINAPI C++ 1.5 (Creación de nuestro ObjetoBoton)

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.

Archivo : ObjetoControl.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Clase que hereda ObjetoHWND y se especializa en controles
class ObjetoControl : public PlantillaEventos<LRESULT, 0> {
public : //////////////////// Miembros publicos
// -Constructor
ObjetoControl(void);
// -Destructor
~ObjetoControl(void);
// Función para crear el control
HWND 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 inicial
static 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.

Archivo : PlantillaEventos.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Mouse move y leave
case 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.

Archivo : ObjetoControl.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 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.

Archivo : ObjetoControl.h
1
2
3
4
5
6
7
8
9
10
11
12
13
// 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 :

Archivo : ObjetoBoton.h
1
2
3
4
5
6
7
// Estados posibles del ObjetoBoton
enum 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 :

Archivo : ObjetoBoton.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
// Clase que hereda ObjetoControl para hacer botones
class ObjetoBoton : public ObjetoControl {
public : ////////////////////// Miembros publicos
// -Constructor
ObjetoBoton(void);
// -Destructor
~ObjetoBoton(void);
// -Función para crear el boton
HWND 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ón
void Texto(const TCHAR *nTexto, const bool nRepintar = true);
// -Función que retorna el texto del botón
const TCHAR *Texto(void);
// -Función que pinta todo el botón
void Pintar(HDC hDC);
// -Función que repinta todo el botón si es necesario
void Repintar(const bool Forzar = false);
// -Función para activar / desactivar el botón
BOOL Activado(const bool nActivar);
////////////////////////////// Eventos re-emplazados
// -Función enlazada al mensaje WM_ERASEBKGND
virtual LRESULT Evento_BorrarFondo(HDC hDC);
// -Función enlazada al mensaje WM_PAINT
virtual LRESULT Evento_Pintar(HDC hDC, PAINTSTRUCT &PS);
// -Función enlazada al mensaje WM_MOUSEMOVE
virtual LRESULT Evento_Mouse_Movimiento(const int cX, const int cY, const UINT Params);
// -Función enlazada al mensaje WM_MOUSELEAVE
virtual LRESULT Evento_Mouse_Saliendo(void);
// -Función enlazada a todos los mensajes WM_?BUTTONDOWN
virtual LRESULT Evento_Mouse_BotonPresionado(const UINT Boton, const int cX, const int cY, const UINT Params);
// -Función enlazada a todos los mensajes WM_?BUTTONUP
virtual LRESULT Evento_Mouse_BotonSoltado(const UINT Boton, const int cX, const int cY, const UINT Params);
// -Función enlazada al mensaje WM_KEYDOWN
virtual LRESULT Evento_Teclado_TeclaPresionada(const UINT Caracter, const UINT Repeticion, const UINT Params);
// -Función enlazada al mensaje WM_KEYUP
virtual 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_TextoNormal
AGREGAR_COLOR(Color_TextoNormal);
// -Macro que crea funciones para asignar y obtener el COLORREF _Color_TextoResaltado
AGREGAR_COLOR(Color_TextoResaltado);
// -Macro que crea funciones para asignar y obtener el COLORREF _Color_TextoPresionado
AGREGAR_COLOR(Color_TextoPresionado);
// -Macro que crea funciones para asignar y obtener el COLORREF _Color_TextoDesactivado
AGREGAR_COLOR(Color_TextoDesactivado);
// -Macro que crea funciones para asignar y obtener el COLORREF _Color_DegradadoSuperior
AGREGAR_COLOR(Color_DegradadoSuperior);
// -Macro que crea funciones para asignar y obtener el COLORREF _Color_DegradadoInferior
AGREGAR_COLOR(Color_DegradadoInferior);
// -Macro que crea funciones para asignar y obtener el COLORREF _Color_DegradadoResaltado
AGREGAR_COLOR(Color_DegradadoResaltado);
// -Macro que crea funciones para asignar y obtener el COLORREF _Color_BordeExterno
AGREGAR_COLOR(Color_BordeExterno);
// -Macro que crea funciones para asignar y obtener el COLORREF _Color_BordeInterno
AGREGAR_COLOR(Color_BordeInterno);
// -Macro que crea funciones para asignar y obtener el COLORREF _Color_BordeInternoResaltado
AGREGAR_COLOR(Color_BordeInternoResaltado);
// -Macro que crea la función Fuente_Desactivada, y la variable HFONT _Fuente_Normal
AGREGAR_FUENTE(Fuente_Normal);
// -Macro que crea la función Fuente_Desactivada, y la variable HFONT _Fuente_Resaltada
AGREGAR_FUENTE(Fuente_Resaltada);
// -Macro que crea la función Fuente_Desactivada, y la variable HFONT _Fuente_Presionada
AGREGAR_FUENTE(Fuente_Presionada);
// -Macro que crea la función Fuente_Desactivada, y la variable HFONT _Fuente_Desactivada
AGREGAR_FUENTE(Fuente_Desactivada);
protected : /////////////////// Miembros privados
// -Texto del botón
TCHAR *_Texto;
// -Estado actual del botón
ObjetoBoton_Estados _Estado;
// -Ultimo estado del botón
ObjetoBoton_Estados _UEstado;
// -Valor para saber si el boton esta presionado
bool _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 :

Archivo : ObjetoBoton.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// Factor de redondeamiento que se usara en el botón
#define REDONDEAMIENTO 2
// Función para crear el botón
HWND 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ón
CrearControl(hWndParent, TEXT("ObjetoBoton"), WS_CHILD, NULL, cX, cY, cAncho, cAlto, nID, NULL);
// Asigno una region redondeada al botón
HRGN Region = CreateRoundRectRgn(0, 0, cAncho, cAlto, REDONDEAMIENTO, REDONDEAMIENTO);
SetWindowRgn(_hWnd, Region, FALSE);
// Hago visible el botón
Visible(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 :

Archivo : ObjetoBoton.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 :

Archivo : ObjetoBoton.cpp
1
2
3
4
5
6
7
8
9
10
11
12
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 :

Archivo : ObjetoBoton.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 :

Archivo : ObjetoBoton.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
void ObjetoBoton::Pintar(HDC hDC) {
RECT RC;
GetClientRect(_hWnd, &RC);
// 1 - Creación de un buffer para el pintado
HDC 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 estado
COLORREF 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 fondo
TRIVERTEX 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 borde
HBRUSH 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 texto
SetBkMode(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ón
BitBlt(hDC, 0, 0, RC.right, RC.bottom, Buffer, 0, 0, SRCCOPY);
// 7 - Selecciono los objetos originales del Buffer
SelectObject(Buffer, VFuente);
SelectObject(Buffer, Viejo);
// 8 - Elimino objetos gdi de la memoria
DeleteObject(Region);
DeleteObject(Bmp);
DeleteDC(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