Capítulo 41 Control Button avanzado

En nuestra primera aproximación a los controles button los dividimos en cuatro categorías: botones de pulsar (push buttons), botones de grupo, check boxes y radio buttons.

En este capítulo veremos algunos detalles que no vimos en esos capítulos, pero esta vez agrupando todas estas categorías en una sola, ya que todas ellas son en realidad botones de diferentes estilos.

Insertar botones durante la ejecución

Al igual que vimos con los controles edit y list box, también es posible insertar controles button durante la ejecución. En el caso del control button tendremos que insertar una ventana de la clase "BUTTON". Para insertar el control también usaremos las funciones CreateWindow y CreateWindowEx.

   HWND hctrl;
...
   hctrl = CreateWindowEx(
      0,
      "BUTTON",        /* Nombre de la clase */
      "Botón 1",       /* Texto del título */
      BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, /* Estilo */
      9, 19,           /* Posición */
      95, 24,          /* Tamaño */
      hwnd,            /* Ventana padre */
      (HMENU)ID_BOTON, /* Identificador del control */
      hInstance,       /* Instancia */
      NULL);           /* Sin datos de creación de ventana */
   SetFocus(hctrl);

A diferencia de los controles que hemos visto antes, en el caso de los botones el texto del título sí tiene uso, ya que será ese texto el que aparezca en el botón, radio button, check box o group box.

El identificador del control se suministra a través del parámetro hMenu, por lo que será necesario hacer un casting de ese valor a HMENU.

Ahora será nuestro procedimiento de ventana, si el control fue insertado en una ventana, o el procedimiento de diálogo, si se trata de un diálogo, el encargado de procesar los mensajes procedentes del control.

Cambiar fuente

También es posible modificar la fuente de un control button enviando un mensaje WM_SETFONT. El lugar apropiado es, por supuesto, al procesar el mensaje WM_INITDIALOG al iniciiar un cuadro de diálogo, o al procesar el mensaje WM_CREATE, al iniciar una ventana.

En el parámetro wParam pasamos un manipulador de fuente, y usaremos la macro MAKELPARAM para crear un valor LPARAM, en el que especificaremos la opción de repintar el control, que se almacena en la palabra de menor peso de LPARAM.

Esto nos permite modificar la fuente durante la ejecución, reflejando los cambios en pantalla.

   static HFONT hfont;
...
   hfont = (HFONT)GetStockObject( DEFAULT_GUI_FONT );
   SendMessage(hctrl, WM_SETFONT, (WPARAM)hfont, MAKELPARAM(TRUE, 0));

Cambiar colores

Análogamente a lo que hemos visto con otros controles, también existe un mensaje que nos permite modificar el color de los controles de tipo botón.

Se trata del mensaje WM_CTLCOLORBTN, que se envía a la ventana propietaria del control cuando debe ser dibujado.

En el parámetro wParam recibiremos un manipulador de contexto de dispositivo del control, y en el parámetro lParam el manipulador ventana del control. Podemos cambiar el color de fondo y el del texto, y cuando se procese este mensaje, y deberemos retornar un manipulador de pincel, que se usará para pintar el fondo:

   static HBRUSH pincel;
...
      case WM_CREATE:
        pincel = CreateSolidBrush(RGB(0,255,0));
        ...
      case WM_CTLCOLORBTN:
        SetTextColor((HDC)wParam, RGB(0,0,255));
        SetBkColor((HDC)wParam, RGB(0,255,0));
        return (LRESULT)pincel;
      case WM_DESTROY:
        DeleteObject(pincel);
        ...

Lo que pasa es que, en la mayor parte de los controles botón, los colores están predefinidos por el sistema, y nuestros cambios al procesar este mensaje no influyen en el aspecto final de los controles.

La excepción son los controles botón del estilo owner-draw, sólo en este tipo de botones puede ser útil procesar este mensaje.

Modificar el bucle de mensajes

Al insertar controles botón en la ventana principal, en lugar de hacerlo en un cuadro de diálogo, hay determinadas funcionalidades, relacionadas con el teclado, que no funcionan de forma similar.

Por ejemplo, para hacer que funcione la tecla de tabulación de modo que cambie el foco entre los distintos controles o para que las teclas como el espacio o el ENTER activen los botones, y para que funcionen las teclas del cursor, hay que modificar el bucle de mensajes de modo que ciertos mensajes, aquellos propios de los cuadros de diálogo, se procesen de forma diferente.

