Ventanas de estado con varias partes

Podemos dividir la barra de estado en partes de diferente anchura, cada una de las cuales puede mostrar un texto diferente, que usaremos para informar al usuario sobre el estado de ciertos valores de la aplicación.

Para dividir la barra de estado en partes se usa un mensaje SB_SETPARTS, indicando en el parámetro wParam el número de partes y el lParam la dirección de un array con tantos valores de enteros como partes, cada uno de los cuales es la distancia, desde el borde izquierdo de la ventana al borde derecho de cada parte, en pixels y en coordenadas de cliente.

Para que la barra de estado ocupe todo el ancho de la ventana padre es necesario que el valor de anchura para la última parte coincida con la anchura del área de cliente. También se puede usar el valor -1 para esa última anchura, de modo que la última parte se ensanchará automáticamente hasta ocupar toda la anchura disponible.

    int anchura[] = {54, 74, 94, -1};
...
		CreateStatusWindow(WS_CHILD|WS_VISIBLE, "Texto de prueba", hwnd, ID_STATUS);
		SendDlgItemMessage(hwnd, ID_STATUS, SB_SETPARTS, 4, (LPARAM)anchura);

Pero probablemente estemos acostumbrados a que la barra de estado ocupe todo el ancho de la ventana, y que la parte que ajusta la anchura sea la primera, y no la última, siendo el resto de las partes siempre igual de anchas.

Para conseguir ese efecto aprovecharemos el procesamiento del mensaje WM_SIZE, obtendremos la anchura del área de cliente, y ajustaremos las anchuras de cada parte en función de esa anchura.

    RECT re;
    int anchura[4];
...
        case WM_SIZE:
           GetClientRect(hwnd, &re);
           anchura[3] = re.right-20;
           anchura[2] = anchura[3]-60;
           anchura[1] = anchura[2]-60;
           anchura[0] = anchura[1]-60;
           SendDlgItemMessage(hwnd, ID_STATUS, SB_SETPARTS, 4, (LPARAM)anchura);
           SendDlgItemMessage(hwnd, ID_STATUS, msg, wParam, lParam);

El número de partes en las que se puede dividir una ventana de estados está limitado a 255. Se trata de un límite poco importante, ya que generalmente usaremos muchas menos partes.

También es posible obtener las dimensiones actuales de las partes de una ventana de estado, usando un mensaje SB_GETPARTS. En wParam indicaremos el número de partes a recuperar, si ese número es mayor que el número de partes existentes, sólo se recuperarán las que existan. En lParam pasaremos un puntero a un array de enteros en los que recibiremos las distancias de los bordes derechos de cada parte. El valor de retorno es el número de partes cuyas anchuras son recuperadas. Si se usa el valor cero para lParam, recuperaremos sólo el número de partes.

		int nPartes;
		int *anchura;
		anchura = (int*)malloc(sizeof(int)*10);
		nPartes = SendDlgItemMessage(hwnd, ID_STATUS, SB_GETPARTS, 10, anchura));
		free(anchura);

Manejar texto

En cada parte de una ventana de estado se puede modificar el texto o recuperarlo.

Para modificarlo se usa el mensaje SB_SETTEXT. En el parámetro wParam se indica el índice, empezando en cero, de la parte en que se quiere modificar el texto, combinado con el tipo de texto a mostrar. Ese tipo puede ser 0, que indica que se trace un borde hundido, SBT_POPOUT que indica un borde sobresaliente, SBT_NOBORDERS que indica que no se tracen bordes, SBT_OWNERDRAW para ventanas de estado owner-draw o SBT_RTLREADING para lenguajes que se escriben de derecha a izquierda.

En lParam se pasa un puntero a la cadena terminada con cero que se debe mostrar, o un valor entero de 32 bits, en caso de ventanas de estado owner-draw.

    SendDlgItemMessage(hwnd, ID_STATUS, SB_SETTEXT, 3|SBT_POPOUT, (LPARAM)"\tbloq num");

