Todo sobre ToolBars. (Parte 1) 

 

Por: Demian Panello.

 

 

ToolBars:

 

Las ToolBar, (barra de herramientas), son usadas extensivamente en muchos tipos de aplicaciones. Proveen al usuario una forma rápida de ejecutar un comando. Generalmente un ToolBar es una serie de botones, cada botón representa un comando específico. Un comando implementado por una barra de herramientas puede estar asociado a un comando del menú, lo que implica que dos items comparten el mismo comando. Ambos items, el menú y la barra de herramientas implementan el mensaje WM_COMMAND. Además pueden implementar el mensaje UPDATE_COMMAND_UI para cambiar el estado de un botón o item del menú. Este mensaje es usado para habilitar, deshabilitar y especificar como activado o desactivado el estado de un comando.

Un barra de herramientas pueden ser implementada como flotante o acoplada a la ventana principal. 

 

Por ejemplo veamos el Wordpad, a continuación una captura:

 

Figura 1

 

A excepción de la regla, las otras barras son acoplables; en la captura se ve la barra acoplada, (docked) a la 

ventana principal, (Frame Window) y la barra de formato, en ese momento, se encuentra flotante, (aunque es acoplable si uno la acerca y suelta en algún lugar de la ventana principal).

 

En MFC una barra de herramientas se implementa con la clase CToolBar, la cual se encarga de la creación, mapeo de los mensajes, acople y desacople, (docking and floating).

 

Una aplicación SDI o MDI standard creada por el Application Wizard tiene una barra de herramientas por defecto, (una barra acoplable). Nosotros a partir de ahora veremos cómo agregar una barra de herramientas extra, cómo implementar los mensajes de comando para los controles de la barra y cómo personalizar su comportamiento por defecto.

 

Agregar una barra de herramientas extra:

 

 

Default ToolBar:

 

Cuando se usa el Application Wizard para generar el "esqueleto" de una aplicación SDI o MDI, en el paso 4, (ver figura 2 más abajo), se nos pregunta si se desea crear una barra de herramientas acoplable por defecto. Esta barra de herramientas, (a partir de aqui llamaremos "default toolbar"), comparte el  mismo ID con el menú principal.

Tiene ocho botones con bitmaps, los cuales son accesos rápidos a los mismos items del menú principal. Luego de ejecutar esta aplicación, veremos una barra de herramientas acoplada al borde superior, (TOP), de la ventana principal. Usando el mouse se puede facilmente acoplar y desacoplar la barra a otros bordes de la ventana.

 

Figura 2

 

 

 

La barra de herramientas se puede editar como recurso desde el entorno de Visual C++. Seleccionando la solapa "Resource View" en la ventana "Workspace", se mostrarán todos los recursos usados en la aplicación. Expandiendo el nodo "ToolBar", (pulsando el "+"), se pueden ver todas las barras de herramientas usadas en la aplicación. Dobble click en el ID  "IDR_MAINFRAME", se editará el bitmap de la barra de herramientas. Se puede editar o eliminar un botón de esa barra, también se puede agregar nuevos botones con imágenes, (bitmaps buttons), y asignarles IDs. Al hacer esto podemos usar un ID de comando existente o crear uno nuevo. El el último caso debemos además implementar el mapa de mensaje para ese comando.

 

El Application Wizard ayuda mucho en eso de agregar de forma automática una barra de herramientas por defecto. No obstante como programadores es importante saber cómo se implementa esa barra de herramientas, ya que si necesitamos hacer cambios, como  por ejemplo si queremos dockear, (acoplar) la barra en el fondo de la ventana en lugar de la parte superior, o acoplarla en los costados, debemos saber qué parte del código "tocar". Desde ya, para esto necesitamos entender, al menos, cómo es la implementación básica de una ToolBar, para así poder personalizarla.

 