Esto se hace añadiendo la función IsDialogMessage. Esta función averigua si un mensaje es de diálogo, y en ese caso lo procesa. Estos mensaje no deben ser procesados por el bucle habitual:

    /* Bucle de mensajes, se ejecuta hasta que haya error o GetMessage devuelva FALSE
       Modificado para procesar ciertas teclas de forma automática. */
    while(TRUE == GetMessage(&mensaje, NULL, 0, 0)) {
       if(!IsDialogMessage(hwnd, &mensaje) ) {
        /* Traducir mensajes de teclas virtuales a mensajes de caracteres */
        TranslateMessage(&mensaje);
        /* Enviar mensaje al procedimiento de ventana */
        DispatchMessage(&mensaje);
        }
    }

Botones con iconos o mapas de bits

Existen dos estilos para los botones que nos permiten usar un gráfico, un icono o un mapa de bits, en lugar de un texto.

El estilo BS_ICON permite asignar un icono a un botón.

El estilo BS_BITMAP permite asignar un mapa de bits a un botón.

Estas asignaciones son independientementes de otros estilos que definan el tipo, es decir, se puede asignar un icono o un mapa de bits a un botón pulsable, a un check box o a un radio button.

Además de indicar el estilo al crear el control, o en la definición del recurso, es necesario asignar la imagen al botón. Para eso se usa el mensaje BM_SETIMAGE. En el parámetro wParam se indica el tipo de imagen, IMAGE_ICON para un icono o IMAGE_BITMAP para un mapa de bits. En el mensaje lParam pasaremos el manipulador de la imagen, que será de tipo HICON para un icono y de tipo HBITMAP para un mapa de bits.

Ejemplo de botón con un icono:

   HWND hctrl;
   HICON hIcono;
...
   hctrl = CreateWindowEx(
      0,
      "BUTTON",        /* Nombre de la clase */
      "icono",         /* Texto del título */
      BS_ICON | BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, /* Estilo */
      9, 49,           /* Posición */
      95, 24,          /* Tamaño */
      hwnd,            /* Ventana padre */
      (HMENU)ID_BOTON2, /* Identificador del control */
      hInstance,       /* Instancia */
      NULL);           /* Sin datos de creación de ventana */
   hIcono = LoadIcon(hInstance, "Icono");
   SendMessage(hctrl, BM_SETIMAGE, (WPARAM)IMAGE_ICON, (LPARAM)hIcono);

Un ejemplo de botón con un mapa de bits:

   HWND hctrl;
   HBITMAP hBitmap;
...
   hctrl = CreateWindowEx(
      0,
      "BUTTON",        /* Nombre de la clase */
      "Bitmap",        /* Texto del título */
      BS_BITMAP | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, /* Estilo */
      9, 9,            /* Posición */
      87+4, 20+4,      /* Tamaño */
      hwnd,            /* Ventana padre */
      (HMENU)ID_BOTON1,/* Identificador del control */
      hInstance,       /* Instancia */
      NULL);           /* Sin datos de creación de ventana */
   hBitmap = LoadBitmap(hInstance,  "power");
   SendMessage(hctrl, BM_SETIMAGE, (WPARAM)IMAGE_BITMAP, (LPARAM)hBitmap);

De forma simétrica, podemos usar el mensaje BM_GETIMAGE para obtener un manipulador del icono o mapa de bits asociado a un control botón. Indicaremos en el parámetro wParam el tipo de imagen a recuperar:

   HICON hIcono;
...
   hIcono = (HICON)SendMessage(hctrl, BM_GETIMAGE, IMAGE_ICON, 0);

Otros estilos para botones

Generalmente, nuestros botones tendrán el estilo BS_TEXT, que es el estilo por defecto y el que se usa si no se indican los estilos BS_ICON, BS_BITMAP o BS_OWNERDRAW.

El estilo BS_TEXT indica que se trata de un botón normal, con un texto que indica (o debería indicar) la acción del botón.

El estilo BS_MULTILINE es una variación de BS_TEXT, que permite fragmentar el texto en varias líneas diferentes, que se amoldan al espacio disponible del botón.

Alineación de contenidos

Hasta ahora sólo hemos mencionado el estilo BS_CENTER, que nos permitía situal el texto, icono o mapa de bits de un botón centrado horizontalmente.