Por otra parte, podemos usar caracteres tabuladores ('\t') para modificar la posición del texto. Si no se usa ninguno, la cadena se alinéa a la izquierda de la parte indicada. El texto a la derecha de un tabulador se muestra centrado en la parte indicada, y el texto después del segundo tabulador se muestra alineado a la derecha.

Para recuperar texto desde una parte de una ventana de estado usaremos el mensaje SB_GETTEXT, y para calcular la longitud del texto en una parte, SB_GETTEXTLENGTH.

En el mensaje SB_GETTEXT indicaremos en el parámetro wParam el índice, basado en cero, de la parte cuyo texto queremos recuperar y en lParam un puntero a un buffer que recibirá la cadena. El valor de retorno indicará el tipo de borde usado para trazar el texto en la palabra de mayor peso, y la longitud de la cadena recuperada en la de menor peso.

En el mensaje SB_GETTEXTLENGTH sólo usaremos el parámetro wParam para indicar el índice de la parte. El valor de retorno será similar al del mensaje SB_GETTEXT.

    char *txt;
    int len;
...
           len = LOWORD(SendDlgItemMessage(hwnd, ID_STATUS, SB_GETTEXTLENGTH, 3, 0));
           txt = (char*)malloc(len+1);
           SendDlgItemMessage(hwnd, ID_STATUS, SB_GETTEXT, 3, (LPARAM)txt);
...
           free(txt);

Si se trata de una ventana de estado con un única parte, se pueden usar los mensajes WM_SETTEXT, WM_GETTEXT y WM_GETTEXTLENGTH, tratando la ventana de estado como un simple control de texto estático.

Finalmente, hay otra posibilidad de mostrar texto en una ventana de estado sin necesidad de crearla, usando la función DrawStatusText. Aunque en realidad lo que muestra se parece más a un control de texto estático.

El primer parámetro es un manipulador del DC de la ventana, el segundo un puntero a una estructrua RECT con las coordenadas de la zona ocupada por el texto. Este rectángulo se usa para mostrar los bordes y la posición del texto. El tercer parámetro es el texto a mostrar, los tabuladores funcionan de forma similar a como lo hacen en el mensaje SB_SETTEXT. El cuarto parámetro indica el tipo de bordes a trazar.

    HDC hdc;
    RECT re;
	
    hdc = GetDC(hwnd);
    re.left = 20; re.top = 10;
    re.right = 150; re.bottom = 30;
    DrawStatusText(hdc, &re, "\tTexto estático", SBT_POPOUT);
    ReleaseDC(hwnd, hdc);

Ejemplo 84


  Nombre Fichero Fecha Tamaño Contador Descarga
D Ejemplo 84 win084.zip 2012-06-15 2443 bytes 102

Ventanas de estado owner-draw

Cada una de las partes de una ventana de estado se puede definir como owner-draw. Esto proporciona un mayor control sobre los contenidos de esas partes, ya que nos permite incluir mapas de bits, o en general, cualquier gráfico GDI que queramos, en lugar de sólo texto.

Para definir una parte como owner-draw basta con enviar un mensaje SB_SETTEXT, añadiendo el tipo SBT_OWNERDRAW al identificador de la parte, en el parámetro wParam, y usando el parámetro lParam para un valor de 32 bits que posteriormente se usará para dibujar el contenido de la parte. Ese parámetro puede ser un puntero a una estructura, un mapa de bits, un entero, etc. El significado del valor queda definido por la aplicación, es decir, por nosotros.

        SendDlgItemMessage(hwnd, ID_STATUS, SB_SETTEXT, 2|SBT_OWNERDRAW, lParam);
...
        SendDlgItemMessage(hwnd, ID_STATUS, SB_SETTEXT, 3|SBT_OWNERDRAW, (LPARAM)hBitmapSi);

