15

I want to:

  • Do data manipulation using a Template workbook
  • Save a copy of this work book as .xlsx (SaveCopyAs doesn't let you change filetypes, otherwise this would be great)
  • Continue showing original template (not the "saved as" one)

Using SaveAs does exactly what is expected - it saves the workbook while removing the macros and presents me the view of the newly created SavedAs workbook.

This unfortunately means:

  • I no longer am viewing my macro enabled workbook unless I reopen it
  • Code execution stops at this point because
  • Any macro changes are discarded if I forget to save (note: for a production environment this is ok, but, for development, it's a huge pain)

Is there a way I can do this?

'current code
Application.DisplayAlerts = False
templateWb.SaveAs FileName:=savePath, FileFormat:=xlOpenXMLWorkbook, CreateBackup:=False
templateWb.Activate
Application.DisplayAlerts = True

'I don't really want to make something like this work (this fails, anyways)
Dim myTempStr As String
myTempStr = ThisWorkbook.Path & "\" & ThisWorkbook.Name
ThisWorkbook.Save
templateWb.SaveAs FileName:=savePath, FileFormat:=xlOpenXMLWorkbook, CreateBackup:=False
ActiveWorkbook.Close
Workbooks.Open (myTempStr)

'I want to do something like:
templateWb.SaveCopyAs FileName:=savePath, FileFormat:=xlOpenXMLWorkbook, CreateBackup:=False 'SaveCopyAs only takes one argument, that being FileName

Also note while SaveCopyAs will let me save it as a different type (ie templateWb.SaveCopyAs FileName:="myXlsx.xlsx") this gives an error when opening it because it now has an invalid file format.

enderland
  • 13,825
  • 17
  • 98
  • 152
  • 1
    a lame workaround I can think of is to SaveCopyAs, open the copy, save it as your desired format, delete the copy. If you shove it into a sub then it won't clutter up your main procedures. – Cor_Blimey Sep 19 '13 at 16:14
  • 1
    Use `SaveCopyAs` to create a copy then, open that copy and do a save as? – Siddharth Rout Sep 19 '13 at 16:20
  • @Cor_Blimey: Sorry didn't see your comment – Siddharth Rout Sep 19 '13 at 16:21
  • Or create a new workbook, copy all your sheets in that and then save it as an xlsx? – Siddharth Rout Sep 19 '13 at 16:22
  • Both those options make me die a little on the inside (ok a lot). @Cor_Blimey I definitely thought of doing that as well initially but it just seems there should be a better way. I'm working with network drives so minimizing saving on them multiple times is ideal. – enderland Sep 19 '13 at 16:25
  • use the user temp directory to create a copy. Or if you are using my second suggestion, then you do not need to save the temp file... – Siddharth Rout Sep 19 '13 at 16:27
  • @SiddharthRout yeah, I'm thinking that might be "best" (using that word very loosely, here...). – enderland Sep 19 '13 at 16:32
  • Yes, it is comparatively very fast. Let me know if you get stuck and I will post a sample. – Siddharth Rout Sep 19 '13 at 16:33
  • Ok, I tested it and it took approx a second for 10 worksheets (Each with 100 rows * 20 Cols of data) – Siddharth Rout Sep 19 '13 at 16:40
  • I'd actually recommend separating your code from your template. It's a pain, I know, but Excel tends to work better if the code that manipulates your template workbook is in a separate workbook or addon. I'd also avoid copying cells or copying worksheets when possible. There are many tiny quirks and issues depending on the version of Excel. – AndASM Sep 19 '13 at 19:23

6 Answers6

6

Here is a much faster method than using .SaveCopyAs to create a copy an then open that copy and do a save as...

As mentioned in my comments, this process takes approx 1 second to create an xlsx copy from a workbook which has 10 worksheets (Each with 100 rows * 20 Cols of data)

Sub Sample()
    Dim thisWb As Workbook, wbTemp As Workbook
    Dim ws As Worksheet

    On Error GoTo Whoa

    Application.DisplayAlerts = False

    Set thisWb = ThisWorkbook
    Set wbTemp = Workbooks.Add

    On Error Resume Next
    For Each ws In wbTemp.Worksheets
        ws.Delete
    Next
    On Error GoTo 0

    For Each ws In thisWb.Sheets
        ws.Copy After:=wbTemp.Sheets(1)
    Next

    wbTemp.Sheets(1).Delete
    wbTemp.SaveAs "C:\Blah Blah.xlsx", 51

LetsContinue:
    Application.DisplayAlerts = True
    Exit Sub
Whoa:
    MsgBox Err.Description
    Resume LetsContinue
End Sub
Siddharth Rout
  • 147,039
  • 17
  • 206
  • 250
  • +1, this is pretty similar to what I ended up with [here](http://stackoverflow.com/a/18901568/1048539) - I made it a bit more robust and into a function. – enderland Sep 19 '13 at 17:45
  • 2
    Also `For Each... ws.Delete` is such a hack ;) – enderland Sep 19 '13 at 17:50
  • It depends on what version of Excel you use. With Excel 2003 copying sheets is not a safe operation and can result in data loss. – AndASM Sep 19 '13 at 19:20
  • Oh yeah, also: `Application.SheetsInNewWorkbook = 1` saves you from having to delete an arbitrary number of sheets and have that nasty `On Error Resume Next` in there. You should save the value from `Application.SheetsInNewWorkbook` and restore it at the end of your Sub. – AndASM Sep 19 '13 at 19:28
  • 1
    @SiddharthRout IIRC there are a few errors that can occur. For example, a quick Google search turns up [the 255 character limit](http://support.microsoft.com/kb/213548). – AndASM Sep 19 '13 at 19:30
  • @AndASM: Ah I was not aware of that. Never had that long a formula or text in my cells :P Learnt something new today. Will have to test it once I lay my hands o Excel 2003. Does that happen even after SP3? – Siddharth Rout Sep 19 '13 at 19:33
  • @SiddharthRout I don't know. It's probably been two years since I've dealt with copying worksheets in Excel. I don't have a copy on hand to test with. – AndASM Sep 19 '13 at 19:47
  • the sooner Excel 2003 is becomes unsupported the better ;) – Cor_Blimey Sep 19 '13 at 21:06
  • @SiddharthRout What happens if you have a macro that runs on opening and thus kills your own macro? – AER Nov 14 '16 at 05:01
6

I did something similar to what Siddharth suggested and wrote a function to do it as well as handle some of the annoyances and offer some more flexibility.

Sub saveExample()
    Application.ScreenUpdating = False

    mySaveCopyAs ThisWorkbook, "C:\Temp\testfile2", xlOpenXMLWorkbook

    Application.ScreenUpdating = True
End Sub

Private Function mySaveCopyAs(pWorkbookToBeSaved As Workbook, pNewFileName As String, pFileFormat As XlFileFormat) As Boolean

    'returns false on errors
    On Error GoTo errHandler



     If pFileFormat = xlOpenXMLWorkbookMacroEnabled Then
        'no macros can be saved on this
        mySaveCopyAs = False
        Exit Function
    End If

    'create new workbook
    Dim mSaveWorkbook As Workbook
    Set mSaveWorkbook = Workbooks.Add

    Dim initialSheets As Integer
    initialSheets = mSaveWorkbook.Sheets.Count


    'note: sheet names will be 'Sheet1 (2)' in copy otherwise if
    'they are not renamed
    Dim sheetNames() As String
    Dim activeSheetIndex As Integer
    activeSheetIndex = pWorkbookToBeSaved.ActiveSheet.Index

    Dim i As Integer
    'copy each sheet
    For i = 1 To pWorkbookToBeSaved.Sheets.Count
        pWorkbookToBeSaved.Sheets(i).Copy After:=mSaveWorkbook.Sheets(mSaveWorkbook.Sheets.Count)
        ReDim Preserve sheetNames(1 To i) As String
        sheetNames(i) = pWorkbookToBeSaved.Sheets(i).Name
    Next i

    'clear sheets from new workbook
    Application.DisplayAlerts = False
    For i = 1 To initialSheets
        mSaveWorkbook.Sheets(1).Delete
    Next i

    'rename stuff
    For i = 1 To UBound(sheetNames)
        mSaveWorkbook.Sheets(i).Name = sheetNames(i)
    Next i

    'reset view
    mSaveWorkbook.Sheets(activeSheetIndex).Activate

    'save and close
    mSaveWorkbook.SaveAs FileName:=pNewFileName, FileFormat:=pFileFormat, CreateBackup:=False
    mSaveWorkbook.Close
    mySaveCopyAs = True

    Application.DisplayAlerts = True
    Exit Function

errHandler:
    'whatever else you want to do with error handling
    mySaveCopyAs = False
    Exit Function


End Function
enderland
  • 13,825
  • 17
  • 98
  • 152
  • 1
    out of curiosity, is there a reason why you copy the sheets and not move them from the temp file? Moving closes the workbook automatically. Anyway, I can see this being quicker than saving a copy, opening etc, but it probably wouldn't be great if you intend to use this method with worksheets with tables, formulae, defined names etc (though as you are using this with a template you control, I suppose you know this isn't an issue). – Cor_Blimey Sep 19 '13 at 21:05
  • 1
    @Cor_Blimey I want to keep the template intact and use this functionality basically like "SaveCopyAs" - if I move the sheets I lose them from the template workbook. – enderland Sep 20 '13 at 13:06
  • whoops - I lost sight of that objective. Thanks. – Cor_Blimey Sep 20 '13 at 16:46
2

There is nothing pretty or nice about this process in Excel VBA, but something like the below. This code doesn't handle errors very well, is ugly, but should work.

We copy the workbook, open and resave the copy, then delete the copy. The temporary copy is stored in your local temp directory, and deleted from there as well.

Option Explicit

Private Declare Function GetTempPath Lib "kernel32" _
         Alias "GetTempPathA" (ByVal nBufferLength As Long, _
         ByVal lpBuffer As String) As Long

Public Sub SaveCopyAs(TargetBook As Workbook, Filename, FileFormat, CreateBackup)
  Dim sTempPath As String * 512
  Dim lPathLength As Long
  Dim sFileName As String
  Dim TempBook As Workbook
  Dim bOldDisplayAlerts As Boolean
  bOldDisplayAlerts = Application.DisplayAlerts
  Application.DisplayAlerts = False

  lPathLength = GetTempPath(512, sTempPath)
  sFileName = Left$(sTempPath, lPathLength) & "tempDelete_" & TargetBook.Name

  TargetBook.SaveCopyAs sFileName

  Set TempBook = Application.Workbooks.Open(sFileName)
  TempBook.SaveAs Filename, FileFormat, CreateBackup:=CreateBackup
  TempBook.Close False

  Kill sFileName
  Application.DisplayAlerts = bOldDisplayAlerts
End Sub
AndASM
  • 9,458
  • 1
  • 21
  • 33
1

I have a similar process, here's the solution I use. It allows the user to open a template, perform manipulation, save the template somewhere, and then have the original template open

  1. user opens macro-enabled template file
  2. do manipulation
  3. save ActiveWorkbook's file path (template file)
  4. execute a SaveAs
  5. set ActiveWorkbook (now the saveas'd file) as a variable
  6. open template file path in step 3
  7. close the variable in step 5

the code looks something like this:

    'stores file path of activeworkbook BEFORE the SaveAs is executed
    getExprterFilePath = Application.ActiveWorkbook.FullName

    'executes a SaveAs
    ActiveWorkbook.SaveAs Filename:=filepathHere, _
    FileFormat:=51, _
    Password:="", _
    WriteResPassword:="", _
    ReadOnlyRecommended:=False, _
    CreateBackup:=False

    'reenables alerts
    Application.DisplayAlerts = True


    'announces completion to user
    MsgBox "Export Complete", vbOKOnly, "List Exporter"             


    'sets open file (newly created file) as variable
    Set wbBLE = ActiveWorkbook

    'opens original template file
    Workbooks.Open (getExprterFilePath)

    'turns screen updating, calculation, and events back on
    With Excel.Application
        .ScreenUpdating = True
        .Calculation = Excel.xlAutomatic
        .EnableEvents = True
    End With

    'closes saved export file
    wbBLE.Close
arbitel
  • 321
  • 6
  • 22
0

Another option (only tested on latest versions of excel).

The Macros are not deleted until the workbook is closed after a SaveAs .xlsx so you can do two SaveAs in quick succession without closing the workbook.

ActiveWorkbook.SaveAs FileFormat:=xlOpenXMLWorkbook, CreateBackup:=False
Application.DisplayAlerts = False
ActiveWorkbook.SaveAs FileFormat:=xlOpenXMLWorkbookMacroEnabled, CreateBackup:=False, ConflictResolution:=xlLocalSessionChanges
Application.DisplayAlerts = True

Note: you need to turn off the DisplayAlerts to avoid getting the warning that the workbook already exists on the second save.

OSKM
  • 728
  • 14
  • 25
0

Nice solution @enderland. I thought I will share my problem, which was slightly different. I read data from excel. For each row in excel table I am opening word template document. Then insert data from excel to it. Then save it to new word document without changing the template. It worked fine when those word documents were closed during running makro. But when some of those were open, it basicly failed to save anything.

What I did was to first check if some Word app is already running, if yes use it:

'Start Word and add a new document, but if word is already running use it
Set wd = GetObject(, "Word.Application")
If wd Is Nothing Then
    Set wd = New Word.Application
End If

Then have a help string variable "templFName", which basicly holds the name of the word template file. For simplicity have all my output word documents which I produce, name as:

templFName & "some unique suffix" & ".docx"

Then see if any of word document which I use (template one or output one) is already open, if yes close it (using the reference from first step):

'close word document if already opened
Dim aDoc As Document
For Each aDoc In Documents
    If InStr(1, aDoc.Name, templFName, 1) Then
        aDoc.ActiveWindow.Close
        Set aDoc = Nothing
    End If
Next aDoc

Then quit the word application and open fresh new one:

wd.Quit
Set wd = Nothing
Set wd = New Word.Application

Thats it. This last step fixed my problem, in case any of the output word documents were open in windows, before running the makro. Hope it will help someone one day. Have a good day.

10101101
  • 193
  • 1
  • 13