Peltier Tech Blog

Excel Charting Utilities | Custom Solutions | Training | Charts and Tutorials | PTS Blog

 

Main menu:

 
Peltier Tech
Chart Utilities

PTS Waterfall Chart Utility
PTS Cluster-Stack Column Chart Utility
PTS Box and Whisker Chart Utility
PTS Marimekko Chart Utility
PTS Dot Plot Utility
PTS Cascade Chart Utility

Subscribe

Site search

Subscribe

Site search


Recent Posts

Recently Commented

Popular Posts

Archive


 

Categories


 

Buy me a coffee

If this topic or the whole blog has been helpful, support further development by treating me to a cup of coffee.


Privacy Policy

Creative Commons License
Licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License.

Robust VBA Save-As Technique

by Jon Peltier
Monday, December 7th, 2009
Peltier Technical Services, Inc., Copyright © 2010.
Licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License.

One of the most important things a VBA procedure can do is save a file, especially a file that’s been substantially modified by that procedure.

This functionality is even better if the user is given the opportunity to specify a path and file name.  You can simply pop up Excel’s own Save As dialog:

Application.Dialogs(xlDialogSaveAs).Show sFileName

This approach is fine for simple applications, but it saves the file before execution returns to the calling procedure. This may not be what you want to do with the file and file name.

Excel’s VBA object model has a more flexible approach. The GetSaveAsFilename method opens a Save As dialog and gives the user control over directory and file name. GetSaveAsFilename does not save the file, but only retrieves the combined path and file name and returns it to the executing procedure for further use. If the user cancels, the function returns the string “False” instead of a filename.

Below is an implementation of GetSaveAsFilename that I’ve wrapped in a function called GSAFN. The function  takes a file name and path, which are used as the defaults in the Save As dialog.  If the file name entered by the user matches an existing file name, it asks the user what to do: overwrite the existing file, enter another file name, or cancel the save operation. If the user cancels the save, GSAFN returns a zero length string.

Function GSAFN(sFileName As String, sPathName As String) As String
  Dim sFullName As String
  Dim sPrompt As String
  Dim sCurDir As String
  Dim iOverwrite As Long

  If ActiveWorkbook Is Nothing Then GoTo ExitSub

  ' save current directory, restore it later
  sCurDir = CurDir

  ' switch to desired directory
  If Len(sPathName) > 0 Then
    ChDrive sPathName
    ChDir sPathName
  End If

  ' loop until unique name is entered
  Do
    sFullName = Application.GetSaveAsFilename(sFileName, _
        "Excel Files (*.xls),*.xls", , _
        "Browse to a folder and enter a file name")

    If Len(sFullName) = 0 Then GoTo ExitSub
    If sFullName = "False" Then GoTo ExitSub

    ' if name is unique, exit loop and save file
    If Not FileExists(sFullName) Then Exit Do

    ' tell user that the filename is in use

    ' parse filename
    sFileName = FullNameToFileName(sFullName)
    sPathName = FullNameToPath(sFullName)

    ' construct message
    sPrompt = "A file named '" & sFileName & "' already exists in '" _
        & sPathName & "'"
    sPrompt = sPrompt & vbNewLine & vbNewLine & _
        "Do you want to overwrite the existing file?"

    ' ask user what to do
    iOverwrite = MsgBox(sPrompt, vbYesNoCancel + vbQuestion, _
        "Image File Exists")

    Select Case iOverwrite
      Case vbYes
        ' overwrite existing file
        Exit Do
      Case vbNo
        ' do nothing, loop again to get new filename
      Case vbCancel
        ' bail out
        GoTo ExitSub
    End Select
  Loop

  ' finally, save the file using filename from above
  Application.DisplayAlerts = False
  ActiveWorkbook.SaveAs sFullName
  Application.DisplayAlerts = True

  GSAFN = sFullName

ExitSub:
  ' restore previous current directory
  ChDrive sCurDir
  ChDir sCurDir

End Function

The following procedures are used in the main procedure above. The FileExists function is more flexible than simply using Dir, because using it does not reset the initial file spec used in Dir. The FullNameToFileName and FullNameToPath functions parse the combined path and file name into a separate path and file name, making the GSAFN function work more easily. They were originally written for Excel 97, before VBA had the RevInStr string function; don’t laugh at the antiquated syntax, because they still work just fine.