Existen otros estilos que nos permiten situar el contenido en otros lugares. Tres de esos estilos permiten definir la alineación del texto en el sentido horizontal: BS_LEFT a la izquierda, BS_CENTER en el centro y BS_RIGHT a la derecha.

Otros tres estilos permiten definir la alineación en sentido vertical: BS_TOP en la parte superior, BS_VCENTER centrado verticalmente y BS_BOTTOM en la parte inferior.

Estos estilos se pueden combinar, eligiendo uno de cada grupo, de modo que podemos elegir nueve posiciones diferentes para situar el texto:

BS_LEFT | BS_TOP BS_CENTER | BS_TOP BS_RIGHT | BS_TOP
BS_LEFT | BS_VCENTER BS_CENTER | BS_VCENTER BS_RIGHT | BS_VCENTER
BS_LEFT | BS_BOTTOM BS_CENTER | BS_BOTTOM BS_RIGHT | BS_BOTTOM

Check box y Radio buttons

Por último, existen otros dos estilos, que son equivalentes: BS_LEFTTEXT y BS_RIGHTBUTTON, que permiten situar el gráfico de botones con estilos check box o radio buttons a la derecha, en lugar de situarlo a la izquierda, que es la posición por defecto.

Ejemplo 68

Nombre Fichero Fecha Tamaño Contador Descarga
Ejemplo 68 win068.zip 2007-03-15 4582 bytes 31

Mensajes de notificación

La clase Button dispone de muchos mensajes de notificación, pero la mayor parte de ellos se mantienen sólo por compatibilidad con versiones de Windows de 16 bits, y no deben usarse en aplicaciones de 32 bits, por lo que no los veremos con detalle.

Nota:

Los mensajes de notificación obsoletos son: BN_DBLCLK, BN_DISABLE, BN_DOUBLECLICKED, BN_HILITE, BN_PAINT, BN_PUSHED, BN_UNHILITE y BN_UNPUSHED. En lugar de usar estos mensajes, las aplicaciones de 32 bits deben usar un control botón con un estilo owner-draw, y la estructura DRAWITEMSTRUCT.

Nos limitaremos a explicar, por lo tanto, sólo tres mensajes de notificación.

Como en todos los casos, los mensajes de notificación se envían a la ventana padre mediante un mensaje WM_COMMAND.

Selección

Cada vez que el usuario hace clic sobre un botón, se envía un mensaje de notificación BN_CLICKED a la ventana propietaria del botón.

Doble clic

Cuando el usuario hace un doble clic sobre un botón se envía un mensaje de notificación BN_DBLCLK. El botón debe tener el estilo BS_OWNERDRAW o BS_RADIOBUTTON.

Pérdida y recuperación de foco

Cuando un control pierde el foco del teclado, se envía un mensaje de notificación BN_KILLFOCUS a la ventana propietaria. Análogamente, cuando un control botón recupera el foco del teclado, se envía un mensaje BN_SETFOCUS a la ventana propietaria.

Inhibir mensajes de notificación

Si definimos un botón sin el estilo BS_NOTIFY, los mensajes BN_DISABLE, BN_PUSHED, BN_KILLFOCUS, BN_PAINT, BN_SETFOCUS y BN_UNPUSHED no se enviarán a la ventana padre del control.

Esto significa que deberemos ser cuidadosos cuando procesemos los mensajes WM_COMMAND procedentes de un botón con el estilo BS_NOTIFY, ya que no todos los mensajes WM_COMMAND que recivamos serán pulsaciones de botón, y por lo tanto no nos servirá el método usado en el capítulo 9, en el que no se verificaba el valor del parámetro wParam.

Por contra, un botón pulsable sin el estilo BS_NOTIFY sólo puede enviar mensajes de notificación BN_CLICKED, de modo que no tiene sentido verificar el valor de la palabra de mayor peso del parámetro wParam.

Tanto si se usa el estilo BS_NOTIFY, como si no, los mensajes BN_CLICKED y BN_DBLCLK siempre se envían.

Estilos de cada tipo de botón

Ya hemos mencionado que los botones pulsables, los check boxes, los radio buttons y las cajas de grupos no son más que controles botón con diferentes estilos. Veamos ahora qué estilos puede tener cada uno de los controles:

Botones pulsables

Botón pulsable

