Esempio: app_two

La Common File Dialog

Il primo passo per aprire e salvare i files è conoscerne il nome… ovviamente potete sempre inserire il nome all’interno del programma, ma onestamente non è una pratica molto utile la maggiorparte delle volte. Siccome è un’operazione molto comune, ci sono delle dialog di sistema predefinite, che permettono all’utente di selezionare un file. Le dialog più comuni per aprire e salvare i files è si ottengono chiamando rispettivamente GetOpenFileName() e GetSaveFileName(). Entrambi lavorano su una struttura OPENFILENAME

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

    ZeroMemory(&ofn, sizeof(ofn));

    ofn.lStructSize = sizeof(ofn); // GUARDATE LA NOTA SOTTO
    ofn.hwndOwner = hwnd;
    ofn.lpstrFilter =
                "Files di testo (*.txt)\\0*.txt\\0Tutti i Files (*.*)\\0*.*\\0";
    ofn.lpstrFile = szFileName;
    ofn.nMaxFile = MAX_PATH;
    ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
    ofn.lpstrDefExt = "txt";

    if(GetOpenFileName(&ofn))
    {
        // Fai qualcosa di utile con il file contenuto in szFileName
    }

Notate la chiamata a ZeroMemory() sulla struttura, per inizializzarla a 0. Questa è una pratica salutare e diffusa per il vostro codice, per non incorrere mai in errori da parte della funzione chiamata. Alcuni valori inutilizzati in questo modo sono settati automaticamente a NULL.

Potete trovare semplicemente il significato di ognuno dei vari membri guardando all’interno della vostra documentazione. Il valore lpstrFilter punta ad una stringa doppiamente terminata con NULL. E potete vedere dall’esempio che ci sono diversi "" all’interno di essa, includo quello alla fine… Il compilatore ne aggiungerà un secondo alla fine come sempre fa per le stringhe costanti (questo è il motivo per cui generalmente non c’è bisogno di inserirli a mano). Il NULL all’interno della stringa suddivide i filtri, ognuno di esso è composto di due parti. Il primo filtro ha la descrizione "Files di testo (*.txt)", e la wildcard non è necessaria, viene generalmente inserita per far rendere conto l’utente dell’estensione del file che si va a cercare. La parte successiva è la wildcard vera e propria su cui verrà applicato il filtro. Facciamo la stessa cosa anche per l’altro filtro, l’unica differenza è che questo è un filtro generico per tutti i files. Potete aggiungere quanti filtri volete.

Il valore lpstrFile punta al buffer che abbiamo allocato per ricevere il nome del file, siccome il esso non può essere più lungo di MAX_PATH, questo è proprio il valore che ho scelto per la sua grandezza. Le flag indicano che la dialog deve permettere all’utente solamente di inserire un nome di file che già esiste (dato che vogliamo aprirne uno, non crearlo) e di nascondere l’opzione per aprire il file in modo di sola lettura. Non supportiamo infatti questa opzione all’interno di questo esempio. Infine gli forniamo una estensione di default, in questo modo se l’utente scrive "foo" ed il file non è trovato, esso proverà ad aprire "foo.txt" prima di restituire un errore.

Per selezionare un file da salvare invece che da aprire, il codice è praticamente lo stesso, eccetto per la chiamata GetSaveFileName(). Dobbiamo anche cambiare le flag adatte per salvare.

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

In questo caso infatti non vogliamo più che il file esista, ma vogliamo che la directory esista, dato che non andremo a crearla noi. Chiederemo inoltre all’utente in caso egli selezioni un file già esistente se desidera sovrascriverlo.

NOTA: MSDN definisce le seguenti regole per il membro lStructSize:

  • lStructSize specifica la lunghezza, in bytes, della struttura
  • Windows NT 4.0: in una applicazione compilata con WINVER e _WIN32_WINNT >= 0x0500, usare OPENFILENAME_SIZE_VERSION_400 per questo membro.
  • Windows 2000/XP: Usare sizeof (OPENFILENAME) per questo parametro.

