Todo sobre los botones.  PARTE 1

 

Por: Demian Panello.

 

He recibido muchos mensajes preguntando acerca de los botones de cómo se hace para deshabilitarlo y ocultarlo, cómo hacer para colocarle un tooltip, cómo saber cuando el puntero del mouse está sobre él, (lo equivalente al evento MouseMove de Visual Basic), cómo cambiarle el color y otras cuestiones a veces no muy difíciles y otras más trabajosas. Entonces decidí escribir una serie de artículos sobre los botones y aclarar las preguntas.

En esta primera parte se verá.

 

- Habilitar y deshabilitar.

- Mostrar y ocultar.

- Uso de Option Buttons.

- Uso de Check Buttons.

- Colocar ToolTips.

 

 Ventanas, ventanas, ventanas:

 

Muchas veces en una aplicación resulta necesario ocultar un botón o desactivarlo, durante la ejecución del programa.

Para poder acceder a las funciones de un botón, hay que tener en cuenta que, como todo en Windows, este objeto es una ventana, cuyo estilo representa lo que vemos como un botón. Como se trata de una ventana, tiene que tener un manejador, (handle), que vendría a ser lo que lo identifica de otros objetos, (otras ventanas, botones, cuadros de edición, listas, etc), en el sistema.

Muchas funciones de Windows, (encapsuladas en objetos de MFC), necesitan como parámetro un handle de ventana sobre la cual actuarán, por lo tanto si deseamos alterar alguna característica de un botón deberemos obtener el handle de ventana. Esto se logra con la función GetDlgItem(IDOBJETO) que devuelve un puntero a la clase CWnd, (lo que en las funciones SDK de Windows equivale a HWND) y recibe como parámetro el ID del objeto, (en nuestro caso un botón).

Teniendo un objeto CWnd lo que resta es acceder a sus funciones por medio del operador ->.

 

CWnd* pBoton = GetDlgItem(IDC_BOTON); (1)

pBoton-> EnableWindow(FALSE);

 

Aquí se obtiene el handle de ventana de un botón y luego se lo deshabilita.

Con un puntero CWnd no es posible acceder a las funciones exclusivas de los botones como GetCheck() ó SetCheck(), (de botones como Option Buttons y Check Buttons), así que a la sentencia (1) deberíamos cambiarla por:

 

CButton* pBoton = (CButton*) GetDlgItem(IDC_BOTON);

pBoton->SetCheck(1);

 

El cast (CButton*) es necesario ya que GetDlgItem() devuelve un puntero a CWnd y no es lo que queremos.

Para ocultar o mostrar un botón se escribe:

 

CWnd* pBoton = GetDlgItem(IDC_BOTON);

if (pBoton->IsWindowVisible())

    pBoton->ShowWindow(SW_HIDE);

else

    pBoton->ShowWindow(SW_SHOW);

Se obtiene un handle de ventana y se invoca a la función IsWindowVisible() que devuelve distinto de 0 en caso que sea visible, entonces con ShowWindow(SW_HIDE) se lo oculta y con ShowWindow(SW_SHOW) se lo muestra.

 

Botones, botones, botones.

 

La clase CButton encapsula las características de los diversos botones de Windows, ¿diversos?, sí, pues dentro de la categoría "botones" tenemos los Push Buttons, Option Buttons, (o Radio Buttons) y Check Buttons. Todos son botones.

 

Push Buttons:

 

De los Push Buttons no hay mucho que decir, ya que es uno de los controles más comunes y no encierra ningún misterio su funcionamiento. Simplemente que el método OnBoton(), que asigna MFC, envía el mensaje WM_COMMAND de manera tal que lo que está escrito en ese método se ejecuta cuando se pulsa el botón, (aunque no haga falta saberlo para utilizar un Push Button en una aplicación MFC, es conveniente aclarar que en realidad es el mensaje WM_COMMAND, que el programador no ve en ningún momento, el que llega a la cola de mensajes cuando se pulsa un botón).

 

Cómo se debe usar un push button:

 

Por lo general no hace falta asociarle una variable de tipo CButton a un botón, (a menos que desee colocarle un tooltip; vea más abajo).

Sólo debe escribir el código del método OnMiBoton, (por ejemplo), para que actúe al pulsarlo.

Si necesita saber si el puntero del mouse está sobre el botón, se usa una clase derivada de CButton, (esto se llama subclassing), y se le agrega el mensaje WM_MOUSEMOVE, pero esto lo veremos en la siguiente parte.

 

