Author Archive

Excel 2007 Likeability

Today I realized that it was about 17 months ago when I saw the first demo of Excel 2007. Like just about everyone else in the room, I was very impressed with what I saw — even though it was little more than a rough draft at the time. Fact is, Excel 2007 demos very well.

Then, when I actually started using it, my initial enthusiasm waned — primarily because I couldn’t find the familiar commands. As I dug deeper, I discovered a few things that just didn’t work like they used to.

During the beta phase, I got very discouraged and really began to hate it. But, I was forced to use it because I was writing books about it. Over time, I actually began to like Excel 2007. By the time the final version was released, I got to the point where I dreaded having to use Excel 2003 rather than 2007. Now that I’m familiar with just about every nook and cranny of Excel 2007, I have a difficult time remembering where the Excel 2003 commands are.

The chart here roughly depicts my past 17 months with Excel 2007 in terms of “likeability.” I don’t know how typical this curve is. I suspect that it will be linear for some; they’ll start out hating it, and gradually learn to love it.

But I do think that anyone who uses this product for any length of time will not want to go back to a previous version. Sure, it has some problems. But its benefits outweigh the problems by a large margin as far as I’m concerned.

Musical Excel

I found some code that uses API functions to play MIDI music at a French site. I adapted the code so it’s easy to use. Copy the code below and paste it into a VBA module.

Option Explicit
Private Declare Function midiOutOpen Lib “winmm.dll” _
    (lphMidiOut As Long, _
    ByVal uDeviceID As Long, _
    ByVal dwCallback As Long, _
    ByVal dwInstance As Long, _
    ByVal dwFlags As Long) As Long
 
Private Declare Function midiOutClose Lib “winmm.dll” _
    (ByVal hMidiOut As Long) As Long
 
Private Declare Function midiOutShortMsg Lib “winmm.dll” _
    (ByVal hMidiOut As Long, _
    ByVal dwMsg As Long) As Long
 
Private Declare Sub Sleep Lib “Kernel32″ (ByVal dwMilliseconds As Long)
Dim hMidiOut As Long
Public lanote As Long
 
Sub PlayMIDI(voiceNum, noteNum, Duration)
    Dim Note As Long
    On Error Resume Next
    midiOutClose hMidiOut
    midiOutOpen hMidiOut, 0, 0, 0, 0
    midiOutShortMsg hMidiOut, RGB(192, voiceNum – 1, 127)
    lanote = 12 + CLng(noteNum)
    Note = RGB(144, lanote, 127)
    midiOutShortMsg hMidiOut, Note
    Sleep (Duration)
    midiOutClose hMidiOut
 End Sub

The PlayMIDI Sub procedure accepts three arguments, and plays a single note. The argument are:

  • voiceNum: A number from 1-128 that represents the instrument sound. Here’s a list of the MIDI voice numbers.
  • noteNum: A number that indicates the note to play. For reference, C is 0, 12, 24, 36, etc. C# is 1, 13, 25, 37, etc.
  • Duration: A number that indicates how long to play the note, in milliseconds (1,000 equals 1 second).

To play around with this, I set up a worksheet that has a 4-column list of notes. A lookup table provides the actual note letters for the values in column B. In the figure, I have it set up to generate random notes and durations. Then, a simple macro plays the song.

 

Then, a simple macro plays the song represented by the worksheet data.

Sub TestMidi()
    Dim r As Long
    ActiveSheet.Calculate
    For r = 2 To Application.CountA(Range(“A:A”))
        Call PlayMIDI(Cells(r, 1), Cells(r, 2), Cells(r, 3))
    Next r
 End Sub

By the way, I have no idea how this code works. Using the RGB function is a mystery to me. One final note. Avoid stopping the code by pressing Ctrl+Break. If you do that, you may get a stuck note that requires closing Excel.

Dynamic Sorting With a UDF

Coincidentally, this post relates to two previous posts — which happen to be unrelated to each other:

Excel 2007 has a new Sort object. I discovered that this object does its thing even when it’s called by a UDF function used in a formula. In other words, yet another example of a UDF that changes the spreadsheet.

The figure below shows an example. The data in columns A and B is sorted immediately (by column B) whenever a change is made within the range A1:B50. So, if I change Anne’s score to 71, her row would move down between Paul and Oscar’s data.

Cell D2 contains a formula that uses the SortRange function:

Function SortRange(rngToSort As Range, Optional order)
‘   Excel 2007 only
‘   Order: 1=ascending, 2=descending
   If IsMissing(order) Then order = 1
    With rngToSort.Parent.Sort
        .SortFields.Clear
        .SortFields.Add Key:=rngToSort.Cells(1, 2), order:=order
        .SetRange rngToSort
        .Header = xlYes
        .Apply
        SortRange = .Rng.Address(False, False) ‘Range actually sorted
   End With
End Function

This function takes two arguments: The range to sort, and the sort order (an optional argument). The Sort object is a member of the Worksheet, so I use the Parent property to get that object, and enclose all of the object manipulations inside of a With-End With structure.

The function does this…

  1. Clears the SortFields (which is a collection)
  2. Adds a key. In this case, the sort key is the second column of the data, and it’s sorted in the order specified by the second argument.
  3. Gets the range to be sorted, using the SetRange method.
  4. Specifies that the range has a header row, via the the Header property.
  5. Does the sorting, via the Apply method
  6. Gets the address of the range that was actually sorted, and assigns it to the function (this is the text returned by the formula). The function is passing a 50-row range, but it only has 13 rows of data.

You can accomplish the same effect with a WorksheetChange event, but this technique allows the user to specify some options without modifying the code. The function, of course, could include more arguments that control how the sorting is done.

Practical applications, anyone?

Beta Testers For PUP v7?

Assuming I work non-stop all weekend, I should have a beta version of my PUP v7 add-in ready to go next week. This version works only with Excel 2007.

If you’re interested in doing some informal beta testing, please let me know.

You’ll need a copy of Excel 2007, of course. If you’re able to devote a few hours trying out the utilities (and reporting the bugs to me), I’d appreciate your help. I’m especially interested in recruiting someone who can test it using a non-English version of Excel.

Here’s a tiny image of the PUP v7 ribbon (click to enlarge):

You can view a list of the PUP changes here: What’s New in PUP v7.

Creating that ribbon UI was the biggest challenge. It even includes a dynamicMenu control (for the PUP Bookmarks) which took me about 4-5 hours to figure out and debug. Doing the same thing with a CommandBar control would have taken about 30 minutes.

Modifying Shapes (and Charts) With UDFs

Do you know that you can write VBA worksheet functions that modify shapes on a worksheet? I didn’t know this until about an hour ago.

Paste the following UDF into a VBA module:

Function ModifyShape(ShapeNumber, ShapeType, Vis)
    With ActiveSheet.Shapes(ShapeNumber)
        .AutoShapeType = ShapeType
        .Visible = Vis
    End With
End Function

Then, add a shape to the worksheet and enter this formula into any cell:

=ModifyShape(1,55,TRUE)

The first argument is the shape’s index number. The second argument is a value that represents the shape’s type (values from 1-138 are supported). The last argument determines whether the shape is visible. The arguments, of course, could use cell references. Change the second argument and watch the shape change its shape. Change the third argument to FALSE and watch the shape disappear.

This sort of thing is a lot more useful in Excel 2007, because embedded charts are contained in shapes. Therefore, you can write formulas that manipulate the chart object properties such as size, position, and visibility. Even better, your UDF can even access the Chart object contained in the shape, and manipulate that. Here’s a simple example:

Function ChangeChartType(CName, CType)
‘   Excel 2007 only
   ActiveSheet.Shapes(CName).Chart.ChartType = CType
End Function