Como ocurre con los menues, una barra es implementada en una ventana tipo CFrameWnd. Al momento de crear la ventana, (de tipo CFrameWnd), necesitamos preparar el recurso de menú, usar la clase CMenu para instanciar un nuevo objeto y luego cargar el menú. Para crear la barra de herramientas hay que seguir unos pasos muy similares a estos: preparar la toolbar, usar la clase CToolBar para instanciar un objeto nuevo y cargar la barra desde el recurso. Luego de cargar la barra con éxito desde el recurso, se pueden llamar una serie de funciones miembros de CToolBar para crear la barra y personalizarla.

En la implementación de la barra por defecto de una aplicación SDI encontraremos una variable de tipo CToolBar declarada en la clase CMainFrame:


class CMainFrame : public CFrameWnd{

...
protected: // variables miembros de la clase CMainFrame
 

       CStatusBar m_wndStatusBar;
       CToolBar m_wndToolBar;
... 

};

 

Siguiendole el rastro a la variable m_wndToolBar, encontraremos que la implementación ocurre en la función CMainFrame::OnCreate(...), donde la ventana principal es creada.

 

Crear una toolbar implica los siguientes pasos, (siempre dentro de la función CMainFrame::OnCreate(...) donde por lo pronto tiene sentido crear una barra):

 

1) Llamar a CToolBar::Create(...) para crear la barra.

2) Llamar a CToolBar::LoadToolBar(...) para cargar la barra desde un recurso, (hay que pasarle el ID del recurso).

3) Llamar a CToolBar::SetBarStyle(...) para especificar diversos estilos, como por ejemplo si el tamaño de la barra será fijo o dinámico, etc.

4) Para hacer que la barra de herramientas sea dockeable, (acoplable), hay que llamar a la función CToolBar::EnableDocking(...) pasandole los flags apropiados que indican a qué borde se va a acoplar la barra, (e incluso se puede indicar que se acople a cualquiera).

5) Finalmente para que el acople sea posible es necesario llamar a la función CMainFrame::DockControlBar(...). Si tenemos más de una barra entonces hay que llamar a esta función por cada una de las barras.

 

Es necesario seguir estos cinco pasos para implementar y modificar los atributos de una barra.

 

 

El mapa de mensajes:

 

 

Como las barras de herramientas son usadas para proveer una manera alternativa de ejecutar comandos, es necesario especificar un mapa de mensajes para los controles de la barra. Esto permitirá encaminar los mensajes que lleguen a nuestra aplicación producto de pulsar un comando de la barra. El proceso de implementar un mapa de mensajes para la barra de herramientas es exactamente igual que el de los menues. En MFC esto se hace declarando una función miembro de tipo afx_msg y agregando  macros como ON_COMMAND y ON_UPDATE_COMMAND_UI.

El "mapeo" de estos mensaje puede implementarse en cualquiera de las cuatro clases de la aplicación SDI, (o MDI), CWinApp, CFrameWnd, CView y CDocument. La elección de en qué clase implementar el mapa de mensajes para los menues o botones de la barra, depende a qué se relacione el item especificamente, normalmente, como esos comandos actuan sobre los datos del documento, es común implementar estos mapas en la clase derivada de CDocument.

 

A continuación una lista de pasos a seguir para implementar en cualquiera de esas cuatro clases un mapa de mensajes para los botones de la barra.

 

1) Declarar  funciones miembros tipo afx_msg.

2) Implementar esas funciones, ( o sea escribir el código que ejecutarán).

3) Agregar las macros de "mapeo" necesarias entre BEGIN_MESSAGE_MAP y END_MESSAGE_MAP, (que son líneas de código generadas por el Application Wizard en el archivo de implementación de la clase elegida). Es necesario usar la macro ON_COMMAND para mapear el mensaje de Windows WM_COMMAND, y usar ON_UPDATE_COMMAND_UI para implementar una rutina de actualización de la interface de usuario, (en caso que sea necesario). Estos mensajes tienen que tener el siguiente formato:

 

 

 

BEGIN_MESSAGE_MAP(class name, base class name)
//{{AFX_MSG_MAP(class name)
ON_COMMAND(command ID, member function name)
ON_UPDATE_COMMAND_UI(command ID, member function name)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

 

