Code For Excel And Outlook Blog is the product of NYC native Jimmy Peña. What can you expect to find here? Lots of Excel and Outlook VBA code, Excel formulas and tricks, and other goodness as I think of it. Do not forget to subscribe to make sure you don't miss out!

Take advantage of Intellisense when writing late bound code
November 17, 2008 – 9:51 am by JP

    Many people are familiar with the page on binding found on Dick Kusleika's website "Dick's Clicks" which advises the following:

To get the most out of the VBA development environment and still write robust code, you should write the code early bound, but change it to late bound before distributing it. Even if you write it for personal use only, it makes sense to convert it to late bound. Someday you will have a different computer or send it to your brother and it won't work because they will have an earlier version. If you're a die-hard procrastinator like me, you will be cursing yourself for not converting to late bound sooner.

    The page also provides a convenient checklist for getting this done:

Late Bound Conversion Checklist

  • Change all declarations from Outlook objects to the generic Object data type
  • Change Set statements to GetObject or CreateObject
  • Change any built-in constants to their intrinsic values
  • Add optional arguments that have a default value

    In this post I am going to go through these steps and show you exactly how to convert early bound to late bound code, taking advantage of Intellisense along the way, to get the best of both worlds, if you will.

    First, here's a small sub that lists all of the files in a folder in column A. Before I wrote the code, I set a reference to the Microsoft Scripting Runtime. That way, we can take advantage of Intellisense to write our code.

VBA:
  1. Sub ListFiles_1()
  2.  
  3. Dim fso As Scripting.FileSystemObject
  4. Dim filen As Scripting.File
  5. Dim NextRow As Long
  6.  
  7. On Error Resume Next
  8. Set fso = New Scripting.FileSystemObject
  9. On Error GoTo 0
  10.  
  11. If fso Is Nothing Then GoTo ExitProc
  12.  
  13. Dim strFolder As String
  14.  
  15. strFolder = "F:\MyFolder\"
  16.  
  17. NextRow = 1
  18.  
  19. For Each filen In fso.GetFolder(strFolder).Files
  20.   Cells(NextRow, 1).Value = filen.Name
  21.   NextRow = NextRow + 1
  22. Next filen
  23.  
  24. ExitProc:
  25. Set fso = Nothing
  26. End Sub

    You'll notice that Intellisense tries to help you when you enter the following statements relating to the Scripting Runtime:

VBA:
  1. Dim fso As Scripting.FileSystemObject
  2. Dim filen As Scripting.File
  3. Set fso = New Scripting.FileSystemObject
  4. For Each filen In fso.GetFolder(strFolder).Files
  5.   Cells(NextRow, 1).Value = filen.Name

    If you started out with the intention of writing late-bound code, and immediately started declaring your variables as Object, you would need to be very familiar with your objects to make sure you access their properties correctly. When its early bound, the computer (mostly) does that for you.

    Now that we've got our early bound code written, and Intellisense has helped us all it can, we start changing the code so it's late bound.

    As you recall from above, the first step is: Change all declarations from Outlook objects to the generic Object data type. So we'll change the declarations to Object for all of the Scripting objects.

VBA:
  1. Sub ListFiles_2()
  2.  
  3. Dim fso As Object
  4. Dim filen As Object
  5. Dim NextRow As Long
  6.  
  7. On Error Resume Next
  8. Set fso = New Scripting.FileSystemObject
  9. On Error GoTo 0
  10.  
  11. If fso Is Nothing Then GoTo ExitProc
  12.  
  13. Dim strFolder As String
  14.  
  15. strFolder = "F:\MyFolder\"
  16.  
  17. NextRow = 1
  18.  
  19. For Each filen In fso.GetFolder(strFolder).Files
  20.   Cells(NextRow, 1).Value = filen.Name
  21.   NextRow = NextRow + 1
  22. Next filen
  23.  
  24. ExitProc:
  25. Set fso = Nothing
  26. End Sub

    Even though we haven't fully converted the code yet, it will still work because we still have that reference to Microsoft Scripting Runtime. We want it to be fully late-bound, so we'll go to step 2: Change Set statements to GetObject or CreateObject. In addition, we'll remove that reference to Microsoft Scripting Runtime.

