Getting Started in VBA

My off-the-cuff recruit a new VBA programmer week comment got a modest amount of traction around the ‘tubes. Yet, I didn’t want to wait a whole year to provide these links. From msdn’s Getting Started with VBA… series.

Are you facing a repetitive clean up of fifty tables in Word 2010? Do you want a particular document to prompt the user for input when it opens? Are you having difficulty figuring out how to get your contacts from Microsoft Outlook 2010 into a Microsoft Excel 2010 spreadsheet efficiently?

You can perform these tasks and accomplish a great deal more by using VBA for Office 2010−a simple, but powerful programming language that you can use to extend Office 2010 applications.

Getting Started with VBA in Office 2010
Getting Started with VBA in Excel 2010
Getting Started with VBA in Outlook 2010
Getting Started with VBA in Powerpoint 2010

Links from John Durant, author of Why VBA Still Makes Sense.

Methods in Excel Podcast

Vocal Microphone

Go check out the first Methods in Excel Podcast hosted by Ross McLean. The first guest is Simon Murphy of Codematic. I’m slotted for a future show, so I’m looking forward to that.

Changing names of some files in a directory

This started off by helping myself to Dick’s code in ‘Removing Spaces from File Names’ (http://www.dailydoseofexcel.com/archives/2009/11/12/removing-spaces-from-file-names/) but quickly evolved to meet my own needs.

First, was the requirement to replace a certain text string by another. I added 3 parameters to the subroutine (Dirname, ReplaceWhat, and ReplaceBy). Also evident was that some files had leading spaces as well as multiple consecutive embedded blanks. I added an optional Boolean doTrim.

By using Dir rather than FileSystemObject I could restrict the returned file names to those that matched the search criteria. Consequently, it was certain that the file name would change.

Option Explicit
    Function addPathSeparator(ByVal DirName As String)
        Dim PS As String: PS = Application.PathSeparator
        If Right(DirName, Len(PS)) <> PS Then _
            DirName = DirName & PS
        addPathSeparator = DirName
        End Function
Sub FilenameReplace(ByVal DirName As String, ByVal ReplaceWhat As String, _
        ByVal ReplaceBy As String, Optional ByVal doTrim As Boolean = False)
    Dim CurrName As String
    DirName = addPathSeparator(DirName)
    CurrName = Dir(DirName & "*" & ReplaceWhat & "*")
    Do While CurrName <> ""
        Dim NewName As String
        NewName = Replace(CurrName, ReplaceWhat, ReplaceBy)
        If doTrim Then NewName = Application.WorksheetFunction.Trim(NewName)
            'VBA Trim leaves embedded multiple spaces alone; _
             Excel's TRIM changes them to a single space
        On Error GoTo Catch1
        Name DirName & CurrName As DirName & NewName
        GoTo Finally1
Catch1:
        Debug.Print "Error changing '" _
                & CurrName & "' to '" & NewName & "'" & vbNewLine _
            & "    Error: " & Err.Description _
                & " (" & Err.Number & ")"
        Resume Finally1
Finally1:
        CurrName = Dir()
        Loop

End Sub

Then, I found some files had characters just before the text to be replaced that were “special characters.” I added an optional boolean useRegExp together with the code to use a regular expression to do the cleaning.

So, a filename like ‘This is a file, change me.xls’ should become ‘This is a file changed you.xls’

Unlike the above code, Dir could not be used to restrict the filenames since it does not support regular expressions. Consequently, I included a test to ensure that the new name differed from the old name before using the Name statement to rename the file.

The code below has been lightly tested as in it worked for the few directories that I had to process, each with a different set of rules.

Option Explicit
#Const EarlyBind = False

Sub FilenameReplaceRegExp(ByVal DirName As String, _
        ByVal ReplaceWhat As String, ByVal ReplaceBy As String, _
        Optional ByVal doTrim As Boolean = False, _
        Optional useRegExp As Boolean = False)

    #If EarlyBind Then
    Dim RE As RegExp
    #Else
    Dim RE As Object
        #End If
    If useRegExp Then
        #If EarlyBind Then
        Set RE = New RegExp
        #Else
        Set RE = CreateObject("VBScript.RegExp")
            #End If
        RE.IgnoreCase = True
        RE.Global = True
        RE.Pattern = ReplaceWhat
        End If
    Dim CurrName As String
    DirName = addPathSeparator(DirName)
    CurrName = Dir(DirName & "*.*")
    Do While CurrName <> ""
        Dim NewName As String
        If useRegExp Then
            NewName = RE.Replace(CurrName, ReplaceBy)
        Else
            NewName = Replace(CurrName, ReplaceWhat, ReplaceBy)
            End If
        If doTrim Then _
            NewName = Application.WorksheetFunction.Trim(NewName)
            'VBA Trim leaves embedded multiple spaces alone; _
             Excel's TRIM changes them to a single space
        If NewName <> CurrName Then
            On Error GoTo Catch1
            Name DirName & CurrName As DirName & NewName
            GoTo Finally1
Catch1:
            Debug.Print "Error changing '" _
                    & CurrName & "' to '" & NewName & "'" & vbNewLine _
                & "    Error: " & Err.Description _
                    & " (" & Err.Number & ")"
            Resume Finally1
Finally1:
            End If
        CurrName = Dir()
        Loop

    End Sub

Invoke the above subroutine as

    FilenameReplaceRegExp "c:\dir to check", "\W*change me", "changed you", True, True

Tushar Mehta

Office Web Apps

I’m trying to be the last blog on earth to announce the Office 2010 Beta. Included in 2010 are Office Web Apps.

With Microsoft Office Web Apps, you can access documents from virtually anywhere. Whether you’re editing a document from home or collaborating with someone around the world, Office Web Apps help you work the way you want.

Office Web Apps stink. Probably. I mean I haven’t even seen them much less used them. So how do I know they stink? Because they are version 1 and version 1 of everything Microsoft makes stinks. In fact, version 1 of everything anyone makes stinks. Here’s the natural order of software progression:

V1: Good intentions, but not well implemented
V2: Fix major pain points and now people are starting to get excited about it
V3: Polished and stable and no one can even remember V2.
V4: They’ve bolted on the latest fad and it takes 20 minutes to load

Vista was V1 and it didn’t do so well. Windows 7 is V2 and there’s some pretty positive buzz. The next version of Windows is going to be one of those rock-solid operating systems that people never want to give up and they’ll use forever.

Office 2007 contained a little change that you may have heard about called Ribbon v1. I heard some good stuff about the Ribbon, but not much. The Ribbon will be better in 2010 and then in the next version we’ll be singing its praises. Some of you doubt this, I know, but it will happen.

I’m not trying to dissuade you from adopting V1. V1 is a necessary step in the evolution. You should just set your expectations appropriately.

Learn VBA to be Lean

Lean Office with Excel and VBA

We have noted that many people are not as adept at using the software we have placed at their finger tips as we would like to think. As a result, we have found that one of the significant losses in the front office is time wasted that could easily be done by the software. Unfortunately, we simply don’t know how to do it.

and later…

The next time you find yourself entering even more data and formatting spreadsheets and reports, consider teaching yourself some VBA over the next few weeks to see how much you can do to help yourself by putting your software to work!

Awesome. I like to see encouragement for people to learn Excel from blogs that are not Excel blogs (i.e. normal people). Also, I’ve been studying the Toyota Production System, so seeing an Excel post on a lean blog is like worlds colliding for me.

I like the simplicity of the message. It doesn’t say “go be an expert” or “go start a VBA blog”. It simply suggests that you teach yourself some VBA over the next couple of weeks. How much VBA can you learn in a couple of weeks? In my opinion, it doesn’t matter. I’d say it’s quite likely that you’d learn something that significantly improves the quality or efficiency of your work. But even if you forget what you learn, there is still value.

Before my first two weeks learning VBA, I knew nothing. VBA so intrigued me, however, that I continued to learn it. Had I abandoned it, I would have learned something else and followed a different path. You can find a path too. If you never try VBA, you’ll never know if that was the path for you. So try it. If you don’t like it, go learn HTML or Javascript or belly dancing. You’ve already mastered Solitaire and The Sims, so move on.

When you’re ready, I recommend Excel VBA Programming for Dummies. I’ve read it several times (because I tech edited it) and I can say that is a great book for beginning VBA programmers. It reminds me of my freshman Calculus professor. She could make the most foreign topics seems easy and natural. The article mentions Microsoft Office Excel 2007 Visual Basic for Applications: Step by Step, which I haven’t read. I have read other ’step-by-step’ titles and I’ve generally liked them.

I officially dub this week “Recruit a new VBA programmer” week.

Visualizing data in Excel: How would you interpret this graphic?

Because I have no clue. It’s from an university alumni magazine. The names I’ve smudged (or tried to anyways) are the various colleges. Could this graphic be done in Excel? Yes. Should it?

ddoe-whats-this-chart

Tushar Mehta (www.tushar-mehta.com)

CodeColorer

On JP’s recommendation I’m trying CodeColorer for syntax highlighting.

Public Sub LoadFromFile()
   
    Dim xmlDoc As MSXML2.DOMDocument
    Dim xmlNodes As MSXML2.IXMLDOMNodeList
    Dim xmlNode As MSXML2.IXMLDOMNode
    Dim i As Long
    Dim clsRandom As CRandom
   
    Set xmlDoc = New MSXML2.DOMDocument
    xmlDoc.Load Environ("USERPROFILE") & "\My Documents\My Dropbox\wc\dkSampleData.xml"
    Set xmlNodes = xmlDoc.documentElement.selectNodes("//SampleDatum")
   
    For i = 0 To xmlNodes.Length - 1
        Set xmlNode = xmlNodes.Item(i)
        Set clsRandom = New CRandom
       
        clsRandom.RandType = xmlNode.selectSingleNode("RandType").nodeTypedValue
        clsRandom.Name = xmlNode.selectSingleNode("Name").nodeTypedValue
        clsRandom.Min = xmlNode.selectSingleNode("Min").nodeTypedValue
        clsRandom.Max = xmlNode.selectSingleNode("Max").nodeTypedValue
        clsRandom.Decimals = xmlNode.selectSingleNode("Decimals").nodeTypedValue
        clsRandom.Sequential = xmlNode.selectSingleNode("Sequential").nodeTypedValue
        clsRandom.Skip = xmlNode.selectSingleNode("Skip").nodeTypedValue
        Me.Add clsRandom
       
    Next i
End Sub

When I installed it, it said that there was a conflict with my existing highlighter. I was hoping that I wouldn’t have to disable the old one because it will make my existing code look ugly. I suppose I could do an update query on all the old posts. Sounds dangerous.

Visualizing data in Excel: Balancing the physical and logical dimensions of a chart

When Excel creates a chart it assigns, by default, certain physical dimensions to the chart. These physical dimensions (height and width) are independent of the logical content of the chart. So, in the case of a column chart the physical shape of the chart is the same irrespective of whether it contains 2 columns or 200. Similarly, for a XY Scatter chart, the size of the chart is independent of the range of the X or Y dimensions. For example, if we were to plot data that constitute a circle, the default chart would look like Figure 1.

ddoe-chart1
Figure 1

This is clearly misleading since the physical width of the chart is greater than the height even though both the X and Y ranges are the same (each of them goes from -30 to +50) - and the minimum and maximum of the plotted values are also the same (-20 and +40 respectively).

In reverse, we could have data that represent an ellipse but because of the physical shape of the chart look like a circle - see Figure 2.

ddoe-chart22
Figure 2

In this case, though the physical dimensions of the chart match each other, the logical X range is 2.5 times larger than the logical Y range (-30to +50 and -10 to +30 respectively).

The code below adjusts the physical height of the active chart so that the ratio of the physical dimensions match the ratio of the logical ones. The corrected version of Figure 1 is Figure 3.

ddoe-chart4
Figure 3

and the corrected version of Figure 2 is Figure 4.

ddoe-chart31
Figure 4

There are two constants that control what the code in balanceAxis does. changeAxes causes the code to change the min. and max. values of both the X and Y axis based on the values of the series in the chart. useSeriesVals results in the code using the min. and max values of the series plotted in the chart for balance calculations rather than the min. and max. values of the 2 axes. Obviously, having changeAxes as true implies useSeriesVals.

To use the below, select a chart — it must be a chart where both X and Y axis are numeric scales — and run the balanceAxis subroutine (use ALT+F8 to open the Macro dialog box) . Given the wide range of possibilities with options across charts, the wide range of charts across all the different versions of Excel, the below works where it works.

Option Explicit
Private Function MinVal(aChart As Chart, Optional CategoryVals As Boolean)
    Dim I As Integer
    If CategoryVals Then _
        MinVal = Application.WorksheetFunction.Min( _
            aChart.SeriesCollection(1).XValues) _
    Else _
        MinVal = Application.WorksheetFunction.Min( _
            aChart.SeriesCollection(1).Values)
   
    For I = 2 To aChart.SeriesCollection.Count
        If CategoryVals Then _
            MinVal = Application.WorksheetFunction.Min(MinVal, _
                aChart.SeriesCollection(I).XValues) _
        Else _
            MinVal = Application.WorksheetFunction.Min(MinVal, _
                aChart.SeriesCollection(I).Values)
        Next I
    End Function
Private Function MaxVal(aChart As Chart, Optional CategoryVals As Boolean)
    Dim I As Integer
    If CategoryVals Then _
        MaxVal = Application.WorksheetFunction.Max( _
            aChart.SeriesCollection(1).XValues) _
    Else _
        MaxVal = Application.WorksheetFunction.Max( _
            aChart.SeriesCollection(1).Values)
   
    For I = 2 To aChart.SeriesCollection.Count
        If CategoryVals Then _
            MaxVal = Application.WorksheetFunction.Max(MaxVal, _
                aChart.SeriesCollection(I).XValues) _
        Else _
            MaxVal = Application.WorksheetFunction.Max(MaxVal, _
                aChart.SeriesCollection(I).Values)
        Next I
    End Function
Private Sub getMinMaxVals(ByVal aChart As Chart, ByVal useSeriesVals As Boolean, _
        ByRef XMin As Single, ByRef XMax As Single, _
        ByRef YMin As Single, ByRef YMax As Single)
    With aChart
    If useSeriesVals Then
        YMin = MinVal(aChart): YMax = MaxVal(aChart)
        XMin = MinVal(aChart, True): XMax = MaxVal(aChart, True)
    Else
        With .Axes(xlValue)
        YMin = .MinimumScale
        YMax = .MaximumScale
            End With
        With .Axes(xlCategory)
        XMin = .MinimumScale
        XMax = .MaximumScale
            End With
        End If
        End With
    End Sub
Private Sub updateChartAxes(aChart As Chart, _
        ByVal XMin As Single, ByVal XMax As Single, _
        ByVal YMin As Single, ByVal YMax As Single)
    With aChart
    With .Axes(xlValue)
    .MinimumScale = YMin
    .MaximumScale = YMax
        End With
    With .Axes(xlCategory)
    .MinimumScale = XMin
    .MaximumScale = XMax
        End With
        End With
    End Sub
Sub balanceAxis()
    Const changeAxes As Boolean = False, _
        useSeriesVals As Boolean = False
    Dim aChart As Chart: Set aChart = ActiveChart
    Dim XMin As Single, XMax As Single, _
        YMin As Single, YMax As Single
    Dim NewXMin As Single, NewXMax As Single, _
        NewYMin As Single, NewYMax As Single
    Const cMaxTries As Integer = 3
    Dim Ratio As Single, OldRatio As Single, Tries As Integer
        'If the axes are on automatic scaling, changing the _
         physical dimension may change the min/max axis values. _
         If that happens and if the height-to-width ratio changes, _
         the code will readjust the physical dimensions.  However, _
         to avoid a perpetual loop the number of attempts is _
         limited to cMaxTries
   Tries = 0
    Do
        Tries = Tries + 1
        getMinMaxVals aChart, changeAxes Or useSeriesVals, _
            XMin, XMax, YMin, YMax
        With aChart
        If changeAxes Then updateChartAxes aChart, XMin, XMax, YMin, YMax
        Dim DesiredHeight As Single
        OldRatio = (YMax - YMin) / (XMax - XMin)
        DesiredHeight = .PlotArea.Width * OldRatio
        .ChartArea.Height = DesiredHeight _
            * .ChartArea.Height / .PlotArea.Height
            'The above retains the ratio of the heights of the _
            chartarea and plotarea
       .PlotArea.Height = DesiredHeight
        getMinMaxVals aChart, changeAxes Or useSeriesVals, _
            NewXMin, NewXMax, NewYMin, NewYMax
        Ratio = (NewYMax - NewYMin) / (NewXMax - NewXMin)
        Debug.Print "Try " & Tries & ", Old Ratio=" & OldRatio _
            & ", Plotarea=" & .PlotArea.Height / .PlotArea.Width _
            & ", Chartarea=" & .ChartArea.Height / .ChartArea.Width _
            & ", Ratio=" & Ratio
            End With
        Loop Until Tries >= cMaxTries Or Abs(OldRatio - Ratio) < 0.00000001
    Debug.Print ""
    End Sub