[ 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