Botón pulsable

Estos botones se pueden definir usando los estilos BS_PUSHBUTTOM y BS_DEFPUSHBUTTON. En el segundo caso, el botón será el botón por defecto, y se activará cuando el usuario pulse la tecla de ENTER.

Check boxes

Check boxes

Check boxes

Los estilos BS_CHECKBOX, BS_AUTOCHECKBOX, BS_3STATE y BS_AUTO3STATE defiene botónes check box de dos estados (los dos primeros) o de tres estados (los dos últimos). Los estilos AUTO, además, se procesarán automáticamente por el sistema.

Añadir que el estilo BS_PUSHLIKE permite definir un check box con la misma apariencia que un botón pulsable.

Radio buttons

Radio buttons

Para definir este tipo de botones disponemos de los estilos BS_RADIOBUTTON y BS_AUTORADIOBUTTON. El segundo define radio buttons automáticos.

En este caso, el estilo BS_PUSHLIKE también permite definir un radio buttons con la misma apariencia que un botón pulsable.

Cajas de grupo

Botón de grupo

Botón de grupo

El estilo BS_GROUPBOX permite definir cajas de grupo.

Todos estos controles, menos los group boxes, envián mensajes WM_COMMAND cuando son pulsados, y mensajes de notificación BN_CLICKED.

Botones owner-draw

Existe un quinto tipo de botón, el owner-draw, que se define con el estilo BS_OWNERDRAW. El comportamiento de estos botones depende de nosotros, ya que estaremos obligados a diseñarlos y pintarlos dependiendo de su estado.

Estados de un botón

Un botón puede tener tres niveles de estado diferentes.

Por una parte, puede tener o no el foco del teclado. Esto se indica, generalmente, mediante un rectángulo de líneas punteadas alrededor del texto.

Puede estar o no resaltado. El resaltado se produce cuando el usuario coloca el ratón sobre el botón y pulsa, y mantiene, el botón izquiedo del ratón. O también si el control tiene le foco y se pulsa la tecla [Espacio].

Por último, en el caso de check boxes y radio buttons, el botón puede o no estar marcado (checked).

Selección de un botón

En los botones que usaremos normalmente, tanto el estado del control como su aspecto gráfico se actualizarán de forma automática por el sistema. Esto se aplica a botones pulsables, que cambiarán de aspecto cuando se pulsen y se suelten y también a los check boxes y radio buttons automáticos, que mostrarán el estado de marcado de forma automática.

En los radio buttons y check boxes no automáticos, el estado de marcado dependerá de nuestra aplicación. Esto nos permite crear condiciones más elaboradas para el cambio de estado que las simples pulsaciones.

Por último, en los botones owner draw, tanto el cambio de estado como la actualización de la representación dependerán de nuestra aplicación. Esto nos dará mucho más trabajo, pero a cambio proporciona una total libertad en cuanto al comportamiento y aspecto de los botones.

Cambios de estado

Para determinar el estado de un botón se usan los mensajes BM_GETCHECK y BM_GETSTATE.

El mensaje BM_GETCHECK puede devolver los valores BST_CHECKED, BST_INDETERMINATE o BST_UNCHECKED, de modo que podemos determinar el estado de marcado de un botón check box o radio button. El valor BST_INDETERMINATE sólo es válido para botones con el estilo BS_3STATE o BS_AUTO3STATE.

El mensaje BM_GETSTATE es algo más completo, ya que puede devolver los valores BST_FOCUS y BST_PUSHED, además de los tres que devuelve BM_GETCHECK.

Podemos usar la máscara 0x0003, o mejor aún, la suma de los valores BST_CHECKED y BST_INDETERMINATE, para extraer sólo los valores de la marca de chequeo. Aunque esto es redundante, ya que podemos usar en su lugar el mensaje BM_GETCHECK.

El valor BST_FOCUS indica si el botón tiene el foco del teclado, y el valor BST_PUSHED si el botón está pulsado.

   if(SendDlgItemMessage(hwnd, ID_BOTON2, BM_GETSTATE, 0, 0) == BST_CHECKED)
      MessageBox(hwnd, "Botón 2 marcado", "Marca", MB_OK);
   else
      MessageBox(hwnd, "Botón 2 no marcado", "Marca", MB_OK);

De forma simétrica, podemos usar los mensaje BM_SETCHECK o BM_SETSTATE para modificar el estado de un botón.

