Capítulo 12 Control básico Scrollbar

Veremos ahora el siguiente control básico: la barra de desplazamiento o Scrollbar.

Las ventanas pueden mostrar contenidos que ocupan más espacio del que cabe en su interior, cuando eso sucede se suelen agregar unos controles en forma de barra que permiten desplazar el contenido a través del área de la ventana de modo que el usuario pueda ver las partes ocultas del documento.

Pero las barras de scroll pueden usarse para introducir otros tipos de datos en nuestras aplicaciones, en general, cualquier magnitud de la que sepamos el máximo y el mínimo, y que tenga un rango valores finito. Por ejemplo un control de volumen, de 0 a 10, o un termostato de -15º a 60º.

Las barras de desplazamiento tienen varias partes o zonas diferenciadas, cada una con su función particular. Me imagino que ya las conoces, pero las veremos desde el punto de vista de un programador.

Una barra de desplazamiento consiste en un rectángulo sombreado con un botón de flecha en cada extremo, y una caja en el interior del rectángulo (llamado normalmente thumb). La barra de desplazamiento representa la longitud o anchura completa del documento, y la caja interior la porción visible del documento dentro de la ventana. La posición de la caja cambia cada vez que el usuario desplaza el documento para ver diferentes partes de él. También se modifica el tamaño de la caja para adaptarlo a la proporción del documento que es visible. Cuanta más porción del documento resulte visible, mayor será el tamaño de la caja, y viceversa.

Hay dos modalidades de ScrollBars: horizontales y verticales.

El usuario puede desplazar el contenido de la ventana pulsando uno de los botones de flecha, pulsando en la zona sombreada no ocupada por el thumb, o desplazando el propio thumb. En el primer caso se desplazará el equivalente a una unidad (si es texto, una línea o columna). En el segundo, el contenido se desplazará en la porción equivalente al contenido de una ventana. En el tercer caso, la cantidad de documento desplazado dependerá de la distancia que se desplace el thumb.

Hay que distinguir los controles ScrollBar de las barras de desplazamiento estándar. Aparentemente son iguales, y se comportan igual, los primeros están en el área de cliente de la ventana, pero las segundas no, éstas se crean y se muestran junto con la ventana. Para añadir estas barras a tu ventana, basta con crearla con los estilos WS_HSCROLL, WS_VSCROLL o ambos. WS_HSCROLL añade una barra horizontal y WS_VSCROLL una vertical.

Un control scroll bar es una ventana de contol de la clase SCROLLBAR. Se pueden crear tantas barras de scroll como se quiera, pero el programador es el encargado de especidicar el tamaño y la posición de la barra.

Ficheros de recursos

Para nuestro ejemplo incluiremos un control ScrollBar de cada tipo, aunque en realidad son casi idénticos en comportamiento:

#include <windows.h>
#include "win011.h"

Menu MENU
BEGIN
 POPUP "&Principal"
 BEGIN
  MENUITEM "&Diálogo", CM_DIALOGO
 END
END

DialogoPrueba DIALOG 0, 0, 189, 106
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "Scroll bars"
FONT 8, "Helv"
BEGIN
 CONTROL "ScrollBar1", ID_SCROLLH, "SCROLLBAR",
    SBS_HORZ | WS_CHILD | WS_VISIBLE,
    7, 3, 172, 9
 CONTROL "Scroll 1:", -1, "STATIC",
    SS_LEFT | WS_CHILD | WS_VISIBLE,
    24, 18, 32, 8
 CONTROL "Edit1", ID_EDITH, "EDIT",
    ES_LEFT | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP,
    57, 15, 32, 12
 CONTROL "ScrollBar2", ID_SCROLLV, "SCROLLBAR",
    SBS_VERT | WS_CHILD | WS_VISIBLE,
    7, 15, 9, 86
 CONTROL "Scroll 2:", -1, "STATIC",
    SS_LEFT | WS_CHILD | WS_VISIBLE,
    23, 41, 32, 8
 CONTROL "Edit2", ID_EDITV, "EDIT",
    ES_LEFT | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP,
    23, 51, 32, 12
 CONTROL "Aceptar", IDOK, "BUTTON",
    BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP,
    40, 87, 50, 14