Cada vez que la aplicación necesita actualizar el contenido de una parte owner-draw de una ventana de estado, se envía un mensaje WM_DRAWITEM a la ventana padre. El parámetro wParam del mensaje contiene el identificador de ventana de la ventana de estado, y el parámetro lParam es un puntero a una estructura DRAWITEMSTRUCT. Toda la información necesaria para dibujar el contenido de la parte está incluida en esta estructura.

Cuando el mensaje WM_DRAWITEM es recibido para mostrar una parte de una barra de estado owner-draw el significado de algunos campos de la estructura DRAWITEMSTRUCT es algo diferente de la que se explica en la documentación:

Miembro Descripción
CtlType No definido, no se usa.
CtlID Identificador de la barra de estado.
itemID Índice de la parte a dibujar.
itemAction No definido, no se usa.
itemState No definido, no se usa.
hwndItem Manipulador de la ventana de estado.
hDC Manipulador del contexto de dispositivo de la ventana de estado.
rcItem Coordenadas de la parte de la ventana a dibujar. Estas coordenadas son relativas a la esquina superior izquierda de la ventana de estado.
itemData Valor de 32 bits definido por la aplicación especificado mediante el parámetro lParam del mensaje {wm:SB_SETTEXT.
    DRAWITEMSTRUCT *dis;
    HDC memDC;
    POINT ptCur;
    char cad[40];
...
        case WM_DRAWITEM:
           dis = (DRAWITEMSTRUCT*)lParam;
           switch(dis->itemID) {
             case 2:
               memDC = CreateCompatibleDC(dis->hDC);
               SelectObject(memDC, hBitmapCoor);
               ptCur.x = LOWORD(dis->itemData);
               ptCur.y = HIWORD(dis->itemData);
               sprintf(cad, "%d,%d", ptCur.x, ptCur.y);
               SetBkMode(dis->hDC, TRANSPARENT);
               TextOut(dis->hDC, dis->rcItem.left+20, dis->rcItem.top, cad, strlen(cad));
               BitBlt(dis->hDC, dis->rcItem.left, dis->rcItem.top, 16, 16, memDC, 0, 0, SRCCOPY);
               DeleteDC(memDC);
               break;
             case 3:
               memDC = CreateCompatibleDC(dis->hDC);
               SelectObject(memDC, (HBITMAP)dis->itemData);
               BitBlt(dis->hDC, dis->rcItem.left, dis->rcItem.top, 16, 16, memDC, 0, 0, SRCCOPY);
               DeleteDC(memDC);
               break;
             default:
               break;
           }
           break;

En este ejemplo vemos cómo utilizamos los valores de la estructrua DRAWITEMSTRUCT para discriminar la parte a dibujar, seleccionar el DC de la ventana de salida, obtener las coordenadas y el valor del parámetro. En el caso de la parte 2, el parámetro son las coordenadas del ratón, en la palabra de menor peso la coordenada x y en la de mayor peso la coordenada y. En el caso de la parte 3, lParam contiene un manipulador de mapa de bits.

Ejemplo 85


  Nombre Fichero Fecha Tamaño Contador Descarga
D Ejemplo 85 win085.zip 2012-06-15 3502 bytes 91

Ventanas de estado simples

Siempre se puede cambiar el tipo de ventana de estado al modo simple (con una única parte), mendiante un mensaje SB_SIMPLE. El texto asignado a la ventana de estado simple se mantiene almacenado de forma independiente del de las partes cuando no está en modo simple, de modo que se puede cambiar de un modo a otro sin que se pierdan los contenidos de ninguna parte. Es habitual que los textos de ayuda de los menús se muestren en modo simple, y una vez el menú ha perdido el foco, se vuelva al modo no simple, sin necesidad de actualizar cada parte.

La única limitación a tener en cuenta en ventanas de estado simples es que no adminten el modo owner-draw.