[ contenidos | #winprog ]

Texto y Fuentes

Ejemplo: font_one

[images/font_one.gif]

Carga de Fuentes

La GDI Win32 tiene algunas capacidades admirables para trabajar con diferentes tipos de estilos de presentación, lenguajes, conjuntos de caracteres etc... Uno de los inconvenientes de esto es que trabajar con fuentes luce algo intimidatorio para el que recién se inicia. CreateFont(), la principal API para trabajar con fuentes tiene 14 parámetros, los cuales especifican el alto, estilo, ancho, familia y otros varios atributos.

Afortunadamente no es tan difícil como parece y una gran parte del trabajo es realizada con valores por default. Todos menos dos de los valores de CreateFont() pueden ser puestos en 0 o en NULL para que el sistema use los valores por default dando como resultado una fuente plana y común.

CreateFont( ) crea un HFONT, un handle a la Fuente Lógica en memoria. Los datos referenciados por este handle pueden ser recuperados dentro de una estrucura LOGFONT usando la operación GetObject( ), de la misma manera que lo hacíamos con los bitmaps donde la estructura BITMAP era rellenada a partir de un HBITMAP. Los miembros de LOGFONT son idénticos a los parámetros de CreateFont() y por conveniencia podemos crear directamente una fuente apartir una estructura LOGFONT ya existente, usando CreateFontIndirect( ). Esto es muy útil debido a que hace mas fácil la tarea de crear una nueva fuente a partir de un handle cuando solo queremos alterar ciertos aspectos de ésta. Para rellenar los campos de la estructura LOGFONT usamos GetObject( ), alteramos los campos que deseamos y creamos una nueva fuente usando CreateFontIndirect( ).

    HFONT hf;
    HDC hdc;
    long lfHeight;
    
    hdc = GetDC(NULL);
    lfHeight = -MulDiv(12, GetDeviceCaps(hdc, LOGPIXELSY), 72);
    ReleaseDC(NULL, hdc);

    hf = CreateFont(lfHeight, 0, 0, 0, 0, TRUE, 0, 0, 0, 0, 0, 0, 0, "Times New Roman");

    if(hf)
    {
        DeleteObject(g_hfFont);
        g_hfFont = hf;
    }
    else
    {
        MessageBox(hwnd, "Font creation failed!", "Error", MB_OK | MB_ICONEXCLAMATION);
    }                   

Este es el código usado para crear la fuente en la imagen ejemplo. La fuente es Times New Roman de 12 puntos con el estilo Itálica El flag para obtener la fuente en itálica es el sexto parámetro de CreateFont( ), el cual se puede apreciar que está puesto en TRUE. El último parámetro indica el nombre de la fuente que queremos usar.

La única parte tramposa de este código es el valor usado para el tamaño de la fuente, el parámetro lfHeight de CreateFont( ). Generalmente las personas, cuando trabajan con fuentes, están acostumbradas a trabajar con tamaños especificados en Puntos, Tamaño 10, Tamaño 12, etc... Sin embargo, CreateFont( ) no acepta tamaños en puntos,solo acepta Unidades Lógicas, las cuales son diferentes en nuestra pantalla que en nuestra impresora y aún entre impresoras y pantallas.

La razón de que exista esta situación es porque la resolución de los diferentes dispositivos es bastante diferente... las impresoras pueden imprimir de 600 a 1200 pixeles por pulgada, mientras que una pantalla con suerte llega a mostrar unos 200. Si usamos el mismo tamaño para la pantalla que para la impresora, quizás no podamos ver algunos caracteres en particular.

Todo lo que tenemos que hacer es convertir el tamaño punto al tamaño lógico para un dispositivo determinado. En este caso el dispositivo es la pantalla, por lo tanto obtenemos el HDC de la pantalla y obtenemos el número de pixeles lógicos por pulgada usando GetDeviceCaps() y metemos esto dentro de la fórmula provista tan generosamene en MSDN la cual usa MulDiv( ) para convertir nuestro tamaño punto, en este caso 12, al tamaño lógico que CreateFont( ) espera. Luego almacenamos este en lfHeight y lo pasamos como primer parámetro de CreateFont( ).

Fuentes por Default

Cuando llamamos por primera vez a GetDC( ) para obtener el HDC de nuestra ventana, la fuente por default que es seleccionada es System, la cual para ser honesto, no es muy atractiva. La forma más simple de obtener una fuente mejor sin tener que pasar por CreateFont() es llamar a GetStockObject() y preguntar por la DEFAULT_GUI_FONT.

Este es un objeto del sistema y podemos usarlo todas las veces que deseamos sin necesidad retener memoria, podemos llamar a DeleteObject() para borrarlo pero esto no causará efecto, lo cual es una buena idea porque no tendremos que averiguar si dicha fuente fué creada con CreateFont( ) o con GetStockObject( ) antes de intentar eliminarla.

Mostrando Texto

Ahora que tenemos una "linda" fuente, ¿como hacemos para mostrar algún texto en la pantalla? Esto es, asumiendo que no queremos usar un control Edit o un control Static.

Las opciones básicas son TextOut( ) y DrawText( ). TextOut() es mas simple pero tiene menos opciones y no hace cubrimientos de palabras o alineación por nosotros.

    char szSize[100];
    char szTitle[] = "These are the dimensions of your client area:";
    HFONT hfOld = SelectObject(hdc, hf);

    SetBkColor(hdc, g_rgbBackground);
    SetTextColor(hdc, g_rgbText);

    if(g_bOpaque)
    {
        SetBkMode(hdc, OPAQUE);
    }
    else
    {
        SetBkMode(hdc, TRANSPARENT);
    }

    DrawText(hdc, szTitle, -1, prc, DT_WORDBREAK);

    wsprintf(szSize, "{%d, %d, %d, %d}", prc->left, prc->top, prc->right, prc->bottom);
    DrawText(hdc, szSize, -1, prc, DT_SINGLELINE | DT_CENTER | DT_VCENTER);

    SelectObject(hdc, hfOld);

La primer cosa que hacemos es usar SelectObject( ) para obtener la fuente que queremos usar dentro de nuestro HDC y para prepararnos para dibujar. Todas las operaciones de texto usarán, de aquí en mas, esta fuente hasta que se seleccione alguna otra.

Ahora vamos a definir los colores del texto y del fondo. Cuando fijamos el color de fondo no cambiamos todo el fondo a un determinado color, solo afecta a ciertas operaciones que dibujan texto usando el color de fondo. Esto también depende del Modo de Fondo (o Background Mode) que esté seleccionado actualmente. Si está definido como OPAQUE (el default) entonces cada vez que se escriba texto, el color de fondo del texto será el que esté seleccionado como background color. Si está definido como TRANSPARENT, el texto es dibujado sin color de fondo y quedará como fondo del texto lo que sea que encuentre dibujado debajo de éste.

Ahora realmente dibujamos el texto usando DrawText( ), pasamos el HDC a usar y el string a dibujar. El tercer parámetro es la longitud del string, pero hemos pasado -1 debido a que DrawText( ) es lo suficientemente astuto para darse cuenta cual es la longitud del texto. En el cuarto parámetro pasamos prc, el puntero al cliente RECT. DrawText( ) dibujará dentro de este rectángulo basándose en los otros flags que le pasamos.

En la primer llamada especificamos DT_WORDBREAK, el cual alinea por default el texto a la izquierda y acomodará el texto automáticamente a los límites del rectángulo... muy útil.

Para la segunda llamada solo imprimimos una sola línea sin ajustar el texto al rectángulo y la centramos horizontalmente y verticalmente (lo cual hará DrawText() cuando dibujemos una sola línea).

Redibujar el Cliente

Sólo una nota sobre el programa ejemplo... cuando WNDCLASS es registrada he puesto los estilos CS_VREDRAW y CS_HREDRAW. Esto provoca que el área cliente entera sea sea redibujada si la ventana cambia su tamaño, mientras que por default solo se dibujan las partes que han cambiado. Esto luce algo malo debido a que el texto centrado es movido cuando la ventana cambia su tamaño pero no se actualiza como esperamos.

Elección de Fuentes

En general cualquier programa que utiliza fuentes le permite al usuario escoger su propia fuente, como también el color y los atributos de estilo a usar.

Al igual que los diálogos comunes para abrir y guardar archivos, hay un diálogo común para escoger una fuente. Este es llamado ChooseFont( ) y funciona junto con la estructura CHOOSEFONT.

HFONT g_hfFont = GetStockObject(DEFAULT_GUI_FONT);
COLORREF g_rgbText = RGB(0, 0, 0);
void DoSelectFont(HWND hwnd)
{
    CHOOSEFONT cf = {sizeof(CHOOSEFONT)};
    LOGFONT lf;

    GetObject(g_hfFont, sizeof(LOGFONT), &lf);

    cf.Flags = CF_EFFECTS | CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS;
    cf.hwndOwner = hwnd;
    cf.lpLogFont = &lf;
    cf.rgbColors = g_rgbText;

    if(ChooseFont(&cf))
    {
        HFONT hf = CreateFontIndirect(&lf);
        if(hf)
        {
            g_hfFont = hf;
        }
        else
        {
            MessageBox(hwnd, "Font creation failed!", "Error", MB_OK | MB_ICONEXCLAMATION);
        }

        g_rgbText = cf.rgbColors;
    }
}

El hwnd en esta llamada es simplemente la ventana que vamos a usar como padre del diálogo.

La forma más fácil de usar este diálogo es junto con una estructura LOGFONT, la cual es bastante parecida a la estructura HFONT que veníamos usando. Hacemos que el campo lpLogFont apunte a LOGFONT, que acabamos de llenar con información y también agregamos el flag CF_INITTOLOGFONTSTRUCT para que ChooseFont( ) use este campo. El flag CF_EFFECTS le dice a ChooseFont( ) que permita al usuario seleccionar un color como así también efectos de subrayado y tachado.

Los estilos Negrita e Itálica no se cuentan como efectos, son considerados partes de la fuente y de hecho algunas fuentes solo vienen en Negrita o en Itálica. Si queremos chequear o prevenir al usuario de seleccionar una fuente negrita o itálica, podemos chequear los campos lfWeight y lfItalic, respectivamente, de la estructura LOGFONT después de que el usuario ha hecho su elección. Luego se le puede preguntar al usuario si desea hacer otra selección o algún cambio a los campos antes de llamar a CreateFontIndirect().

El color de una fuente no está asociado con un HFONT y por lo tanto debe ser almacenado por separado. EL campo rgbColors de la estructura CHOOSEFONT es usado para pasar el color inicial y para recuperar luego el nuevo color.

CF_SCREENFONTS indica que queremos fuentes diseñadas para funcionar en la pantalla y no fuentes diseñadas para funcionar en una impresora. Algunos soportan ambas, otros soportan una u otra. Dependiendo de para que vas a usar la fuente, este y muchos otros flags pueden ser encontrados en MSDN para definir exactamente que fuentes deseamos que el usuario sea capaz de seleccionar.

Elección de colores

Para permitirle al usuario cambiar el color de la fuente, existe el diálogo común ChooseColor( ). Este es el código usado para permitirle al usuario seleccionar el color de fondo en programa ejemplo.
COLORREF g_rgbBackground = RGB(255, 255, 255);
COLORREF g_rgbCustom[16] = {0};
void DoSelectColour(HWND hwnd)
{
    CHOOSECOLOR cc = {sizeof(CHOOSECOLOR)};

    cc.Flags = CC_RGBINIT | CC_FULLOPEN | CC_ANYCOLOR;
    cc.hwndOwner = hwnd;
    cc.rgbResult = g_rgbBackground;
    cc.lpCustColors = g_rgbCustom;

    if(ChooseColor(&cc))
    {
        g_rgbBackground = cc.rgbResult;
    }
}

Esto es algo complicado, nuevamente estamos usando el parámetro hwnd como padre del diálogo. El parámetro CC_RGBINIT indica que debemos comenzar con el color que pasamos en el campo rgbResult, el cual también es donde obtenemos el color que el usuario ha seleccionado cuando el diálogo se cierra.

El arreglo g_rgbCustom de 16 COLORREFs es necesario para almacenar cualquier valor que el usuario decide poner dentro de la tabla de colores personalizados incluída en el diálogo. Podemos potencialmente almacenar estos valores en otro lugar que no sea el registro, porque de no ser así dichos valores se perderán al cerrar el programa. Este parámetro no es opcional.

Cambio de la Fuente en los Controles

Algo que podemos hacer en algún punto es cambiar la fuente de los controles en los diálogos o ventanas. Generalmente este es el caso cuando usamos CreateWindow( ) para crear controles como lo hemos hecho en los ejemplos anteriores. Los controles al igual que las ventanas usan la fuente System por default, por lo tanto usamos WM_SETFONT para definir un nuevo handle a la fuente (de GetStockObject()) para que use el control. Podemos usar este método con fuentes que creamos usando CreateFont(). Simplemente pasamos el handle a la fuente como wParam y ponemos lParam en TRUE para hacer que el control se redibuje.

He realizado esto en los ejemplos anteriores, pero tiene sentido mencionarlo aquí debido a que es relevante y muy breve:

    SendDlgItemMessage(hwnd, IDC_OF_YOUR_CONTROL, WM_SETFONT, (WPARAM)hfFont, TRUE);

Donde hfFont es, por su puesto, el HFONT que queremos usar y IDC_OF_YOUR_CONTROL es el ID del control al cual le queremos cambiar la fuente.


Copyright © 1998-2003, Brook Miles (theForger). All rights reserved.

Versión en Español: FedericoPizarro - 2003