END

Para ver cómo funcionan las barras de scroll hemos añadido dos controles Edit, que mostrarán los valores seleccionados en cada control ScrollBar. Para más detalles acerca de estos controles ver control scrollbar.

Ahora veremos más cosas sobre los estilos de los controles ScrollBar:

CONTROL "ScrollBar1", ID_SCROLLH, "SCROLLBAR",
   SBS_HORZ | WS_CHILD | WS_VISIBLE,
   7, 3, 172, 9
CONTROL "ScrollBar2", ID_SCROLLV, "SCROLLBAR",
   SBS_VERT | WS_CHILD | WS_VISIBLE,
   7, 15, 9, 86
  • CONTROL es la palabra clave que indica que vamos a definir un control.
  • A continuación, en el parámetro text, en el caso de los scrollbar sólo sirve como información, y no se usa.
  • id es el identificador del control. El identificador será necesario para inicializar y leer el valor del scrollbar, así como para manipular los mensajes que produzca.
  • class es la clase de control, en nuestro caso "SCROLLBAR".
  • style es el estilo de control que queremos. En nuestro caso es una combinación de un estilo scrollbar y varios de ventana:
    • SBS_HORZ: Indica se trata de un scrollbar horizontal.
    • SBS_VERT: Indica se trata de un scrollbar vertical.
    • WS_CHILD: crea el control como una ventana hija.
    • WS_VISIBLE: crea una ventana inicialmente visible.
  • coordenada x del control.
  • coordenada y del control.
  • width: anchura del control.
  • height: altura del control.

Iniciar controles Scrollbar

Los controles Scrollbar tienen varios tipos de parámetros que hay que iniciar. Los límites de valores mínimo y máximo, y también el valor actual.

El lugar adecuado para hacerlo también es al procesar el mensaje WM_INITDIALOG de nuestro cuadro de diálogo, y para ajustar los parámetros podemos usar mensajes o funciones. En el caso de hacerlo con mensajes hay que usar la función SendDlgItemMessage.

También usaremos una estructura para almacenar los valores iniciales y de la última selección de las dos barras de desplazamiento:

// Datos de la aplicación
typedef struct stDatos {
   int ValorH;
   int ValorV;
} DATOS;

Declararemos los datos como estáticos en el procedimiento de ventana, y asignaremos valores iniciales a los controles al procesar el mensaje WM_CREATE:

    static DATOS Datos;
...
        case WM_CREATE:
           hInstance = ((LPCREATESTRUCT)lParam)->hInstance;
           Datos.ValorH = 10;
           Datos.ValorV = 32;
           return 0;

Pasaremos un puntero a nuestra estructura de datos al procedimiento de diálogo usando el parámetros lParam y la función DialogBoxParam:

DialogBoxParam(hInstance, "DialogoPrueba", hwnd, DlgProc, (LPARAM)&Datos);

En el procedimiento de diálogo disponemos de un puntero estático a la estructura de datos, que inicializaremos al procesar el mensaje WM_INITDIALOG.

La parte de inicialización de los scrollbars es como sigue. Hemos inicializado el scrollbar horizontal usando las funciones y el vertical usando los mensajes:

    static DATOS *Datos;