En la mayoría de los casos, los mapas de mensajes se pueden implementar usando el Class Wizard, por lo tanto sólo es necesario seleccionar el ID del comando y confirmar el nombre de la función. No obstante hay algunos casos en el cual es necesario agregar el mensaje manualmente.

 

Agregar un nuevo recurso ToolBar:

 

Bien, ahora que sabemos cómo se implementa la barra de herramientas por defecto, es fácil agregar un barra más. Primero crearemos con el Application Wizard una aplicación SDI llamada "Bar", dejando todos los parametros por defecto.

 

Antes de modificar el código debemos agregar un nuevo recurso de ToolBar y modificar sus botones.

Para esto procure seguir los siguientes pasos:

 

1) Estando en el proyecto "Bar", en la ventana Workspace seleccione la solapa Resource View.

2) Luego, vaya al menú Insert-> Resource del menú, (o pulse CTRL + R). Aparecerá una ventana para seleccionar el tipo de recurso a agregar. Seleccione "toolbar" y luego pulse "New". Se creará una nueva barra de herramientas en blanco con el ID: IDR_TOOLBAR1. Ya que este ID por defecto no es muy descriptivo, lo cambiaremos. Pulse sobre el nodo IDR_TOOLBAR1 con el botón derecho y luego seleccione "Properties".  Aparecerá una ventana con una solapa, en el cuadro de texto "ID", escriba "ID_COLOR_BAR".

3) Para modificar la barra de herramientas, simplemente use las herramientas de edición de gráficos que provee el entorno y agregue cuatro botones en la barra pintados con los colores rojo, verde, azul y amarillo, y los IDs: ID_BUTTON_RED, ID_BUTTON_GREEN, ID_BUTTON_BLUE e ID_BUTTON_YELLOW. 

 

 

Figura 3

 

Declarando una nueva variable miembro.

 

Luego de que el recurso de barra está listo, podemos implementar este toolbar en la aplicación.

El primer paso  es declarar una nueva variable miembro CToolBar en la clase CMainFrame:

 

{
...
protected:
      CStatusBar m_wndStatusBar;
      CToolBar m_wndToolBar;
      CToolBar m_wndColorBar;
...
};

 

La nueva variable es m_wndColorBar, la cual ha sido agregada a continuación de las otras dos variables usadas que implementan la barra de estado por defecto y la "default toolbar".

 

Luego hay que localizar la implementación de la función CMainFrame::OnCreate(...) y allí veremos como se crea la "default toolbar":

 

...
if (!m_wndToolBar.Create(this) || !m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
{
    TRACE0("Failed to create toolbar\n");
    return -1;
}
...

 

La función CToolBar::Create(...) es la primera en invocarse para poder crear la barra. Luego, CToolBar::LoadToolBar(...) es llamada para cargar el recurso de la barra, (en el caso de la "default toolbar" es IDR_MAINFRAME).

Cuando se llama a la función CToolBar::Create(...), es necesario especificarle la ventana padre de la toolbar pasandole un puntero a CWnd. Como esta función está siendo llamada desde la clase CMainFrame, nosotros debemos usar el objeto "this" como puntero a la ventana padre.

El siguiente fragmento de código muestra cómo se especifican los estilos de la "default toolbar":

 

 

m_wndToolBar.SetBarStyle( m_wndToolBar.GetBarStyle() | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC );

 

La función CToolBarStyle(...) "setea" los estilos de la barra, los cuales pueden combinarse usando la operación OR a nivel de bits. Como no se quiere perder el estilo por defecto de la barra, primero se llama a la función CToolBar::GetBarStyle() para obtener los estilos actuales los cuales inmediatamente se combinan con los nuevos por medio del operados | , (OR). En el código anterior, tres nuevos estilos se agregan a la barra: primero el flag CBRS_TOOLTIPS permite admitir tool tips, (descripciones breves flotantes), para cuando se pasa con el puntero del mouse sobre el botón; segundo, el flag CBRS_FLYBY permite que en la barra de estado se muestre un texto descriptivo; el tercer flag CBRS_SIZE_DYNAMIC permite que el usuario modifique el tamaño de la barra de herramientas, si no se especifica este estilo, por defecto será de tamaño fijo.

 


Nota

Los textos para tooltip y flyby son recursos de la aplicación, y el ID de la cadena debe ser el mismo que el del ID de comando del control. Por ejemplo, si queremos un texto tooltip y flyby para el botón ID_BUTTON_RED, debemos crear un cadena en la tabla de cadenas, ("string table" en recursos). En la cadena el texto es separado por '\n', donde la primer parte es el texto para flyby y lo que se encuentra luego de '\n' para el tooltip.

 


 

Continuando con el analisis de la implementación de la barra de herramientas por defecto, (default toolbar). La siguiente línea de código especifica la característica de acoplamiento de la barra:

 

m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);

 