VBA:
  1. Sub ListFiles_Final()
  2.  
  3. Dim fso As Object
  4. Dim filen As Object
  5. Dim NextRow As Long
  6.  
  7. On Error Resume Next
  8. Set fso = CreateObject("Scripting.FileSystemObject")
  9. On Error GoTo 0
  10.  
  11. If fso Is Nothing Then GoTo ExitProc
  12.  
  13. Dim strFolder As String
  14.  
  15. strFolder = "F:\MyFolder\"
  16.  
  17. NextRow = 1
  18.  
  19. For Each filen In fso.GetFolder(strFolder).Files
  20.   Cells(NextRow, 1).Value = filen.Name
  21.   NextRow = NextRow + 1
  22. Next filen
  23.  
  24. ExitProc:
  25. Set fso = Nothing
  26. End Sub

    Steps 3 and 4 don't really apply to this example, but here's a small example if you were writing late-bound code for Outlook to create a MailItem.

Change:

VBA:
  1. Dim olApp As Outlook.Application
  2. Set olApp = Outlook.Application
  3. olApp.CreateItem(olMailItem)

to:

VBA:
  1. Dim olApp As Object
  2. Set olApp = CreateObject("Outlook.Application")
  3. olApp.CreateItem(0)

According to the list of Outlook 2003 Constants, the value of olMailItem is 0 (zero), so the late-bound version uses that value instead of the constant.

Share and Enjoy:
  • StumbleUpon
  • Technorati
  • Digg
  • Google
  • del.icio.us
  • MisterWong

Export Excel Range to a Picture File
November 13, 2008 – 9:47 pm by JP

    If you ever needed to capture a range, for pasting into a document or email, you'll need to save it as an image first. If so, you might find this routine useful. It takes a contiguous range, starting in A1, and creates a GIF file with a snapshot of the range.

    A temporary, intermediate chart is used, so we can use its Export Method to create the GIF. The chart is then deleted. Through trial and error, I guessed at the appropriate chart size. I'm using Windows XP with a resolution of 1024x768, so you might need to adjust the constants' values as appropriate.

VBA:
  1. Sub CopyRangeToGIF()
  2. ' save a range from Excel as a picture
  3. Dim rng As Excel.Range
  4. Dim cht As Excel.ChartObject
  5.  
  6. Const strPath As String = "C:\"
  7.  
  8. Application.ScreenUpdating = False
  9.  
  10. Set rng = Range("A1").CurrentRegion
  11.  
  12. rng.CopyPicture xlScreen, xlPicture
  13.  
  14. Set cht = ActiveSheet.ChartObjects.Add(0, 0, rng.Width + 10, rng.Height + 10)
  15.  
  16. cht.Chart.Paste
  17.  
  18. cht.Chart.Export strPath & "myfile.gif"
  19.  
  20. cht.Delete
  21.  
  22. ExitProc:
  23. Application.ScreenUpdating = True
  24. Set cht = Nothing
  25. Set rng = Nothing
  26. End Sub

    This code will only create the picture. You'll probably want to do something with it.

    If you want to add it to a Word document, try this:

VBA:
  1. Dim wrdApp As Object ' Word.Application
  2. Dim wrdDoc As Object ' Word.Document
  3. Set wrdApp = CreateObject("Word.Application")
  4. 'wrdApp.Visible = True
  5. Set wrdDoc = wrdApp.Documents.Add
  6. wrdDoc.InlineShapes.AddPicture "C:\myfile.gif"

    To send it as an attachment in an Outlook email, try this:

VBA:
  1. Dim olApp As Object ' Outlook.Application
  2. Dim Msg As Object ' Outlook.MailItem
  3. Set olApp = CreateObject("Outlook.Application")
  4. Set Msg = olApp.CreateItem(0)
  5.  
  6. With Msg
  7. .To = "coworker@mycompany.com"
  8. .Body = "Check out this range!"
  9. .Attachments.Add "c:\myfile.gif"
  10. .Send
  11. End With

    If you want to send the range in the body of an email, you're better off using the technique described here.

    And last but not least, if you wanted the picture in a PowerPoint slide, this code will create a new presentation and insert the picture into a slide. Unlike the code snippets above for Outlook and Word, which use the saved GIF copy, for PowerPoint you'll need to call this code right after executing the Range.CopyPicture Method.

VBA:
  1. Dim pptApp As Object ' PowerPoint.Application
  2. Dim pptPres As Object ' PowerPoint.Presentation
  3. Dim pptSlide As Object ' PowerPoint.Slide
  4.  
  5. Set pptApp = CreateObject("PowerPoint.Application")
  6. Set pptPres = pptApp.Presentations.Add
  7. Set pptSlide = pptPres.Slides.Add
  8.  
  9. pptSlide.Shapes.Paste

[Update 11/14/2008]: CopyRangeToGIF sub was updated based on suggestion from Jon Peltier. No need for complicated calculations!

