Friday, February 24, 2012

Application.Evaluate

In Excel 2011 Application.Evaluate is broken

For example the test macro below will show you 1 in Excel 2004 and all Windows Excel versions.
Sub TestAppEvaluate()
    ThisWorkbook.Names.Add "Version", "=1"
    MsgBox Application.Evaluate("'" & ThisWorkbook.Name & "'!Version")
End Sub

But in Excel 2011 this will not work, you can use this as a workeround :
Sub TestAppEvaluate2()
    Dim VersionConstant As String
    ThisWorkbook.Names.Add "Version", "=2"
    VersionConstant = ThisWorkbook.Names("Version").RefersTo
    MsgBox Mid(VersionConstant, 2, Len(VersionConstant) - 1)
End Sub

Select Folder with VBA/Macscript

With the example below you can select a folder in a browse dialog and also set the Root folder.
In this example it will always open on the desktop, see the commented line in the code to use a fixed path.

Sub Select_Folder_On_Mac()
    Dim folderPath As String
    Dim RootFolder As String

    On Error Resume Next
    RootFolder = MacScript("return (path to desktop folder) as String")
    'Or use RootFolder = "Macintosh HD:Users:Ron:Desktop:TestMap:"
    folderPath = MacScript("(choose folder with prompt ""Select the folder""" & _
    "default location alias """ & RootFolder & """) as string")
    On Error GoTo 0

    If folderPath <> "" Then
        MsgBox folderPath
    End If
End Sub

Change the sheet/range that you want to merge :

I use the first worksheet of each workbook in my example (index 1)
Change the worksheet index or fill in a sheet name like: mybook.Worksheets("YourSheetName")

And you can change the range A1:C1 to your range.

With mybook.Worksheets(1)
    Set SourceRange = .Range("A1:C1")
End Wit
h

On my Windows page (see link above) there are other example that you can adapt for Excel 2011.

Note : When you Run this macro it wil ask you to select the folder with the files


Sub MergeWorkbooksOnMac()
    Dim FilesInPath As String
    Dim MyFiles() As String
    Dim SourceRcount As Long, FNum As Long
    Dim mybook As Workbook, BaseWks As Worksheet
    Dim sourceRange As Range, destrange As Range
    Dim rnum As Long, CalcMode As Long
    Dim folderPath As String

    On Error Resume Next
    folderPath = MacScript("choose folder as string")
    If folderPath = "" Then Exit Sub
    On Error GoTo 0

    'If there are no files in the folder exit the sub
    FilesInPath = Dir(folderPath)
    If FilesInPath = "" Then
        MsgBox "No files found"
        Exit Sub
    End If

    'Fill the array(myFiles) with the list of Excel files in the folder
    FNum = 0
    Do While FilesInPath <> ""
        If FilesInPath Like "*.xls" Then
            FNum = FNum + 1
            ReDim Preserve MyFiles(1 To FNum)
            MyFiles(FNum) = FilesInPath
        End If
        FilesInPath = Dir()
    Loop

    'Add a new workbook with one sheet
    Set BaseWks = Workbooks.Add(xlWBATWorksheet).Worksheets(1)
    BaseWks.Range("A1").Font.Size = 36
    BaseWks.Range("A1").Value = "Please Wait"
    rnum = 3

    'Change ScreenUpdating, Calculation and EnableEvents
    With Application
        CalcMode = .Calculation
        .Calculation = xlCalculationManual
        .ScreenUpdating = False
        .EnableEvents = False
    End With

    'Loop through all files in the array(myFiles)
    If FNum > 0 Then
        For FNum = LBound(MyFiles) To UBound(MyFiles)
            Set mybook = Nothing
            On Error Resume Next
            Set mybook = Workbooks.Open(folderPath & MyFiles(FNum))
            On Error GoTo 0

            If Not mybook Is Nothing Then

                On Error Resume Next

                With mybook.Worksheets(1)
                    Set sourceRange = .Range("A1:C1")
                End With

                If Err.Number > 0 Then
                    Err.Clear
                    Set sourceRange = Nothing
                Else
                    'if SourceRange use all columns then skip this file
                    If sourceRange.Columns.Count >= BaseWks.Columns.Count Then
                        Set sourceRange = Nothing
                    End If
                End If
                On Error GoTo 0

                If Not sourceRange Is Nothing Then

                    SourceRcount = sourceRange.Rows.Count

                    If rnum + SourceRcount >= BaseWks.Rows.Count Then
                        MsgBox "Sorry there are not enough rows in the sheet"
                        BaseWks.Columns.AutoFit
                        mybook.Close savechanges:=False
                        GoTo ExitTheSub
                    Else

                        'Copy the file name in column A
                        With sourceRange
                            BaseWks.Cells(rnum, "A"). _
                                    Resize(.Rows.Count).Value = MyFiles(FNum)
                        End With

                        'Set the destrange
                        Set destrange = BaseWks.Range("B" & rnum)

                        'we copy the values from the sourceRange to the destrange
                        With sourceRange
                            Set destrange = destrange. _
                                            Resize(.Rows.Count, .Columns.Count)
                        End With
                        destrange.Value = sourceRange.Value

                        rnum = rnum + SourceRcount
                    End If
                End If
                mybook.Close savechanges:=False
            End If

        Next FNum
        BaseWks.Columns.AutoFit
    End If

ExitTheSub:
    BaseWks.Range("A1").Value = "Ready"
    'Restore ScreenUpdating, Calculation and EnableEvents
    With Application
        .ScreenUpdating = True
        .EnableEvents = True
        .Calculation = CalcMode
    End With
End Sub

Select files on a Mac (GetOpenFilename)

Note: The macro example for Windows and Mac are both using this function to test if the file is open.
Function bIsBookOpen(ByRef szBookName As String) As Boolean
' Rob Bovey
    On Error Resume Next
    bIsBookOpen = Not (Application.Workbooks(szBookName) Is Nothing)
End Function
In Windows you can use filefilter to only display the files you want and
use MultiSelect to select more then one file. Also it is possible with ChDrive and ChDir
to set the folder that is selected when GetOpenFilename opens, see basic example below :
Sub Select_File_Or_Files_Windows()
    Dim SaveDriveDir As String
    Dim MyPath As String
    Dim Fname As Variant
    Dim N As Long
    Dim FnameInLoop As String
    Dim mybook As Workbook

    'Save current Dir
    SaveDriveDir = CurDir

    'Set path to the folder that you want to open
    MyPath = Application.DefaultFilePath

    'You can also use a fixed path like this
    'MyPath = "C:\Users\Ron de Bruin\Test"

    'Change Drive/Dir to MyPath
    ChDrive MyPath
    ChDir MyPath

    'Open GetOpenFilename, there are more file filters possible
    Fname = Application.GetOpenFilename( _
            FileFilter:="Excel 97-2003 Files (*.xls), *.xls", _
            Title:="Select a file or files", _
            MultiSelect:=True)

    'Do what you want with the files you selected
    If IsArray(Fname) Then
        With Application
            .ScreenUpdating = False
            .EnableEvents = False
        End With

        For N = LBound(Fname) To UBound(Fname)

            'Get only the file name and test if it is open
            FnameInLoop = Right(Fname(N), Len(Fname(N)) - InStrRev(Fname(N), _
            Application.PathSeparator, , 1))
            If bIsBookOpen(FnameInLoop) = False Then

                Set mybook = Nothing
                On Error Resume Next
                Set mybook = Workbooks.Open(Fname(N))
                On Error GoTo 0

                If Not mybook Is Nothing Then
                    MsgBox "You open this file : " & Fname(N) & vbNewLine & _
                           "And after you press OK it will be closed" & vbNewLine & _
                           "without saving, replace this line with your own code."
                    mybook.Close savechanges:=False
                End If
            Else
              MsgBox "We skip this file : " & Fname(N) & " because it Is already open"
            End If
        Next N
        With Application
            .ScreenUpdating = True
            .EnableEvents = True
        End With
    End If

    'Change Drive/Dir back to SaveDriveDir
    ChDrive SaveDriveDir
    ChDir SaveDriveDir
End Sub

When you want to add more filters you can use this in the macro to filter on xls and csv files
    Fname = Application.GetOpenFilename( _
            FileFilter:="XLS Files (*.xls),*.xls,CSV Files (*.csv),*.csv", _
            Title:="Select a file or files", _
            MultiSelect:=True)

But on a Mac the filefilter is not working and it is not possible to select more then one file.
Also ChDir is not working like in Windows to set the folder that will open with GetOpenFilename.

But we can use a combination of VBA and Applescript, see example below that only display xls files
and you can set the start folder and you can select one or more then one file:
Sub Select_File_Or_Files_Mac()
    Dim MyPath As String
    Dim MyScript As String
    Dim MyFiles As String
    Dim MySplit As Variant
    Dim N As Long
    Dim Fname As String
    Dim mybook As Workbook

    On Error Resume Next
    MyPath = MacScript("return (path to documents folder) as String")
    'Or use MyPath = "Macintosh HD:Users:Ron:Desktop:TestFolder:"

    MyScript = _
    "set applescript's text item delimiters to "","" " & vbNewLine & _
            "set theFiles to (choose file of type " & _
          " {""com.microsoft.Excel.xls""} " & _
            "with prompt ""Please select a file or files"" default location alias """ & _
            MyPath & """ multiple selections allowed true) as string" & vbNewLine & _
            "set applescript's text item delimiters to """" " & vbNewLine & _
            "return theFiles"

    MyFiles = MacScript(MyScript)
    On Error GoTo 0

    If MyFiles <> "" Then
        With Application
            .ScreenUpdating = False
            .EnableEvents = False
        End With

        MySplit = Split(MyFiles, ",")
        For N = LBound(MySplit) To UBound(MySplit)

            'Get file name only and test if it is open
            Fname = Right(MySplit(N), Len(MySplit(N)) - InStrRev(MySplit(N), _
            Application.PathSeparator, , 1))
            If bIsBookOpen(Fname) = False Then

                Set mybook = Nothing
                On Error Resume Next
                Set mybook = Workbooks.Open(MySplit(N))
                On Error GoTo 0

                If Not mybook Is Nothing Then
                    MsgBox "You open this file : " & MySplit(N) & vbNewLine & _
                           "And after you press OK it will be closed" & vbNewLine & _
                           "without saving, replace this line with your own code."
                    mybook.Close savechanges:=False
                End If
            Else
              MsgBox "We skip this file : " & MySplit(N) & " because it Is already open"
            End If
        Next N
        With Application
            .ScreenUpdating = True
            .EnableEvents = True
        End With
    End If
End Sub

Note:
change true to false in this line in the code if you not want to be able to select more then one file.
multiple selections allowed true
You can remove the Split and loop part because MyFiles is already your answer when you set it to false.


You see that I now only filter on xls files , other options are :

xlsx
org.openxmlformats.spreadsheetml.sheet

xlsm
org.openxmlformats.spreadsheetml.sheet.macroenabled

xlsb
com.microsoft.Excel.sheet.binary.macroenabled

xls
com.microsoft.Excel.xls

csv
public.comma-separated-values-text

docx
org.openxmlformats.wordprocessingml.document

docm
org.openxmlformats.wordprocessingml.document.macroenabled

doc
com.microsoft.word.doc

pptx
org.openxmlformats.presentationml.presentation

pptm
org.openxmlformats.presentationml.presentation.macroenabled

ppt
com.microsoft.powerpoint.ppt


Tip : In the example file we filter on one file type (xls) but you can filter on more file types like this

Change

SaveAs and FileFormat numbers


These are the main file formats in Excel 2007-2010:

51 = xlOpenXMLWorkbook (without macro's in 2007-2010, xlsx)
52 = xlOpenXMLWorkbookMacroEnabled (with or without macro's in 2007-2010, xlsm)
50 = xlExcel12 (Excel Binary Workbook in 2007-2010 with or without macro's, xlsb)
56 = xlExcel8 (97-2003 format in Excel 2007-2010, xls)

Note: I always use the FileFormat numbers instead of the defined constants
in my code so that it will compile OK when I copy the code into an Excel
97-2003 workbook. (For example, Excel 97-2003 won't know what the
xlOpenXMLWorkbookMacroEnabled constant is.)

But In Excel 2011 you must add 1 to each number.

xlsx = 52
xlsm = 53
xlsb = 51
xls = 57

Important to know this because SaveAs requires you to provide both the FileFormat
parameter and the correct file extension.

ActiveWorkbook.SaveAs "C:\ron.xlsm", fileformat:=52

On a Mac in Excel 2011 you must use this


ActiveWorkbook.SaveAs "C:\ron.xlsm", fileformat:=53

Use VBA to Delete all files in folder

Below there are two examples that delete every file in a folder Test in your documents folder.
The first will delete the files from your system and the second one will move the files to the Trash.
Note: see the wilcard * in the first macro, you can change that to for example *.pdf or *.docx

Sub DeleteFilesInFolder()
    Dim FolderWithFiles As String
    Dim scriptToRun As String

    FolderWithFiles = MacScript("return (path to documents folder) as string") &amp; "Test:"

    scriptToRun = scriptToRun &amp; "tell application " &amp; Chr(34) &amp; _
                  "Finder" &amp; Chr(34) &amp; Chr(13)
    scriptToRun = scriptToRun &amp; _
                  "do shell script ""rm "" &amp; quoted form of posix path of " &amp; _
                  Chr(34) &amp; FolderWithFiles &amp; """ &amp; " &amp; Chr(34) &amp; "*" &amp; Chr(34) &amp; Chr(13)
    scriptToRun = scriptToRun &amp; "end tell"

    On Error Resume Next
    MacScript (scriptToRun)
    On Error GoTo 0
End Sub


Sub MoveFilesInFolderToTrash()
    Dim FolderWithFiles As String

    FolderWithFiles = MacScript("return (path to documents folder) as string") &amp; "Test:"
   
    scriptToRun = scriptToRun &amp; "tell application " &amp; Chr(34) &amp; _
                  "Finder" &amp; Chr(34) &amp; Chr(13)
    scriptToRun = scriptToRun &amp; "delete (every item of folder " &amp; _
                  Chr(34) &amp; FolderWithFiles &amp; Chr(34) &amp; ")" &amp; Chr(13)
    scriptToRun = scriptToRun &amp; "end tell" &amp; Chr(13)

    On Error Resume Next
    MacScript (scriptToRun)
    On Error GoTo 0

End Sub

Use VBA Kill to delete files

If you use the Kill command on a Mac be aware that there is a file name(with extension) limit of 32 characters.
Be sure that you use shorter file names or use a MacScript.

This will blow on a Mac for example (file not found error)

Kill "Macintosh HD:Users:rondebruin:Documents:Part of SendMail(Attachment)Testers.xls 12-jul-11 15-22-04.xls"

You can use this Mac Script instead if you can't be sure that you are below the limit.

Sub DeleteFileOnMac()
Dim scriptToRun As String
Dim Filestr As String

Filestr = "Macintosh HD:Users:rondebruin:Documents:Part of SendMail(Attachment)Testers.xls 12-jul-11 15-22-04.xls"

scriptToRun = scriptToRun &amp; "tell application " &amp; Chr(34) &amp; "Finder" &amp; Chr(34) &amp; Chr(13)
scriptToRun = scriptToRun &amp; "delete file " &amp; Chr(34) &amp; Filestr &amp; Chr(34) &amp; Chr(13)
scriptToRun = scriptToRun &amp; "end tell"

MacScript (scriptToRun)

End Sub

Note: that Kill remove the file from your system and the applescript example above move it to the Trash.

But you can also use this instead of Kill if you really want to delete the file :

Sub DeleteFile()
    Dim FileName As String
    Dim scriptToRun As String

    'Delete file ron.xlsm on your desktop
    FileName = MacScript("return (path to desktop folder) as string") &amp; "ron.xlsm"

    scriptToRun = scriptToRun &amp; "tell application " &amp; Chr(34) &amp; _
                  "Finder" &amp; Chr(34) &amp; Chr(13)
    scriptToRun = scriptToRun &amp; _
                  "do shell script ""rm "" &amp; quoted form of posix path of " &amp; _
                  Chr(34) &amp; FileName &amp; Chr(34) &amp; Chr(13)
    scriptToRun = scriptToRun &amp; "end tell"

    On Error Resume Next
    MacScript (scriptToRun)
    On Error GoTo 0
End Sub


Or you can use the function KillFileOnMac below to delete a file, you can call it in your macro like this :
KillFileOnMac "Macintosh HD:Users:Ron:Documents:YourFileName.xlsm"

Function KillFileOnMac(Filestr As String)
    Dim ScriptToKillFile As String
    ScriptToKillFile = ScriptToKillFile &amp; "tell application " &amp; Chr(34) &amp; _
                  "Finder" &amp; Chr(34) &amp; Chr(13)
    ScriptToKillFile = ScriptToKillFile &amp; _
                  "do shell script ""rm "" &amp; quoted form of posix path of " &amp; _
                  Chr(34) &amp; Filestr &amp; Chr(34) &amp; Chr(13)
    ScriptToKillFile = ScriptToKillFile &amp; "end tell"

    On Error Resume Next
    MacScript (ScriptToKillFile)
    On Error GoTo 0
End Function