void CBotonesDlg::OnCentro()

{

    AfxMessageBox("Se ha pulsado el botón Aceptar");

}

 

Check Buttons:

 

Un check button es ese control que en su representación gráfica por defecto, (y la más típica), es una "cajita" a la cual se la puede marcar, (un tilde). 

Se le asocia un variable de tipo BOOL que cuando es TRUE lo marca y FALSE le quita la marca. Como se trata de una variable asociada al control siempre hay que usar la función UpdateData(FALSE) para que los cambios en la variable se reflejen en el control y UpdateData(TRUE) para que los cambios en el control se reflejen en la variable. Se puede tener varios check box y cada uno de ellos es independiente con respecto a los otros, de manera tal que puede haber más de un check box marcado.

Si no se le asocia una variable a un check box, entonces al momento de conocer el estado del mismo, (marcado o no), es necesario obtener un puntero a CButton para acceder a las funciones GetCheck() y SetCheck(), como indiqué anteriormente..

 

Option Buttons, (Radio Buttons):

 

Los options buttons permiten indicar selecciones únicas, esto es, nunca va a encontrar un option button solo, sino que lo verá agrupado con otros, (por lo menos 2), de manera tal que siempre habrá sólo uno de ellos marcados. Supongamos que tenemos la siguiente disposición de option buttons:

 

 

Lo importante es el primer option button, éste tiene que tener marcada la propiedad Group y solamente a él hay que asociarle una variable de tipo int.

Esta variable de tipo entera toma el valor 0 si está seleccionado el primero del grupo, 1 el segundo, 2 el tercero y así sucesivamente.

Entonces si el ID de este primer option button es IDC_PRIMERO y se le asignó, (con el Class Wizard por ejemplo),  la variable entera m_iOpcion, cuando desee obtener el texto del option button seleccionado debe escribir:

UpdateData(TRUE);
CWnd* oSel = GetDlgItem(IDC_PRIMERO + m_iOpcion);

CString texto;

oSel->GetWindowText(texto);

La línea marcada en amarillo es la clave, pues obtengo un handle de ventana del option button seleccionado, ya que m_iOpcion tiene un número que indica cual es el marcado, (se actualizó en la línea anterior con el llamado a UpdateData(TRUE)), sumando ese valor al ID del primero se obtiene un desplazamiento que corresponde al ID del marcado, (que no importa si es IDC_RADIO2 o IDC_TERCERO, etc). Por eso es muy importante, al momento de diseñar el diálogo, colocar los option button seguidos, así los ID, (que en realidad son números), son también seguidos.

Con el handle de ventana puedo obtener el texto del option button con una llamada a GetWindowText().

 

Aplicación de ejemplo:

 

Cree un proyecto MFC basado en diálogo y diseñe la disposición de controles más o menos como muestra la siguiente figura:

 

 

Las propiedades son, (sin contar el botón Aceptar que está por defecto):

 

Objeto

ID

Propiedades

Variable.

Push button

IDC_MOSTRAR_OCULTAR

por defecto.

CButton m_btnMostrar

Push button

IDC_ACTIVAR_DESACTIVAR

por defecto.

CButton m_btnActivar

Push button

IDC_IZQUIERDA

client edge/ modal frame

CButton m_btnIzquierda

Push button

IDC_CENTRO

client edge/ modal frame

CButton m_btnCentro

Push button

IDC_DERECHA

client edge/ modal frame

CButton m_btnDerecha

Group Box

IDC_STATIC

por defecto

ninguna

Radio Button

IDC_PRIMERO

group

m_iOpcion

Radio Button

por defecto

por defecto

ninguna

Radio Button

por defecto

por defecto

ninguna

Radio Button

por defecto

por defecto

ninguna

 

(nuevamente destacado en amarillo lo más importante al usar Radio buttons).

Le asociamos variables miembros a los Push buttons sólo porque es necesario para colocarle un tooltip, sino no hubiera hecho falta.

Al pulsar uno de los botones "Izquierda", "Centro" o "Derecha", mostraremos en la barra de título un texto indicando cual de estos botones ha sido pulsado y que Radio button está seleccionado.

 

Verifique en el constructor del diálogo que la variable m_iOpcion sea igual a 0 así por defecto tenemos el primero de los options marcado.

