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.