Sostanzialmente questo significa che in Windows 2000 hanno aggiunto alcuni membri alla struttura, e quindi la sua grandezza è cambiata. Se il codice sopra non funziona, possibilmente è dovuto al fatto che la grandezza usata dal vostro compilatore, e quella che il sistema (es. Windows 98, Windows NT4) si aspettava sono differenti. e la chiamata è fallita. Se accade questo provate ad usare OPENFILENAME_SIZE_VERSION_400 invece di sizeof(ofn). Un ringraziamento alle persone che mi hanno fatto notare questa cosa.

Lettura e scrittura dei files.

In windows ci sono diverse opzioni, a seconda di come si desidera accedere al file. Potete usare il vecchio io.h open()/read()/write(), oppure potete usare stdio.h fopen()/fread()/fwrite(), e in C++ potete usare gli iostreams.

In ogni caso, in windows, tutti questi metodi alla fine chiamano l’API Win32, che sarà quella che useremo qui. Se vi trovate meglio ad usare un altro metodo di IO con i files potete effettuare le modifiche al codice in modo abbastanza semplice.

Per aprire i files, potete usare OpenFile() oppure CreateFile(). MS raccomanda il solo uso di CreateFile() dato che OpenFile() è ora considerato “obsoleto”. CreateFile() è una funzione molto più versatile e permetto un più ampio controllo sul modo in cui si aprono i files.

Lettura

Diciamo per esempio che avete permesso all’utente di selezionare un file 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; // Aggiunge NULL
                    if(SetWindowText(hEdit, pszFileText))
                        bSuccess = TRUE; // Ha funzionato!
                }
                GlobalFree(pszFileText);
            }
        }
        CloseHandle(hFile);
    }
    return bSuccess;
}

C’e’ anche una funzione completa per leggere un file di testo ed inserirlo all’interno di un controllo. Prende come parametro l’handle dell’edit control e il nome del file da leggere. Questa funzione particolare ha però pochi controlli sugli errori e l’IO sui files è una cosa dove un sacco di cose possono andare storte e quindi bisogna stare attenti agli errori. Notate la variabile dwRead. Non la usiamo eccetto come parametro in ReadFile(). Questo parametro DEVE essere fornito, e la chiamata fallisce senza di esso.

Nella chiamata a CreateFile() GENERIC_READ vuol dire che vogliamo solo l’accesso in lettura. FILE_SHARE_READ vuol dire che va bene se altri programmi aprono il file nello stesso momento in cui lo facciamo noi. Ma SOLAMENTE se vogliono vogliono aprirlo in lettura. Non vogliamo che essi ci scrivano dentro mentre noi stiamo leggendo. Infine OPEN_EXISTING vuol dire che deve aprire il file solamente se esiste, non deve crearlo né sovrascriverlo.

Dopo aver aperto il file e controllato che CreateFile() sia andato a buon fine, possiamo controllarne la grandezza cosiì sapremo quanta memoria avremo bisogno di allocare per leggerlo completamente. Dopodiché allochiamo la memoria, controlliamo che sia stata allocata correttamente, dopodiché chiamiamo ReadFile() per caricarne i contenuti dal disco al nostro buffer di memoria. La funzione API non ha nessun concetto di Files di testo, quindi non farà cose del tipo leggere una singola linea di testo, oppure aggiungere NULL alla fine della stringa. Questo è il motivo per cui abbiamo allocato un byte extra e dopo aver letto il file gli aggiungiamo il NULL a mano, in modo che possiamo passare il buffer come stringa a SetWindowText()

Quando tutto è andato bene diciamo alla funzione di restituire TRUE al chiamante, non prima però di aver liberato il buffer di memoria ed aver chiuso l’handle del file.

Scrittura

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;
}

Molto simile a quella per leggere i files, la funzione per scriverli ha pochi cambiamenti. Prima di tutto Quando chiamiamo CreateFile() specifichiamo di volere l’accesso il lettura, che il file deve sempre essere creato nuovo (e se esiste deve essere cancellato prima di essere aperto) e che se non esiste, deve essere creato con attributi normali.

In seguito ci ricaviamo la grandezza del buffer di memoria necessario dall’edit control, dato che rappresenta la sorgente dei dati. Dopo aver allocato la memoria, richiediamo la stringa dall’edit control usando GetWindowText() dopodiché lo scriviamo nel file con WriteFile(). Di nuovo, come con ReadFile() non usiamo il parametro che ritorna quanto è stato scritto.