...
        case WM_INITDIALOG:
           Datos = (DATOS*)lParam;
           SetScrollRange(GetDlgItem(hDlg, ID_SCROLLH), SB_CTL,
              0, 100, TRUE);
           SetScrollPos(GetDlgItem(hDlg, ID_SCROLLH), SB_CTL,
              Datos->ValorH, TRUE);
           SetDlgItemInt(hDlg, ID_EDITH, (UINT)Datos->ValorH, FALSE);
           SendDlgItemMessage(hDlg, ID_SCROLLV, SBM_SETRANGE,
              (WPARAM)0, (LPARAM)50);
           SendDlgItemMessage(hDlg, ID_SCROLLV, SBM_SETPOS,
              (WPARAM)Datos->ValorV, (LPARAM)TRUE);
           SetDlgItemInt(hDlg, ID_EDITV, (UINT)Datos->ValorV, FALSE);

Para iniciar el rango de valores del scrollbar se usar la función SetScrollRange o el mensaje SBM_SETRANGE. Para cambiar el valor seleccionado o posición se usa la función SetScrollPos o el mensaje SBM_SETPOS.

Iniciar controles scrollbar: estructura SCROLLINFO

A partir de la versión 4.0 de Windows existe otro mecanismo para inicializar los scrollbars. También tiene la doble forma de mensaje y función. Su uso se recomienda en lugar de SetScrollRange, que sólo se conserva por compatibilidad con Windows 3.x.

Se trata de la función SetScrollInfo y del mensaje SBM_SETSCROLLINFO. También es necesaria una estructura que se usará para pasar los parámetros tanto a la función como al mensaje: SCROLLINFO.