La función CToolBar::EnableDocking(...) hace la barra "acoplable", "dockeable". El flag CBRS_ALIGN_ANY indica que la barra se puede acoplar a cualquiera de los cuatros bordes de la ventana principal. Desde ya se puede cambiar esto especificando CBRS_ALING_TOP, CBRS_ALIGN_BOTTOM, CBRS_ALIGN_LEFT o combinaciones de algunos de éstos, los cuales no hacen falta aclarar mucho ya que son lo suficientemente descriptivos.

 

Especificando aún algunos de estos flags la barra seguirá sin poder ser acoplable si la ventana no admite acoplamiento. Debemos llamar entonces a la función CFrameWnd::EnableDocking(...) para soportar el acoplamiento en la ventana principal y además llamar a CFrameWnd::DockControlBar(...) por cada barra para realmente acoplarla. El siguiente código muestra cómo estas funciones son invocadas para la "default bar".

 

EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(&m_wndToolBar);

 

 

Creando una nueva barra de herramientas:

 

Bien, necesitamos entonces escribir todo ese código pero para nuestra nueva barra m_wndColorBar. 

 

El siguiente código muestra como quedaría la función CMainFrame::OnCreate(...) con el agregado del código para nuestra nueva barra de herramientas:

 

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
        return -1;
    if (!m_wndToolBar.Create(this) || !m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
   {
      TRACE0("Failed to create toolbar\n");
       return -1;
   }

   if (!m_wndColorBar.Create(this) || !m_wndColorBar.LoadToolBar(IDR_COLOR_BAR))
   {
      TRACE0("Error al crear la barra de colores\n");
      return -1;
   }


   if (!m_wndStatusBar.Create(this) || !m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT)))

  {
     TRACE0("Failed to create status bar\n");
      return -1;
   }

m_wndToolBar.SetBarStyle(m_wndToolBar.GetBarStyle() | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC);
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);

// Se especifican los estilos de la barra de colores
m_wndColorBar.SetBarStyle(m_wndColorBar.GetBarStyle() | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC);
m_wndColorBar.EnableDocking(CBRS_ALIGN_ANY);   // La barra se puede acoplar a cualquier borde de la ventana
EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(&m_wndToolBar);
DockControlBar(&m_wndColorBar);
return 0;
}

 

Compilando y ejecutando la aplicación, veremos que la barra ha sido creada. La barra puede ser acoplada a cualquie borde de la ventana o incluso puede ser flotante. Si se acopla la ventana al borde derecho o izquierdo, veremos que la barra automaticamente adoptará una forma vertical. Esta característica es soportada por la clase MFC CToolBar y no necesitamos codificar ninguna línea de código más para obtener esto.

 

Figura 4

 

En la captura, (Figura 4) se ve que la barra, (originalmente acoplada arriba junto a la barra de herramientas por defecto), fue arrastrada y soltada sobre el borde derecho de la ventana. Seguramente los botones de su barra no lucen como los mios, eso es porque aún no se ha implementado ninguna función para manejar los mensaje de comando, (yo ya lo he hecho por eso los botones de mi barra de herramientas están disponibles).

 

 

El mapa de mensajes.

 

 