Share and Enjoy:
  • StumbleUpon
  • Technorati
  • Digg
  • Google
  • del.icio.us
  • MisterWong

Shipment Tracking Userform for batch processing
November 12, 2008 – 8:39 pm by JP

    In my previous post Shipment Tracking Add-In for Excel I introduced an add-in UDF for tracking package shipments from UPS, DHL and FedEx. A few more caveats about that function:

  • It appears to only work for domestic (U.S.) shipments.
  • It does work for FedEx tracking numbers, but only the 12-digit version. There is a 22-digit version of the tracking number which you need to pass to the function directly as an argument (not as a cell reference).

    If anyone would like me to include international shipments, I will certainly check out that functionality and see if it is possible. For more information in general about the function, check out the post from the link above.

    I've completed a userform-based add-in that uses the functionality from the ShipTrack add-in. It employs a batch feature which allows you to track packages in bulk.

    In order to use the UI, you will need to install the ShipTrack add-in from my previous post, in order for the new add-in to access the previous add-in's functions. Here is a link to the add-in: ShipTrack Add-in.

You must install the ShipTrack add-in before installing the new UI add-in. Also, both add-ins must be in the same folder.

    And of course here is a link to the new add-in: ShipTrackUI Add-in

Requires MSXML 6.0 as follows: c:\windows\system32\msxml6.dll

    Just like the ShipTrack add-in, you'll need to install this one in the traditional way (go to Tools>Add-Ins and browse for the XLA file). After installing the ShipTrackUI add-in, you will see an addition to your Tools Menu: Batch Shipment Tracking. Here is a screenshot of the form that appears when you select that option:

shiptrack-userform-300x164

    Click 'Open File' to select a file for processing. Your target worksheet must be formatted with two columns: A list of tracking numbers in column A (with header in A1) and a list of the carriers in column B (with header in B1). There cannot be any blank rows or missing data. Here is a sample:

shiptrackui-test-file-251x300

    Click 'Submit' to begin processing. There is a label control on the form which updates to show you progress. After it is finished, the form looks like this:

shiptrack-userform2-300x164

    And your spreadsheet will be updated in column C with the information provided by the appropriate website:

shiptrackui-test-file2-265x300

    The form is very basic, so please let me know if you run into any runtime errors or other problems. Your feedback is critical!

Enjoy,
JP

Share and Enjoy:
  • StumbleUpon
  • Technorati
  • Digg
  • Google
  • del.icio.us
  • MisterWong

Shipment Tracking Add-In for Excel
November 7, 2008 – 12:20 pm by JP

    Have you ever wanted to incorporate shipment tracking into your spreadsheet application?

    I've just completed another add-in for Excel -- this one is a UDF that lets you track shipments you've sent via DHL, UPS or FedEx.

To install:

    First, download and save the add-in somewhere special. Here is the file: Shipment Tracking Add-In For Excel

    Start Excel and go to Tools > Add-Ins... and browse to the folder where you placed the file. Don't double-click on the file to install it. (I wrote the add-in to allow you to do this, but the functionality doesn't seem to be working as intended, so it's better to just install it the way you are supposed to install add-ins anyway.)

addindialogbox-263x300

    After installing the add-in, you'll have an additional function in the DDE/External category. You can access the function the same way as any other function in Excel.

  • In the worksheet, press Shift-F3, type "ShipTrack" or choose DDE/External in the "Select a category" dropdown
  • Start typing "=ShipTrack(" in a worksheet cell
  • Go to Insert>Function or click Insert Function next to the formula bar.

ddeexternalfunctions-300x255

Usage:

=ShipTrack(Tracking_Number,Carrier)

    Where Tracking_Number is a string literal in quotes, or a cell reference which contains a UPS or DHL tracking number, and Carrier is a string literal or cell reference contains the name of the carrier (either "UPS" or "DHL" or "FedEx"). These parameters can also be entered directly into the function, instead of as a cell reference.

    FedEx tracking numbers are too long to fit in a cell (they get truncated after 15 digits) so you'll need to enter them directly into the formula as such:

=ShipTrack("093974897039389749","FedEx")

    The following conditions will cause an error message:

  • If you (accidently) try to pass a FedEx number to the function as a cell reference;
  • If you (accidently) enter blanks or leave out any of the arguments
  • If you pass an unknown or misspelled carrier name;
  • If the tracking number cannot be found by the carrier, for example if it is too old or invalid;
  • If one of the three carriers changes their website and the functionality breaks.
  • If something else happens that I didn't plan for ;-)

    If you do get an error message, you'll need to check which of the above conditions occurred. You might have to manually track your package on the carrier's website and see what happens.

    The carrier name is not case sensitive, so you can use all lower case, CamelCase, aNy CaSe YoU wANT. :)