This function assumes an embedded chart. It uses two arguments: the ChartObject’s name, and the chart type (e.g., 5 is xlPie, -4100 is xl3DColumn, etc.).

Even better — a function that allows you to specify a chart’s min and max scale values:

Function ChangeChartAxisScale(CName, lower, upper)
‘   Excel 2007 only
   With ActiveSheet.Shapes(CName).Chart.Axes(xlValue)
        .MinimumScale = lower
        .MaximumScale = upper
    End With
End Function

The ability to adjust a chart’s axes based on calculations has long been on the Excel wish list. I wonder if Microsoft even knows this is now possible?

Weekend Music Post

Dick forgot to post a weekend free-for-all-thing last Friday, so I’m stepping in and taking over.

Let’s talk about music.

Who plays an instrument? Dick hasn’t talked about his piano playing in quite a while. How’s it coming, Dick?

If you’d visit my house, you’d find about 12 guitars, five banjos, a mandolin, two ukuleles, an electronic keyboard, a dulcimer, a fiddle, and a few harmonicas. I dabble a lot, but I’m a master of nothing. But I enjoy it, and that’s what it’s all about.

What type of music do you like to listen to?

Is there anyone who has no interest in music?

What Level Are You?

When I make a post here, I always wonder how many people actually benefit. Is it too advanced? Too simplistic? In other words, I have no idea what kind of Excel users actually read this blog.

So how about a poll? Please reply, even if you don’t actively participate here by posting comments.

Writing To A Range Using VBA

If you need to use a VBA procedure to write values to a range, most people would probably create a loop and write the values one cell at a time. Like this:

Sub LoopFillRange()
'   Fill a range by looping through cells
   Dim CellsDown As Long, CellsAcross As Long
    Dim CurrRow As Long, CurrCol As Long
    Dim StartTime As Double
    Dim CurrVal As Long

'   Change these values
   CellsDown = 500
    CellsAcross = 200
   
    Cells.Clear
'   Record starting time
   StartTime = Timer

'   Loop through cells and insert values
   CurrVal = 1
    Application.ScreenUpdating = False
    For CurrRow = 1 To CellsDown
        For CurrCol = 1 To CellsAcross
            Range("A1").Offset(CurrRow - 1, CurrCol - 1).Value = CurrVal
            CurrVal = CurrVal + 1
        Next CurrCol
    Next CurrRow

'   Display elapsed time
   Application.ScreenUpdating = True
    MsgBox Format(Timer - StartTime, "00.00") & " seconds"
End Sub

>On my system, writing 100,000 values using a loop takes 9.73 seconds.

A faster way to accomplish the task is to put the value in an array, and then transfer the array to the worksheet. The procedure below writes 100,000 values in 0.16 second — about 60 times faster than the looping method.

Sub ArrayFillRange()
'   Fill a range by transferring an array
   Dim CellsDown As Long, CellsAcross As Long
    Dim i As Long, j As Long
    Dim StartTime As Double
    Dim TempArray() As Double
    Dim TheRange As Range
    Dim CurrVal As Long

'   Change these values
   CellsDown = 500
    CellsAcross = 200

    Cells.Clear
'   Record starting time
   StartTime = Timer

'   Redimension temporary array
   ReDim TempArray(1 To CellsDown, 1 To CellsAcross)

'   Set worksheet range
   Set TheRange = Range(Cells(1, 1), Cells(CellsDown, CellsAcross))

'   Fill the temporary array
   CurrVal = 0
    Application.ScreenUpdating = False
    For i = 1 To CellsDown
        For j = 1 To CellsAcross
            TempArray(i, j) = CurrVal
            CurrVal = CurrVal + 1
        Next j
    Next i

'   Transfer temporary array to worksheet
   TheRange.Value = TempArray

'   Display elapsed time
   dApplication.ScreenUpdating = True
    MsgBox Format(Timer - StartTime, "00.00") & " seconds"
End Sub

Displaying CommandBar FaceID Images