Como habrá visto, al pulsar los botones de nuestra nueva barra, no pasa nada, ningún botón funciona. Esto es porque todavía no hemos implementado ninguna función que mapee los mensaje de los nuevos comando de la barra, por lo tanto los botones no estan disponibles.

 

En las aplicaciones Windows, los comando son ejecutados enviando el mensaje WM_COMMAND. Cuando el usuario pulsa un item del menú o un botón de una barra de herramientas, el sistema envia el mensaje WM_COMMAND a la aplicación. Todos los mensajes de Windows tienen dos parámetros, WPARAM y LPARAM, (no tienen nada en particular, son dos enteros, cuando la aplicación recibe el mensaje tambien recibe los parámetros). Para el mensaje WM_COMMAND, el parámetro WPARAM es usado para guardar el ID, (ID de comando, como por ejemplo ID_BUTTON_RED en nuestro ejemplo), el cual puede ser examinado por la aplicación para así responder apropiadamente.

 

Para el mensaje WM_COMMAND la función de mapeo no recibe parámetros y debe retornar void, (para otros tipos de mensajes el formato de las funciones puede variar). El mapa de mensaje puede implementarse usando la macro ON_COMMAND que tiene el siguiente formato:

 

ON_COMMAND(ID del control, nombre de la función )

 

Por ejemplo, si tenemos la función miembro OnButtonRed() en la clase CBarDoc, y deseamos "mapear", "rutear" el mensaje WM_COMMAND que ocurre al pulsar el botón ID_BUTTON_RED a esta función podemos implementar el mapa de mensaje como sigue:

 

BEGIN_MESSAGE_MAP(CBarDoc, CDocument)
ON_COMMAND(ID_BUTTON_RED, OnButtonRed)
END_MESSAGE_MAP()

 

El mapeo de un mensaje usando la macro debe realizarce entre las macros BEGIN_MESSAGE_MAP y END_MESSAGE_MAP. Tenga presente que si se quiere que otras funciones miembros de otras clases tambien reciban el mensaje, debemo implementar el mapa en cada clase por separado.

 

Class Wizard está diseñado para facilitar la tarea de cración de mapas de mensajes. Provee una manera práctica y rápida de agregar entradas en un mapa de mensaje de una clase. Todo lo que hay que hacer es seleccionar un mensaje y confirmar los nombre de las funciones que se iran creando, (siempre verificando en qué clase se está creando el mapa).

A continuación se listan los pasos para agregar, (usando el Class Wizard) una función manejadora del mensaje WM_COMMAND para el botón ID_BUTTON_RED en la clase CBarDoc:

 

1) Pulse CTRL + W para abrir el Class Wizard.

2) Seleccione la solapa "Message Maps".

3) De la lista "Class Name" seleccione CBarDoc. (porque mapearemos los comando a funciones miembro de esta clase).

4) De la lista "Object Ids" seleccione "ID_BUTTON_RED".

5) De la lista "Messages" seleccione "COMMAND".

6) Pulse el botón "Add Function".

7) Confirme la ventanita que pregunta si se desea crear una función con ese nombre.

8) La función se agrega a la lista "Members Functions".

 

Se pueden repetir los pasos 4 al 7 para los otros botones, (ID_BUTTON_GREEN, ID_BUTTON_BLUE e ID_BUTTON_YELLOW).

 

Luego de cerrar el Class Wizard las funciones agregadas estarán visibles para editar, por ejemplo para ID_BUTTON_RED se obtuvo:

 

void CBarDoc::OnButtonRed()
{
    AfxMessageBox("Rojo");
}

 

Bien, en esta primera parte vimos cómo se implementa la barra de herramientas por defecto de una aplicación SDI o MDI, luego esto nos sirvió para entender cómo implementar una nueva barra de herramientas, y eso hicimos creamos con el editor del Visual Studio una nueva barra y la pusimos en funcionamiento en una aplicación SDI.

 

En la siguiente parte continuaremos viendo cómo hacer que los botones cambien de estado e implementaremos una lista desplegable en la barra de herramientas.

 

Volver a la página principal