BM_SETCHECK nos permite modificar el estado de marcado de un botón del tipo Check Box o Radio Button, usando uno de los valores BST_CHECKED, BST_INDETERMINATE o BST_UNCHECKED, en el parámetro wParam.

El mensaje BM_SETSTATE permite modificar el estado del resaltado de un botón. Para ello se usa el parámetro wParam. Un valor TRUE resalta el botón, y un valor FALSE elimina el resalte. Estos estados no afectan más que a la apariencia del botón. El estado resaltado corresponde a cuando el usuario pulsa y maniene pulsado un botón.

   SendDlgItemMessage(hwnd, ID_BOTON2, BM_SETSTATE, TRUE, 0);
   SendDlgItemMessage(hwnd, ID_BOTON2, BM_SETCHECK, BST_UNCHECKED, 0);

Funciones para controles botón

Como en el resto de los controles, los mensajes se pueden envíar mediante dos funciones distintas.

Por una parte, el la función SendMessage nos permite enviar un mensaje a una ventana o control, disponiendo de su manipulador de ventana:

   SendMessage(hctrl, WM_SETFONT, (WPARAM)hfont, MAKELPARAM(TRUE, 0));

Si no disponemos de tal manipulador existen otras opciones. Por ejemplo, podemos obtener un manipulador de ventana o control, a partir del identificador, mediante la función GetDlgItem. Combinando estas dos funciones, podemos enviar un mensaje a un control, aunque no dispongamos de un manipulador de ventana:

   SendMessage(GetDlgItem(hwnd, ID_BOTON1), WM_SETFONT,
      (WPARAM)hfont, MAKELPARAM(TRUE, 0));

Sin embargo, existe una función que combina las acciones de estas dos, se trata de SendDlgItemMessage:

   SendDlgItemMessage(hwnd, ID_BOTON1, WM_SETFONT,
      (WPARAM)hfont, MAKELPARAM(TRUE, 0));

A pesar de que aparezca la abreviatura "Dlg" como parte de estas dos últimas funciones, ambas funcionan tanto en cuadros de diálogo como en ventanas normales. Es decir, podemos usar estas dos funciones para enviar mensajes a controles insertados en ventanas corrientes.

Funciones propias de controles botón

Otras funciones, que ya vimos previamente en los capítulos 14 y 15, son específicas para controles botón de los tipos check box y radio button.

Aunque estas funciones tienen sus equivalentes en mensajes, a menudo nos serán útiles en nuestros programas.

La función CheckDlgButton, nos permite cambiar el estado de marcado de un control botón del tipo check box o radio button, en realidad equivale a enviar un mensaje BM_SETCHECK.

La función CheckRadioButton es más útil, ya que trabaja con grupos de radio buttons. Dentro de un grupo de radio buttons sólo uno de ellos puede estar marcado en un momento dado. Usar esta función nos permite marcar uno de los controles, y eliminar la marca del que la tenía previamente, en una única operación.

La función IsDlgButtonChecked nos permite conocer el estado de marcado de un control botón. Es equivalente a enviar un mensaje BM_GETCHECK al control.

Modificar el estilo de un botón

Es posible modificar el estilo de un botón durante la ejecución. Para ello se usa el mensaje BM_SETSTYLE, indicando en el parámetro wParam el nuevo estilo del botón y en lParam si se debe o no redibujar el control, un valor TRUE en la palabra de menor peso indica que se debe redibujar, un valor FALSE, que no:

   SendDlgItemMessage(hwnd, ID_BOTON, BM_SETSTYLE,
      BS_PUSHBUTTON | BS_LEFTTEXT | WS_CHILD | WS_VISIBLE | WS_TABSTOP,
      MAKELPARAM(TRUE,0));

Botones owner-draw

Ya lo hemos mencionado antes: los controles botón también disponen de un estilo owner-draw.

Un control botón con el estilo owner-draw, BS_OWNERDRAW no tiene un comportamiento definido, ni un aspecto gráfico concreto. Será el procedimiento de ventana o diálogo de la ventana propietaria del control el encargado de actualizar el aspecto en pantalla y también de definir las respuestas a cada evento de teclado o ratón.

De modo que un botón owner-draw puede ser un botón pulsable, un radio button, un check box o cualquier otra cosa que inventemos.