Usando esta forma, el ejemplo anterior quedaría así:

    static DATOS *Datos;
    SCROLLINFO sih = {
       sizeof(SCROLLINFO),
       SIF_POS | SIF_RANGE | SIF_PAGE,
       0, 104,
       5,
       0,
       0;
    SCROLLINFO siv = {
       sizeof(SCROLLINFO),
       SIF_POS | SIF_RANGE | SIF_PAGE,
       0, 54,
       5,
       0,
       0;

...
        case WM_INITDIALOG:
           Datos = (DATOS*)lParam;
           sih.nPos = Datos->ValorH;
           siv.nPos = Datos->ValorV;
           SetScrollInfo(GetDlgItem(hDlg, ID_SCROLLH), SB_CTL, &sih, TRUE);
           SetDlgItemInt(hDlg, ID_EDITH, (UINT)Datos->ValorH, FALSE);
           SendDlgItemMessage(hDlg, ID_SCROLLV, SBM_SETSCROLLINFO,
			  (WPARAM)TRUE, (LPARAM)&siv);
           SetDlgItemInt(hDlg, ID_EDITV, (UINT)Datos->ValorV, FALSE);
           return FALSE;

El segundo campo de la estructura SCROLLINFO consiste en varios bits que indican qué parámetros de la estructura se usarán para inicializar los scrollbars. Hemos incluido la posición, el rango y el valor de la página. Sería equivalente haber puesto únicamente SIF_ALL.

El valor de la página no lo incluíamos antes, y veremos que será útil al procesar los mensajes que provienen de los controles scrollbar. Además el tamaño de la caja de desplazamiento se ajusta de modo que esté a escala en relación con el tamaño total del control scrollbar. Si hubiéramos definido una página de 50 y un rango de 0 a 100, el tamaño de la caja sería exactamente la mitad del tamaño del scrollbar.

Hay que tener en cuenta que el valor máximo que podremos establecer en un control no es siempre el que nosotros indicamos en el miembro nMax de la estructura SCROLLINFO. Este valor depende del valor de la página (nPage), y será nMax-nPage+1. Así que si queremos que nuestro control pueda devolver 100, y la página tiene un valor de 5, debemos definir nMax como 104. Este funcionamiento está diseñado para scrollbars como los que incluyen las ventanas, donde la caja indica la porción del documento que se muestra en su interior.

Procesar los mensajes procedentes de controles Scrollbar

Cada vez que el usuario realiza alguna acción en un control Scrollbar se envía un mensaje WM_HSCROLL o WM_VSCROLL, dependiendo del tipo de control, a la ventana donde está insertado. En realidad pasa algo análogo con todos los controles, pero en el caso de los Scrollbars, es imprescindible que el programa procese algunos de esos mensajes adecuadamente.

Estos mensajes entregan distintos valores en la palabra de menor peso del parámetro wParam, según la acción del usuario sobre el control. En la palabra de mayor peso se incluye la posición actual y en lParam el manipulador de ventana del control.

De modo que nuestra rutina para manejar los mensajes de los scrollbars debe ser capaz de distinguir el control del que procede el mensaje y el tipo de acción, para actuar en consecuencia.

En nuestro caso es irrelevante la orientación de la barra de scroll, podemos distinguirlos por el identificador de ventana, de todos modos procesaremos cada uno de los dos mensajes con una rutina distinta.

Para no recargar en exceso el procedimiento de ventana del diálogo, crearemos una función para procesar los mensajes de las barras de scroll. Y la llamaremos al recibir esos mensajes:

    switch (msg)                  /* manipulador del mensaje */
    {
...
        case WM_HSCROLL:
           ProcesarScrollH(hDlg, (HWND)lParam,
              (int)LOWORD(wParam), (int)HIWORD(wParam));
           return FALSE;
        case WM_VSCROLL:
           ProcesarScrollV(hDlg, (HWND)lParam,
              (int)LOWORD(wParam), (int)HIWORD(wParam));
           return FALSE;
...

Los códigos que tenemos que procesar son los siguientes:

  • SB_BOTTOM desplazamiento hasta el principio de la barra, en verticales arriba y en horizontales a la izquierda.
  • SB_TOP: desplazamiento hasta el final de la barra, en verticales abajo y en horizontales a la derecha.
  • SB_LINERIGHT y SB_LINEDOWN: desplazamiento una línea a la derecha en horizontales o abajo en verticales.
  • SB_LINELEFT y SB_LINEUP: desplazamiento un línea a la izquierda en horizontales o arriba en verticales.
  • SB_PAGERIGHT y SB_PAGEDOWN: desplazamiento de una página a la derecha en horizontales y abajo en verticales.
  • SB_PAGELEFT y SB_PAGEUP: desplazamiento un párrafo a la izquierda en horizontales o arriba en verticales.
  • SB_THUMBPOSITION: se envía cuando el thumb está en su posición final.
  • SB_THUMBTRACK: el thumb se está moviendo.
  • SB_ENDSCROLL: el usuario a liberado el thumb en una nueva posición.

En nuestro caso, no haremos que la variable asociada se actualice hasta que pulsemos el botón de aceptar, pero actualizaremos la posición del thumb y el valor del control edit asociado a cada scrollbar.

Veamos por ejemplo la rutina para tratar el scroll horizontal:

void ProcesarScrollH(HWND hDlg, HWND Control, int Codigo, int Posicion)
{
   int Pos = GetScrollPos(Control, SB_CTL);

   switch(Codigo) {
      case SB_BOTTOM:
         Pos = 0;
         break;
      case SB_TOP:
         Pos = 100;
         break;
      case SB_LINERIGHT:
         Pos++;
         break;
      case SB_LINELEFT:
         Pos--;
         break;
      case SB_PAGERIGHT:
         Pos += 5;
         break;
      case SB_PAGELEFT:
         Pos -= 5;
         break;
      case SB_THUMBPOSITION:
      case SB_THUMBTRACK:
         Pos = Posicion;
      case SB_ENDSCROLL:
         break;
   }
   if(Pos < 0) Pos = 0;
   if(Pos > 100) Pos = 100;
   SetDlgItemInt(hDlg, ID_EDITH, (UINT)Pos, FALSE);
   SetScrollPos(Control, SB_CTL, Pos, TRUE);
}

Como puede observarse, actualizamos el valor de la posición dependiendo del código recibido. Nos aseguramos de que está dentro de los márgenes permitidos y finalmente actualizamos el contenido del control edit. La función para el scrollbar vertical es análoga, pero cambiando los identificadores de los códigos y los valores de los límites.

Hemos usado una función nueva, GetScrollPos para leer la posición actual del thumb.

Procesar mensajes de scrollbar usando SCROLLINFO

Como comentamos antes, a partir de la versión 4.0 de Windows existe otro mecanismo para inicializar los scrollbars. También tiene la doble forma de mensaje y función. Su uso se recomienda en lugar de GetScrollRange, que sólo se conserva por compatibilidad con Windows 3.x.

Usando GetScrollInfo, la función para procesar el mensaje del scrollbar horizontal quedaría así:

void ProcesarScrollH(HWND hDlg, HWND Control, int Codigo, int Posicion)
{
   SCROLLINFO si = {
       sizeof(SCROLLINFO),
       SIF_ALL, 0, 0, 0, 0, 0};

   GetScrollInfo(Control, SB_CTL, &si);

   switch(Codigo) {
      case SB_BOTTOM:
         si.nPos = si.nMin;
         break;
      case SB_TOP:
         si.nPos = si.nMax;
         break;
      case SB_LINEDOWN:
         si.nPos++;
         break;
      case SB_LINEUP:
         si.nPos--;
         break;
      case SB_PAGEDOWN:
         si.nPos += si.nPage;
         break;
      case SB_PAGEUP:
         si.nPos -= si.nPage;
         break;
      case SB_THUMBPOSITION:
      case SB_THUMBTRACK:
         si.nPos = Posicion;
      case SB_ENDSCROLL:
         break;
   }
   if(si.nPos < si.nMin) si.nPos = si.nMin;
   if(si.nPos > si.nMax-si.nPage+1) si.nPos = si.nMax-si.nPage+1;

   SetScrollInfo(Control, SB_CTL, &si, true);
   SetDlgItemInt(hDlg, ID_EDITH, (UINT)si.nPos, FALSE);
}

Usamos los valores de la estructura para acotar la posición de la caja y para avanzar y retroceder de página, esto hace que nuestra función sea más independiente y que use menos constantes definidas en el fuente.

También se puede usar el mensaje SBM_GETSCROLLINFO, basta con cambiar la línea:

GetScrollInfo(Control, SB_CTL, &si);

por esta otra:

SendDlgItemMessage(hDlg, ID_SCROLLH,
   SBM_GETSCROLLINFO, 0, (LPARAM)&siv);

Devolver valores a la aplicación

Cuando el usuario ha decidido que los valores son los adecuados pulsará el botón de Aceptar. En ese momento deberemos capturar los valores de los controles scroll y actualizar la estructura de parámetros.

También en este caso podemos usar un mensaje o una función para leer la posición del thumb del scrollbar. La función ya la hemos visto un poco más arriba, se trata de GetScrollPos. El mensaje es SBM_GETPOS, ambos devuelven el valor de la posición actual del thumb del control.

Usaremos los dos métodos, uno con cada control. El lugar adecuado para leer esos valores sigue siendo el tratamiento del mensaje WM_COMMAND:

     case WM_COMMAND:
           switch(LOWORD(wParam)) {
              case IDOK:
                 Datos->ValorH = GetScrollPos(GetDlgItem(hDlg, ID_SCROLLH), SB_CTL);
                 Datos->ValorV = SendDlgItemMessage(hDlg, ID_SCROLLV,
                    SBM_GETPOS, 0, 0);
                 EndDialog(hDlg, FALSE);
                 return TRUE;
              case IDCANCEL:
                 EndDialog(hDlg, FALSE);
                 return FALSE;

Ejemplos 11 y 12

Nombre Fichero Fecha Tamaño Contador Descarga
Ejemplo 11 win011.zip 2004-05-17 3524 bytes 181
Nombre Fichero Fecha Tamaño Contador Descarga
Ejemplo 12 win012.zip 2004-05-17 3616 bytes 172