[ contenidos | #winprog ]

Aplicación Parte 2: Uso de archivos y Diálogos Comunes

Ejemplo: app_two

[images/app_two.jpg]

Diálogos Comunes para archivos

El primer paso para abrir o guardar archivos es obtener el nombre del archivo a usar... aunque, simplemente podríamos poner el nombre del archivo en el código de nuestro programa, pero honestamente esto no es muy útil en la mayoría de los programas.

Debido a que esto es una tarea bastante común, existen diálogos predefinidos por el sistema que pueden ser usados para permitir al usuario seleccionar el nombre de un archivo. Los diálogos mas comunes, para abrir y para guardar archivos, son accedidos a través de GetOpenFileName( ) and GetSaveFileName( ) respectivamente, ambos de los cuales utilizan una estructura OPENFILENAME.

    OPENFILENAME ofn;
    char szFileName[MAX_PATH] = "";

    ZeroMemory(&ofn, sizeof(ofn));

    ofn.lStructSize = sizeof(ofn); // Mira la nota a continuación
    ofn.hwndOwner = hwnd;
    ofn.lpstrFilter = "Text Files (*.txt)\0*.txt\0All Files (*.*)\0*.*\0";
    ofn.lpstrFile = szFileName;
    ofn.nMaxFile = MAX_PATH;
    ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
    ofn.lpstrDefExt = "txt";

    if(GetOpenFileName(&ofn))
    {
        // Do something usefull with the filename stored in szFileName 
    }

Observa que en la estructura llamamos a ZeroMemory( ) para inicializarla a 0. Esto es generalmente una práctica prudente, dado que muchas APIs suelen asignar los campos que no utilizamos con NULL y de esta forma no necesitamos asignar cada miebro que no utilizamos.

Puedes encontrar fácilmente el significado de cada uno de los campos de la estructura en la documentación. El campo lpstrFilter apunta a un string doblemente terminado en NULL, puedes ver en el ejemplo que hay varios "\0" a través de éste, incluyendo uno al final... el compilador agregará el segundo al final, como siempre lo hace con los strings constantes (esto es por lo que generalmente no necesitas ponerlos tu mismo). Los NULLs en este string lo dividen en filtros, donde cada uno tiene dos partes. El primer filtro tiene la descripción "Text Files (*.txt)", la parte con la especificación (*.txt) no es necesaria aquí. La segunda parte es la especificación real del primer filtro, "*.txt". Hacemos lo mismo con el segundo filtro, excepto que es un filtro genérico para todos los archivos. Podemos agregar tantos filtros como deseemos.

El campo lpstrFile apunta al búffer que hemos asignado para almacenar el nombre del archivo, debido a que la longitud del nombre del archivo no puede ser mayor que MAXPATH, éste es el valor que escogeremos para el tamaño de dicho búffer.

Los flags indican que el diálogo sólo debe permitir al usuario ingresar nombres de archivos que ya existen (esto es porque queremos abrirlos, no crearlos) y para ocultar la opción de abrirlos en modo de sólo lectura, opción que no queremos proveer. Finalmente proveemos una extensión por default, por lo tanto si el usuario tipea "foo" y el archivo no es encontrado, intentaremos abrir "foo.txt".

Cuando seleccionamos un archivo para guardar en lugar de abrir, el código es bastante parecido, excepto que llamamos a GetSaveFile( ) y además necesitamos cambiar los flags para poner opciones mas apropiadas al guardar archivos.

    ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT;

En este caso no necesitamos que el archivo exista, pero si necesitamos que el directorio exista debido a que no vamos a crearlo si no existe. Además, si el archivo existe, preguntamos al usuario si está seguro que desea sobreescribirlo.

NOTA: MSDN establece lo siguiente para el campo lStructSize:

lStructSize
Especifica la longitud de la estructura en bytes.

Windows NT 4.0: En una aplicación que es compilada con WINVER y _WIN32_WINNT >= 0x0500, usar para este campo el valor OPENFILENAME_SIZE_VERSION_400.

Windows 2000/XP: Usar para este parámetro el valor OPENFILENAME.

Básicamente, esto significa que, como Windows 2000 ha agregado algunos miembros a esta estructura, su tamaño ha cambiado. Si el código anterior no funciona, posiblemente sea porque probablemente no coincidan el tamaño que tu compilador usa y el tamaño que tu sistema operativo espera (Windows 98, Windows NT4), por lo tanto la llamada falla. Si esto sucede, intenta usar OPENFILENAME_SIZE_VERSION_400 en lugar de sizeof(ofn). Gracias a las personas que me han notificado esto.

Lectura y Escritura de Archivos

En windows tenemos algunas opcciones para especificar de que manera queremos acceder a los archivos. Podemos usar la vieja librería io.h open()/read()/write(), o podemos usar la librería stdio.h fopen()/fread()/fwrite(), y por último si usamos C++ podemos usar iostreams.

Sin embargo, en Windows, todos éstos métodos finalmente llaman a las funciones de la API Win32, las cuales usaremos aquí. Si estás utilizando otro método para la entrada/salida de archivos, entonces te resultará fácil entender el método que vamos a ver.

Para abrir archivos, podemos usar OpenFile( ) o CreateFile( ). MS recomienda usar solamente CreateFile( ) debido a que OpenFile( ) es "obsoleta". CreateFile( ) es una funcióm mucho más versátil y provee un gran control sobre la forma en que abrimos nuestros archivos.

Lectura

Digamos, por ejemplo, que le has permitido al usuario seleccionar un archivo usando GetOpenFileName( )...

BOOL LoadTextFileToEdit(HWND hEdit, LPCTSTR pszFileName)
{
    HANDLE hFile;
    BOOL bSuccess = FALSE;

    hFile = CreateFile(pszFileName, GENERIC_READ, FILE_SHARE_READ, NULL,
        OPEN_EXISTING, 0, NULL);
    if(hFile != INVALID_HANDLE_VALUE)
    {
        DWORD dwFileSize;

        dwFileSize = GetFileSize(hFile, NULL);
        if(dwFileSize != 0xFFFFFFFF)
        {
            LPSTR pszFileText;

            pszFileText = GlobalAlloc(GPTR, dwFileSize + 1);
            if(pszFileText != NULL)
            {
                DWORD dwRead;

                if(ReadFile(hFile, pszFileText, dwFileSize, &dwRead, NULL))
                {
                    pszFileText[dwFileSize] = 0; // Add null terminator
                    if(SetWindowText(hEdit, pszFileText))
                        bSuccess = TRUE; // funcionó!
                }
                GlobalFree(pszFileText);
            }
        }
        CloseHandle(hFile);
    }
    return bSuccess;
}

Hay una función completa para leer un archivo de texto dentro de un control edit. Esta función, toma como parámetro el handle al control edit y el nombre del archivo a leer. Además esta particular función tiene un buen chequeo de errores, debido a que la entrada/salida de archivos está expuesta a una gran cantidad de ellos y por lo tanto, necesitamos chequearlos.

Nota: No usaremos la variable dwRead, excepto como prámetro en ReadFile( ). Este parámetro DEBE ser provisto, de no ser así la llamada fallará.

En la llamada a CreateFile( ), GENERIC_READ significa que queremos acceso de sólo lectura. FILE_SHARE_READ significa que otros programas pueden abrir el archivo al mismo tiempo que nosotros lo hacemos, pero sólo si quieren leerlo, no queremos que escriban en el archivo mientras estamos leyendo. Por último, OPEN_EXISTING significa que sólo abriremos el archivo si existe, no queremos crearlo.

Una vez que hemos abierto el archivo y hemos chequeado si CreateFile( ) se ejecutó con éxito, chequeamos el tamaño del archivo para averiguar cuanta memoria necesitamos reservar para poder leer el archivo entero. Cuando reservamos dicha memoria, chequeamos para asegurarnos que se hizo con éxito, y luego llamamos a ReadFile( ) para cargar el contenido del disco dentro del búffer en memoria. Las funciones de la API para archivos no entienden sobre Archivos de Texto, por lo tanto no proveen mecanismos para leer una linea de texto, o agregar terminadores NULL al final de nuestros strings. Esta es la razón por la que hemos asignado un byte extra, después de leer el archivo agregamos el terminador NULL para que luego podamos pasar el búffer de memoria como si fuera un string a SetWindowsText( ).

Una vez que todo esto se ha ejecutado con éxito, ponemos en nuestra variable booleana bSucces (que indica si la operación se ha ejecutado con éxito) el valor TRUE. Por último, liberamos el búffer de memoria y cerramos el archivo.

Escritura

BOOL SaveTextFileFromEdit(HWND hEdit, LPCTSTR pszFileName)
{
    HANDLE hFile;
    BOOL bSuccess = FALSE;

    hFile = CreateFile(pszFileName, GENERIC_WRITE, 0, NULL,
        CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if(hFile != INVALID_HANDLE_VALUE)
    {
        DWORD dwTextLength;

        dwTextLength = GetWindowTextLength(hEdit);
        // No need to bother if there's no text.
        if(dwTextLength > 0)
        {
            LPSTR pszText;
            DWORD dwBufferSize = dwTextLength + 1;

            pszText = GlobalAlloc(GPTR, dwBufferSize);
            if(pszText != NULL)
            {
                if(GetWindowText(hEdit, pszText, dwBufferSize))
                {
                    DWORD dwWritten;

                    if(WriteFile(hFile, pszText, dwTextLength, &dwWritten, NULL))
                        bSuccess = TRUE;
                }
                GlobalFree(pszText);
            }
        }
        CloseHandle(hFile);
    }
    return bSuccess;
}

Es muy similar a leer archivos, pero con algunos cambios. Primero que todo, cuando llamamos a CreateFile( ) especificamos que queremos acceso de lectura (Read), que el archivo debería simpre ser creado nuevamente (y si existe debería ser borrado cuando es abierto) y si no existe que sea creado con los atributos de los archivos normales.

A continuación obtenemos del control edit, la longitud necesaria del búffer de memoria, debido a que éste es la fuente de los datos. Una vez que hemos asignado la memoria, solicitamos el string al control edit usando GetWindowText( ) y luego lo escribimos al achivo con WriteFile( ). Nuevamnete, al igual que con ReadFile( ), es necesario el parámetro que retorna cuanto fué escrito, aún cuando no lo usemos.


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

Versión en Español: FedericoPizarro - 2003