Lo primero es crear el control con el estilo BS_OWNERDRAW:

   HWND hctrl;
...
   hctrl = CreateWindowEx(
      0,
      "BUTTON",        /* Nombre de la clase */
      "Botón 2",       /* Texto del título */
      BS_OWNERDRAW | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, /* Estilo */
      9, 49,           /* Posición */
      95, 24,          /* Tamaño */
      hwnd,            /* Ventana padre */
      (HMENU)ID_BOTON, /* Identificador del control */
      hInstance,       /* Instancia */
      NULL);           /* Sin datos de creación de ventana */

Cuando existen botones con el estilo owner-draw, la ventana padre del control recibirá un mensaje WM_DRAWITEM cada vez que el control deba ser redibujado. Este mensaje se recibe para todos los controles owner-draw existentes, de modo que deberemos distinguir a qué control en concreto se refiere el mensaje. Esta es la parte sencilla, ya que en el parámetro wParam de este mensaje recibiremos el identificador del control.

Por otra parte, en el parámetro lParam, recibiremos un puntero a una estructura DRAWITEMSTRUCT, que nos proporciona información sobre todos los detalles necesarios para decidir el modo en que debemos dibujar el control.

Por una parte, el campo CtlType contendrá el valor ODT_BUTTON, indicando que el control es un botón. El campo CtlID contiene el identificador del control. El campo itemID no tiene ninguna información en el caso de un control botón. El campo itemAction contiene un valor que especifica el tipo de acción de dibujo requerido. Puede ser ODA_DRAWENTIRE, si se necesita actualizar el control completo, ODA_FOCUS si el único cambio es la pérdida o recuperación del foco o ODA_SELECT, si ha cambiado el estado de selección.

El campo itemState especifica el estado del control. En el caso de los botones, el valor de este campo puede ser una combinación de: ODS_DISABLED, si el control está deshabilitado, ODS_FOCUS, si el control tiene el foco o ODS_SELECTED, si el control está seleccionado.

El campo hwndItem contiene el manipulador de ventana, hDC contiene el manipulador de contexto de dispositivo del control. El campo rcItem contiene el rectángulo que define los límites de la zona a dibujar. E itemData no contiene nada válido en el caso de los controles botón.

Este ejemplo visualiza un control botón con forma elíptica, y cambia el color del fondo a naranja cuando está pulsado. Si se deshabilita, se muestra una cruz:

   LPDRAWITEMSTRUCT lpdis;
   HBRUSH pincel, pincel2;
...
   case WM_DRAWITEM:
      lpdis = (LPDRAWITEMSTRUCT)lParam;
      GetClientRect(lpdis->hwndItem, &re);
      SetBkMode(lpdis->hDC, TRANSPARENT);
      pincel = CreateSolidBrush(GetSysColor(COLOR_BACKGROUND));
      pincel2 = CreateSolidBrush(RGB(240,120,0));
      FillRect(lpdis->hDC, &re, pincel);
      if(wParam == ID_BOTON2) {
         if(lpdis->itemState & ODS_SELECTED) {
            SelectObject(lpdis->hDC, (HBRUSH)GetStockObject(GRAY_BRUSH));
            SelectObject(lpdis->hDC, (HPEN)GetStockObject(WHITE_PEN));
            Ellipse(lpdis->hDC, re.left, re.top, re.right, re.bottom);
            TextOut(lpdis->hDC, re.left+14, re.top+4, "Botón 2", 7);
         } else if(lpdis->itemState & ODS_DISABLED) {
            MoveToEx(lpdis->hDC, 0, 0, NULL);
            LineTo(lpdis->hDC, re.right, re.bottom);
            MoveToEx(lpdis->hDC, 0, re.bottom, NULL);
            LineTo(lpdis->hDC, re.right, 0);
         } else {
            SelectObject(lpdis->hDC, pincel2);
            SelectObject(lpdis->hDC, (HPEN)GetStockObject(WHITE_PEN));
            Ellipse(lpdis->hDC, re.left, re.top, re.right, re.bottom);
            TextOut(lpdis->hDC, re.left+12, re.top+2, "Botón 2", 7);
         }
      }
      DeleteObject(pincel);
      DeleteObject(pincel2);
      return 0;

Ejemplo 69

Nombre Fichero Fecha Tamaño Contador Descarga
Ejemplo 69 win069.zip 2007-03-15 9132 bytes 37