How it works:

    The function uses a late-bound XMLHTTP object to perform a web query of the given tracking number. It is much faster than using the Shdocvw.InternetExplorer object, but there is still a slight delay because it is late-bound.

Requires MSXML 6.0 as follows: c:\windows\system32\msxml6.dll

    It is volatile so it will recalculate repeatedly as the worksheet is used. I don't recommend setting calculation to manual, so you should delete the formula after using it, or you can comment it out in the worksheet by editing the cell (press F2, then Home) and pre-pending an apostrophe, so the cell would look like '=ShipTrack(A1,B1). Just remove the apostrophe to re-enter the formula.

    I had a limited number of tracking numbers to use to test out the functionality, please test it out and let me know if you find any bugs, tracking numbers errors where you go to the site and the tracking number was found, etc.

    I'm also planning a userform version, perhaps with a batch upload feature, so stay tuned!

[Update 11/12/2008]: I made it to Pointy Haired Dilbert's Excel Links of the Week, again! Check out Chandoo's post here. Also I should mention that DHL is ceasing U.S. domestic express service on January 30, 2009. So if you or your company is using DHL (and you didn't know by now), you probably want to start looking at other options.

And I would also like to add that FedEx tracking numbers also have a 12 digit format which will work in the formula as described for UPS and DHL. If you use that number instead, you can put it in a cell and pass the cell reference to the ShipTrack function.

--JP

Share and Enjoy:
  • StumbleUpon
  • Technorati
  • Digg
  • Google
  • del.icio.us
  • MisterWong

A simple formula trick to visualize your errors
November 3, 2008 – 2:49 pm by JP

    Here's a neat trick I use to make spotting worksheet errors very simple.

    Wrap your formula in an IF statement that returns the letter "J" if True, or the letter "L" if False, and change the cell's font to Wingdings.

    In the Wingdings font, "J" is a smiley face :) and "L" is a frown :( . So you (or your end users) can get an immediate visual clue as to the success or failure of their formula. It's low-tech but very effective.

    For example, don't just write

=A1=A2

to compare two cells (which just returns a boring TRUE or FALSE to the target cell). Instead, use

=IF(A1=A2,"J","L")

    Don't forget to change the cell's font to Wingdings. Even better, blow it up to a size 20 and make it bold to really stand out. ;-)

    Let's take it even further with conditional formatting. Set up your target cell's conditional formatting as follows:

condformat1-300x144

    Now your smiley face is a happy green color, and the bad formulas get an ugly red color. Perfect for those PHBs who can't be bothered to read a spreadsheet, so they can get a color-coded view. I hope that doesn't dissuade you from using this technique in your own work. ;)

    Here's a related topic for which you can use similar techniques: Creating a non-graphic chart directly in a range

    Now if I want a count of formula errors, I just use the COUNTIF formula as follows. Assuming I filled down my IF formula down an unspecified number of rows in column B,

="Raw errors: "&COUNTIF(B:B,"L")

would return the number of sad faces (i.e. FALSE returns) in that column.

    Typically, though, I'll use the IF formula to evaluate multiple cells in the same row. In that case, I might want to know how many full row errors there are; in other words, how many rows had all of their formulas return FALSE. I'll use SUMPRODUCT for this.

="Full row errors: "&SUMPRODUCT((B2:B5000="L")*(C2:C5000="L"))

or

="Full row errors: "&SUMPRODUCT(--(B2:B5000="L")*--(C2:C5000="L"))

or

="Full row errors: "&SUMPRODUCT(N(B2:B5000="L")*(N(C2:C5000="L")))

    These formulas will compare each pair of cells (B2 & C2, B3 & C3, etc), evaluate whether each one equals "L" (1 for TRUE, 0 for FALSE) and multiplies the values together. Only the rows where both expressions evaluate to TRUE will add 1 to the total.

    For SUMPRODUCT you can't use full column references (in Excel 2003), so I just chose an arbitrarily large range. Adjust as needed. The only difference with the last two formulas is the double unary (--, that's two hyphens) or N() function, which might be required to convert text to values, but for the most part only useful to achieve "Excel-god" status in your office. 8)

Enjoy,
JP

Share and Enjoy:
  • StumbleUpon
  • Technorati
  • Digg
  • Google
  • del.icio.us
  • MisterWong