Salvare file in VBA: FileDialog Vs GetSaveAsFilename

Nell’articolo Usare l’oggetto FileDialog in VBA di Excel ci siamo concentrati su tutte le possibili declinazioni dell’oggetto FileDialog, tuttavia abbiamo anche preso coscienza del limite che affligge il FileDialog di tipo msoFileDialogSaveAs, ovvero l’impossibilità di cambiare i filtri di Default. Ecco perché in questo articolo vedremo come salvare un file in VBA con il metodo Application.GetSaveAsFilename e impostare liberamente i filtri che ci servono!

Ognuno il suo… punto di forza.

Il FileDialog msoFileDialogSaveAs è indubbiamente un oggetto solido e funzionale, tanto più che la caratteristica che apprezzo maggiormente è quella di delegare al Sistema il controllo dell’esistenza del File indicato nella finestra di dialogo.

Tuttavia, sebbene questo dettaglio da solo, molte volte sia bastato a farmelo preferire, è chiaro che l’impossibilità di gestire i filtri in autonomia rende questo oggetto poco flessibile.

E’ vero, c’è la possibilità di impostare tramite la FilterIndex un filtro tra quelli predefiniti, ma ci sono almeno due ipotesi in cui quella soluzione potrebbe non andar bene:

  • il tipo file che abbiamo intenzione di salvare non è compreso tra quelli di default;
  • essendo quella una lista basata principalmente sul sistema in uso, impostare FilterIndex = 19 per salvare un file di tipo .txt, potrebbe non trovare corrispondenza su un’installazione o un Computer differente.

Per scavalcare questo ostacolo e riprenderci il controllo totale sui filtri, dobbiamo cambiare strumento e bussare alla porta del metodo Application.GetSaveAsFilename.

Application.GetSaveAsFilename: soluzione ideale, con un aiutino.

A differenza del FileDialog, che è un oggetto complesso ricco di proprietà e metodi, GetSaveAsFilename è un semplice metodo dell’oggetto Application, che si limita a mostrare la finestra di dialogo e a restituire, se correttamente selezionato, direttamente il nome del File.
Nel nostro caso, il suo punto di forza risiede proprio nella proprietà FileFilter, che accetta una stringa d’impostazione liberamente personalizzabile, esattamente come volevamo.

Questa libertà, però, ha un prezzo che è quello di perdere le funzioni native del FileDialog:

  • il controllo di esistenza del file selezionato, per evitare sovrascritture indesiderate, probabilmente quella più utile;
  • l’apposizione di un “punto fantasma” alla fine del nome file, nel caso in cui si tralasci di impostare un filtro specifico.

La buona notizie è che, come vedremo, possiamo porre rimedio in modo indolore ad entrambi i problemi.

La strategia: un paracadute e un filtro di salvataggio.

La strategia che ritengo più sicura è quella di “incapsulare” la GetSaveAsFilename in una procedura personalizzata, che abbia dei parametri in ingresso e che compia su quei parametri dei controlli di coerenza, per restituire tramite una semplice riga di codice, un nome di file pulito e sicuramente utilizzabile.

Per restituire alla nostra procedura la stessa sicurezza del FileDialog nativo, la strategia si articola su due fronti:

1. Il controllo di sovrascrittura.

Prima di confermare il percorso, intercettiamo se il file esiste già sul disco. Per farlo in modo semplice e riutilizzabile, ci affideremo a una semplice routine d’appoggio, che chiameremo FileExists.

Function FileExists(sFileName As String) As Boolean
    FileExists = Not (Dir(sFileName) = vbNullString)
End Function

La Function ci restituirà un valore Vero/Falso che useremo nella procedura principale per determinare se il file sia presente o meno. In caso affermativo mostreremo un avviso esplicito all’utente, offrendogli la scelta di procedere alla sovrascrittura o di tornare alla finestra di dialogo per cambiare nome.

2. Il “punto fantasma”

Facendo dei test, durante la scrittura della procedura, mi sono accorto di un comportamento nativo di GetSaveAsFilename piuttosto insidioso: se non viene specificato alcun filtro (lasciando il parametro a vbNullString oppure omettendolo), il sistema imposta automaticamente il filtro generico (*.*).

Questo automatismo fa sì che, se l’utente omette l’estensione, il percorso restituito termini con un punto isolato (ad esempio Nome_Del_File.).

Dato che questo comportamento inaspettato, potrebbe portare problemi nel successivo salvataggio e nella gestione del file, per disinnescare questa anomalia, la nostra funzione imposterà un cosiddetto “fallback”, ovvero un ripiego esplicito su un generico file di testo (*.txt).

In questo modo, se chi richiama la funzione si dimentica di passare i filtri personalizzati, la procedura imposterà automaticamente un filtro standard e sicuro, aggirando il bug del punto in coda e permettendo comunque all’utente di specificare l’estensione desiderata.

Una scatola per la GetSaveAsFilename: la funzione GetFileNameByDialog

Passiamo dunque a racchiudere la GetSaveAsFilename in una “black box”.

I parametri in ingresso sono tutti opzionali e ricalcano in modo praticamente identico quelli del metodo standard: possiamo passare una cartella o un nome iniziale (InitPath e InitFileName), titolo della finestra (DialogTitle) e la stringa con i nostri filtri personalizzati (FileFilter).