Luego agregue una función al diálogo de tipo void y de nombre CambiarTitulo(UINT nID) y en ella escriba:

 

void CBotonesDlg::CambiarTitulo(UINT nID)
{
//Obtengo un manejador del botón pulsado, (cuyo ID ha sido pasado como parámetro).
CWnd* bPulsado = GetDlgItem (nID);

UpdateData(TRUE);
CWnd* oSel = GetDlgItem(IDC_PRIMERO + m_iOpcion);
CString Titulo, tBoton, tOpcion;

//Tomo el texto del botón
bPulsado ->GetWindowText (tBoton);
oSel->GetWindowText(tOpcion);

Titulo.Format("Pulsó: %s. Marcado: %s", tBoton, tOpcion);

//Lo coloco en la barra del diálogo.
SetWindowText(Titulo);
}

Para los botones Izquierdo, Centro y Derecha escriba:

void CBotonesDlg::OnIzquierda() 
{
//Llamo a la función que cambia el título del diálogo de acuerdo al
//botón pulsado, pasandole el ID del mismo.

CambiarTitulo(IDC_IZQUIERDA);
}

void CBotonesDlg::OnCentro() 
{
//Llamo a la función que cambia el título del diálogo de acuerdo al
//botón pulsado, pasandole el ID del mismo.

CambiarTitulo(IDC_CENTRO);
}

void CBotonesDlg::OnDerecha() 
{
//Llamo a la función que cambia el título del diálogo de acuerdo al
//botón pulsado, pasandole el ID del mismo.

CambiarTitulo(IDC_DERECHA); 
}

Para los botones Ocultar y Desactivar escriba:

void CBotonesDlg::OnMostrarOcultar() 
{
CWnd* b;
BOOL Visible;

//******* Botón Izquierdo *******

b=GetDlgItem(IDC_IZQUIERDA);   //Obtengo el manejador

Visible = b->IsWindowVisible ();

if (Visible)
    b->ShowWindow (SW_HIDE);    //SW_SHOW Muestra
else
    b->ShowWindow (SW_SHOW);

//****** Botón del centro ********

b=GetDlgItem(IDC_CENTRO);  //Obtengo el manejador

Visible = b->IsWindowVisible ();

if (Visible)
    b->ShowWindow (SW_HIDE);
else
    b->ShowWindow (SW_SHOW);

//****** Botón derecho ********

b=GetDlgItem(IDC_DERECHA);   //Obtengo el manejador

Visible = b->IsWindowVisible ();

if (Visible)
    b->ShowWindow (SW_HIDE);
else
    b->ShowWindow (SW_SHOW);

//Cambio ahora la propiedad caption del botón
b= GetDlgItem(IDC_MOSTRAR_OCULTAR);

if (Visible)
    b->SetWindowText ("Mostrar");
else
    b->SetWindowText ("Ocultar"); 
}

void CBotonesDlg::OnActivarDesactivar() 
{

BOOL noActivo;
//Manejadores de los botones "Desactivar", "Izquierda", "Centro" y "Derecha"
CWnd* b=GetDlgItem(IDC_ACTIVAR_DESACTIVAR);
CWnd* bIzq=GetDlgItem(IDC_IZQUIERDA);
CWnd* bCen=GetDlgItem(IDC_CENTRO);
CWnd* bDer=GetDlgItem(IDC_DERECHA);

//Tomo el estado de uno de ellos, (los tres serán activados y desactivados en conjunto).
noActivo= bIzq->IsWindowEnabled (); 

//Les cambio el estado.
bIzq->EnableWindow (!noActivo);
bCen->EnableWindow (!noActivo);
bDer->EnableWindow (!noActivo);

//Le coloco el texto apropiado al botón.
if (noActivo)
    b->SetWindowText("Activar"); 
else
    b->SetWindowText("Desactivar");
}

Quizás se pregunte por qué no uso las variables de tipo CButton en lugar de andar averiguando handles de ventanas en cada función. Es verdad, debería haber usado las variables, aprovechando que las tuve que agregar para luego colocar tooltips, pero opté por manipular handles para ejemplificar uno de los temas de este artículo. Pero, si hay variables asignadas, es preferible, (más legible), usarlas.

Cómo colocar ToolTips a un botón:

Los tooltips son esa información que window muestra cuando se posiciona el mouse sobre un control. Desgraciadamente en VC++ no es tan sencillo como en VB donde el tooltip sólo se trata de una propiedad, Aquí deberemos escribir código y bastante por cierto.