If you create custom menus or toolbars, you’re probably familiar with the FaceId property — a numeric value that specifies an image. People have come up with lots of ways to display the FaceId images. Here’s one I haven’t seen before.

Start with an empty workbook, and paste the ShowFaceIDs procedure into a VBA module. Adjust the ID_START and ID_END constants to specify which images you’d like to see (it’s currently set up to show the first 500). Run the macro, and the 18 x 18 pixel images are displayed on the active worksheet. Click an image and you’ll see it’s FaceID value in the Name box.

Sub ShowFaceIDs()
    Dim NewToolbar As CommandBar
    Dim TopPos As Long, LeftPos As Long
    Dim i As Long, NumPics As Long

‘- – - – - Change These – - – - -
   Const ID_START As Long = 1
    Const ID_END As Long = 500
‘- – - – - – - – - – - – - – - – - – - -

‘   Delete existing TempFaceIds toolbar if it exists
   On Error Resume Next
    Application.CommandBars(“TempFaceIds”).Delete
    On Error GoTo 0

‘   Clear the sheet
   ActiveSheet.Pictures.Delete
    Application.ScreenUpdating = False
   
‘   Add an empty toolbar
   Set NewToolbar = Application.CommandBars.Add _
        (Name:=“TempFaceIds”)

‘   Starting positions
   TopPos = 5
    LeftPos = 5
    NumPics = 0
   
    For i = ID_START To ID_END
        On Error Resume Next
        NewToolbar.Controls(1).Delete
        With NewToolbar.Controls.Add(Type:=msoControlButton)
            .FaceId = i
            .CopyFace
        End With
        On Error GoTo 0
       
        NumPics = NumPics + 1
        ActiveSheet.Paste
        With ActiveSheet.Shapes(NumPics)
            .Top = TopPos
            .Left = LeftPos
            .Name = “FaceID “ & i
            .PictureFormat.TransparentBackground = True
            .PictureFormat.TransparencyColor = RGB(224, 223, 227)
        End With
       
‘       Update top and left positions for the next one
       LeftPos = LeftPos + 16
        If NumPics Mod 40 = 0 Then
            TopPos = TopPos + 16
            LeftPos = 5
        End If
    Next i
    ActiveWindow.RangeSelection.Select
    Application.CommandBars(“TempFaceIds”).Delete
End Sub

Even if you don’t create CommandBars, you might have fun looking at all the little pictures.

Posting From Word 2007

This is just a test post, using Word 2007 Beta. I tried to get it to work with my own blog, but no luck. WordPress is one of the supported options, and it’s supposed to work with the MetaBlogger API. But either I can’t figure out what their cryptic dialogs want, or it just doesn’t work.

Let’s try a list:

  • Bullet Point #1
  • Another one
  • Final Bullet Point

    Does indented text get tagged as blockquote?

How about a picture?

http://j-walkblog.com/images/absolutopenmic.jpg

Let’s try a table:

Column 1

Column 2

Column 3

Row 1, Column 1

Row 1, Column 2

Row 1, Column 3

Row 2, Column 1

Row 2, Column 2

Row 2, Column 3

 

Here’s a code listing using the [ vb ] tags.

 <pre>Sub AddToShortCut()
‘ Adds a menu item to the Cell shortcut menu
   Dim Bar As CommandBar
    Dim NewControl As CommandBarButton
    DeleteFromShortcut
    Set Bar = CommandBars(“Cell”)
    Set NewControl = Bar.Controls.Add _
      (Type:=msoControlButton, ID:=1, _
      temporary:=True)
    With NewControl
      .Caption = “Toggle &amp;Word Wrap”
      .OnAction = “ToggleWordWrap”
      .Picture = Application.CommandBars.GetImageMso(“WrapText”, 16, 16)
      .Style = msoButtonIconAndCaption
   End With
End Sub
</pre>
<p>

(I updated this using Live Writer — which at least lets you edit the HTML code.)

 

 

And it handles images, too.