All’interno della routine, un ciclo Do…Loop si occuperà di gestire l’interazione con l’utente, bloccandolo nella finestra di dialogo finché non compie una scelta sicura (confermare un file non esistente, accettare esplicitamente una sovrascrittura, oppure annullare l’operazione).

Function GetFileNameByDialog(Optional InitPath As String, _
                             Optional InitFileName As String, _
                             Optional DialogTitle As String, _
                             Optional Filters As Variant) As String

    Dim NewFileName As Variant
    Dim retval As Integer
    
    InitPath = InitPath & IIf(Right(InitPath, 1) <> "\", "\", vbNullString)
    
    If IsMissing(Filters) Then Filters = vbNullString
    If Filters = vbNullString Then Filters = "File di testo (*.txt), *.txt"
    
    Do
        NewFileName = Application.GetSaveAsFilename( _
            Title:=DialogTitle, _
            FileFilter:=Filters, _
            InitialFileName:=InitPath & InitFileName)
            
        If Not NewFileName = False Then    '<--- se non è stato premuto annulla
            If FileExists(CStr(NewFileName)) Then    '<--- controllo che il file non sia esistente
                retval = MsgBox("Il file è già esistente! Sovrascriverlo?", _
                                 vbExclamation Or vbYesNo, _
                                 IIf(DialogTitle <> "", DialogTitle, "Salva con nome"))
                'dopo il MsgBox retval può essere:
                ' - vbYes -> Sovrascrittura
                ' - vbNo -> viene riproposto il DialogBox
                If retval = vbNo Then
                    'Recupero il nuovo percorso e il nome file impostati dall'utente
                    'per riproporli alla nuova visualizzazione del DialogBox
                    InitPath = Left(NewFileName, InStrRev(NewFileName, "\"))
                    InitFileName = Mid(NewFileName, InStrRev(NewFileName, "\") + 1)
                End If
            Else
                retval = vbYes  '<--- se non esiste, viene impostato vbYes che permette di proseguire
            End If
        Else
            Exit Function '<--- se è stato premuto annulla si esce
        End If
    Loop Until retval = vbYes '<--- se non esiste o si può sovrascrivere, andiamo avanti
            
    GetFileNameByDialog = NewFileName

End Function

Nota: se analizzando il codice avete notato che la variabile NewFileName è stata dichiarata come Variant, nonostante il suo scopo finale sia quello di ospitare il percorso di un file, ovvero un dato di tipo String, la spiegazione è molto semplice e risiede nel comportamento “ibrido” del metodo Application.GetSaveAsFilename.

La finestra “Salva con nome…”, quando viene premuto il tasto “Salva” restituisce una stringa col nome del File, ma se l’utente preme Annulla restituisce il valore booleano False. Questo significa che se utilizzassimo una variabile di tipo String per testare questo valore, al momento esatto della chiusura della finestra di dialogo otterremmo un errore di runtime (Tipo non corrispondente). L’uso del Variant intercetta in modo sicuro entrambe le situazioni.

Due istruzioni per un file sicuro.

Grazie a questa procedura, ogni volta che nel nostro progetto avremo bisogno di salvare un file, possiamo farlo in tutta tranquillità, avendo a disposizione l’aiuto di una finestra standard di Windows, con tutte le comodità del caso: filtri personalizzati e controllo di esistenza del file, per evitare sovrascritture indesiderate.

Usarla, è questione di un paio di righe di codice, magari associate all’evento Click di un pulsante:

Sub ComandoSalva_Click()
    Dim FileDaSalvare As String

    ' Richiamo la funzione personalizzata passando solo i filtri che mi interessano
    FileDaSalvare = GetFileNameByDialog(Filters:="File Excel (*.xlsx), *.xlsx")

    ' Se la stringa non è vuota, procedo con il salvataggio vero e proprio
    If FileDaSalvare <> vbNullString Then
        ActiveWorkbook.SaveAs FileDaSalvare
    End If

End Sub

Nell’esempio sopra la GetFileNameByDialog è stata chiamata con parametri minimali, solo i filtri, ma possiamo indicare ovviamente tutti i parametri, per personalizzare completamente la finestra di dialogo, dal nome della finestra ai Filtri specifici, anche non standard:

FileDaSalvare = GetFileNameByDialog(InitPath:="C:\Users\Nome_Utente\Desktop\", _
                                    InitFileName:="Esportazione_" & Format(Date, "dd_mm_yyyy"), _
                                    DialogTitle:="Specifica il nome del file...", _
                                    Filters:="File Proprietario (*.xyz), *.xyz")

Come potete notare, il codice per la selezione del file rimane snello, leggibile e, soprattutto, a prova di errori, perché tutta la complessità, tra controlli e riscontri, è “inscatolata” nella Funzione.

Spero che questo approfondimento su come salvare un file in VBA con il metodo Application.GetSaveAsFilename sia utile per rendere le vostre applicazioni più robuste e vicine all’esperienza d’uso standard di Windows.

Vi invito a contattarmi nel caso ci fossero inesattezze nell’articolo o fossero necessarie ulteriori informazioni sull’argomento.

Leave a Comment