Primero utilizaremos la String Table, a la cual se accede por medio de la solapa Resource del Class View, para especificar los textos de los tooltips. Una String Table es una tabla de cadenas donde cada cadena tiene un ID asociado, (por lo general todo lo relacionado con recursos tiene un ID). Si pulsa con el botón derecho en el espacio en blanco luego de la última cadena de la tabla, (probablemente la que corresponde al texto "&Acerca de..."), y selecciona "properties", aparecerá el cuadro de propiedades, (pulse el icono del alfiler para que quede fija esta ventana), con esta ventana agregue las siguientes cadenas:

IDS_OCULTAR: Oculta/ muestra los botones de abajo.

IDS_DESACTIVAR: Desactiva/ activa los botones de abajo.
IDS_IZQUIERDA: Soy el botón de la izquierda.
IDS_CENTRO: Soy el botón del centro.
IDS_DERECHA: Soy el botón de la derecha.

Ahora agregue un puntero a CToolTipCtrl al diálogo llamado m_pToolTip y en el constructor inicialícelo con NULL, (m_pToolTip = NULL).

Ahora agregue al diálogo una función de tipo BOOL llamada PoneToolTips() y escriba:

BOOL CBotonesDlg::PoneToolTips()
{
//Creo un objeto CToolTipCtrl en el "montón", (heap).
m_pToolTip = new CToolTipCtrl;

//Si la función Create falla entonce retorno FALSE.
if (!m_pToolTip->Create(this))
    return FALSE;

//Se agregan los texto de la String Table a cada botón.
if (!m_pToolTip->AddTool(&m_btnMostrar, IDS_OCULTAR))
    return FALSE;
if (!m_pToolTip->AddTool(&m_btnActivar, IDS_DESACTIVAR))
    return FALSE;
if (!m_pToolTip->AddTool(&m_btnIzquierda, IDS_IZQUIERDA))
    return FALSE;
if (!m_pToolTip->AddTool(&m_btnCentro, IDS_CENTRO))
    return FALSE;
if (!m_pToolTip->AddTool(&m_btnDerecha, IDS_DERECHA))
    return FALSE;
//Activo los ToolTips.
m_pToolTip->Activate(TRUE);
return TRUE;
}

En esta función creamos una instancia del objeto CToolTipCtrl y llamamos a la función Create() si todo continúa bien comenzamos a agregar los ToolTips a los controles por medio de la función AddTool cuyos parámetros son un puntero al control, (la variable de tipo CButton que hemos asignado a cada botón) y el ID de cadena correspondiente. Finalmente llamamos a Activate(TRUE).

Un tooltip se activa con un mensaje del mouse, para que esto se lleve a cabo deberemos agregar con el Class Wizard la función virtual PreTranslateMessage(MSG* pMsg) al diálogo. Pulse con el derecho sobre la clase del diálogo y seleccione "Add virtual function" luego agregue PreTranslateMessage y escriba:

BOOL CBotonesDlg::PreTranslateMessage(MSG* pMsg) 
{
//Si existe un objeto CToolTipCtrl
if (m_pToolTip!=NULL)
    //Le paso al control ToolTip el mensaje del mouse.
    m_pToolTip->RelayEvent(pMsg); 

//Retorno la función de la clase base.
return CDialog::PreTranslateMessage(pMsg);
}

La aplicación llama a esta función antes que TranslateMessage, (ver capítulo 1 de Aplicaciones SDK), entonces si el objeto ToolTip está creado se llama a la función RelayEvent() que se encarga de pasarle al objeto el mensaje del mouse para que lo procese. 

Nos quedan dos cosas más: llamar a la función PoneToolTips() desde OnInitDialog() y agregar el mensaje WM_DESTROY al diálogo para eliminar el objeto CToolTipCtrl.

if (!PoneToolTips())
    MessageBox("No se ha podido colocar los ToolTips", "Error", MB_ICONERROR);

 En OnInitDialog() y en OnDestroy():

void CBotonesDlg::OnDestroy() 
{
CDialog::OnDestroy();
//Elimino del "montón" el objeto ToolTip.
delete m_pToolTip; 
}

En la siguiente parte veremos como pintar un botón y que actúe a un evento como mover el mouse.

Descargar fuente de los ejemplos: botones.zip (28 Kb).

Todo sobre los botones. Parte 2.

Volver a la página principal