Function FileExists(ByVal FileSpec As String) As Boolean
  ' Karl Peterson, Former MS VB6 MVP
  Dim Attr As Long
  ' Guard against bad FileSpec by ignoring errors
  ' retrieving its attributes.
  On Error Resume Next
  Attr = GetAttr(FileSpec)
  If Err.Number = 0 Then
    ' No error, so something was found.
    ' If Directory attribute set, then not a file.
    FileExists = Not ((Attr And vbDirectory) = vbDirectory)
  End If
End Function

Function FullNameToFileName(sFullName As String) As String
  Dim k As Integer
  Dim sTest As String
  If InStr(1, sFullName, "[") > 0 Then
    k = InStr(1, sFullName, "[")
    sTest = Mid(sFullName, k + 1, InStr(1, sFullName, "]") - k - 1)
  Else
    For k = Len(sFullName) To 1 Step -1
      If Mid(sFullName, k, 1) = "\" Then Exit For
    Next k
    sTest = Mid(sFullName, k + 1, Len(sFullName) - k)
  End If
  FullNameToFileName = sTest
End Function

Function FullNameToPath(sFullName As String) As String
  ''' does not include trailing backslash
  Dim k As Integer
  For k = Len(sFullName) To 1 Step -1
    If Mid(sFullName, k, 1) = "\" Then Exit For
  Next k
  If k < 1 Then
    FullNameToPath = ""
  Else
    FullNameToPath = Mid(sFullName, 1, k - 1)
  End If
End Function

GSAFN is called as follows:

Sub TestGSAFN()
  Dim sFile As String
  sFile = GSAFN("testfile.xls", "C:\Temp")
  If Len(sFile) > 0 Then
    MsgBox "File successfully saved"
  Else
    MsgBox "File was not saved"
  End If
End Sub

GSAFN and the supporting functions are so helpful that I include them in a general Tools module which I drop into each new project I develop.

Related Posts:

Bookmark and share this entry:
  • Twitter
  • Digg
  • Facebook
  • LinkedIn
  • del.icio.us
  • Technorati
  • StumbleUpon
  • Google Bookmarks
  • Reddit
  • MySpace
  • Slashdot
  • Yahoo! Buzz
  • Tumblr

Learn how to create Excel dashboards.

Comments


Pingback from Excel Theatre Blog » Excel Twitters 20091207
Time: Monday, December 7, 2009, 9:31 am

[...] P.S. If you want to save your Excel file with VBA, try the code in Jon Peltier’s article today: Robust VBA Save-As Technique [...]


Comment from JP
Time: Monday, December 7, 2009, 12:07 pm

I think you meant

sFile = GSAFN(“testfile.xls”, “C:\Temp”)

?

Also, any plans to publish that Tools module?


Comment from Jon Peltier
Time: Monday, December 7, 2009, 12:20 pm

Jimmy -

Oops! I do the same thing for GetOpenFileName. I copied the wrong test procedure.

I have a bunch of other things in that module which I could blog about. Maybe I’ll milk it for a few more posts, then present the whole module. I’ve already shared these helper functions in File Name Functions.


Comment from JP
Time: Monday, December 7, 2009, 12:34 pm

Good deal. You might get some downloads if you put everything into a module and exported it as a .bas. Just a thought.


Comment from Bob Flanagan
Time: Monday, December 7, 2009, 11:26 pm

Jon, I think you need one more check: To see if there is already a file open by the name the user specifies:

dim wB as workbook
set wb = nothing
on error resume next
set wb = workbooks(sFileName)
on error goto 0
if not wB is nothing then
Msgbox “A file by the name you specified is already open. Please specify another name”
end if


Comment from Jon Peltier
Time: Tuesday, December 8, 2009, 12:26 am

Thanks, Bob. Good point. Also have to make sure that we allow the file being saved to already have the selected name, so it’s even a bit more intricate. When I get a moment, I’ll tweak the code.


Comment from Jan Karel Pieterse
Time: Tuesday, December 8, 2009, 2:22 am

Does the FileExists function work on UNC paths too?


Comment from Jon Peltier
Time: Tuesday, December 8, 2009, 8:09 am

In a quick test on my office network, I was able to use FileExists to check for a file on another share, using \\share\path\file.ext addressing.


Comment from Bryan
Time: Tuesday, December 22, 2009, 3:27 pm

Hello, the code above seems to very long to achieve such a simple things. I have used the Application.GetSaveAs function, and was also able to specify what the title will be. However, I cannot for the life of me get it to actually save. Isn’t there some code that would save the file as the new file name and type right after the user pressed “save” from the dialog box? I placed and “application.save” in the code so when the user presses save, it saves it, but it doesn’t do what I would like. Any help?


Comment from Jon Peltier
Time: Tuesday, December 22, 2009, 3:55 pm

Bryan -

At the top of the article, I do state that if you don’t need much functionality around the save-as routine, you could use Excel’s built-in Save-As dialog.

GetSaveAsFileName is not designed to save a file, it’s designed to get the save-as file name. This allows the code to do various things before saving, and even skip the save if that’s what the programmer intended. This particular routine checks for duplicate file names, but it could instead automatically overwrite the existing file, or append duplicates with -1, -2, etc., or even append a date .

You might notics a line that says

ActiveWorkbook.SaveAs sFullName

This is the command that actually saves the workbook.


Comment from Bryan
Time: Tuesday, December 22, 2009, 4:04 pm

But, can I used the built in Save-As dialog if i want to specify with the code what the file name will be?


Comment from Jon Peltier
Time: Tuesday, December 22, 2009, 4:10 pm

Bryan -

Do you just want to save the file, or do you want to present the user with a default file name that they can change?

If you’re just saving the file, skip the dialog altogether. To insert a default file name into the Save As dialog, use this syntax:

Application.Dialogs(xlDialogSaveAs).Show arg1:=”C:\my documents\test.xls”

(Thanks to Dave Peterson, whose answer to the same question appeared in several places, including http://www.pcreview.co.uk/forums/thread-966348.php).


Comment from Bryan
Time: Wednesday, December 30, 2009, 6:01 pm

Thank you. This is going through the steps and saving my file, but when i go to re-open the file that was saved, I get a corrupt error saying that the file type is not correct. Here is my code:

Sub SelectSaveFileName()
Dim TheFile As Variant

TheFile = Application.GetSaveAsFilename(Sheets(“Test Report”).Range(“$B$4″).Value & “.xls”, _
“Workbook (*.xls), *.xls”, , “Please save you file:”) ‘ SELECTS THE CELL AS THE FILE NAME

If TheFile = False Then
MsgBox “You cancelled; Your file was NOT saved!”

Else
ActiveWorkbook.SaveAs TheFile
MsgBox “You have saved ” & CStr(TheFile)
End If

End Sub

The file I run my macro on starts as a CSV file, but I want to save as an xls file. Do you see any issues with this? The macro is on my personal.xls now, but i want to in the end deploy as an addin. This is just the save subroutine from my macro–it is much longer. Thanks!
Bryan


Comment from Jon Peltier
Time: Thursday, December 31, 2009, 2:21 am

Bryan -

If the file was imported as a CSV file, you have to tell Excel what format to save it in:

ActiveWorkbook.SaveAs TheFile, xlWorkbookNormal


Comment from Bryan
Time: Thursday, December 31, 2009, 10:00 am

Thank you. This worked great.

Write a comment

I welcome comments from my readers. If you have an opinion on this post, if you have a question or if there is anything to add, I want to hear from you. Whether you agree or disagree, please join the discussion.

If you want to include an image in your comment, post it on your own site or on one of the many free image sharing sites, and include a link in your comment. I'll download your image and insert the necessary html to display the image inline.

Read the PTS Blog Comment Policy.





Subscribe without commenting

PTS Waterfall Chart Utility PTS Cluster-Stack Column Chart Utility PTS Box and Whisker Chart Utility PTS Marimekko Chart Utility PTS Dot Plot Utility PTS Cascade Chart Utility

Create Excel dashboards quickly with Plug-N-Play reports.