Archive for the ‘Automation’ Category.

Printing First Page of Email in Outlook

I print out my email. No, I'm not one of the Luddites that prints out every email (or has his secretary do it) because he doesn't understand how to work his email machine. My system for organization is loosely based on Getting Things Done and its derivative 43 Folders. Everything I have to do is represented by at least one piece of paper. So don't try to convince me to quit printing my email, because it's not going to happen.

If I get an email that requires some action in the future, I need one of these pieces of paper to represent that future action. Until recently, I would print the email to serve that purpose. As you already know, email can get quite long and that means I can end up with 10 sheets of paper where I only needed one. When you deal with accountants and lawyers, it's even worse because each of their messages in the thread has a page of disclaimers. Oddly, Outlook's print dialog doesn't let you specify a page range. There are some work arounds, but none of them are suitable for me. I read all my email in plain text and I reply in plain text (HTML is for web pages, not email). Since I'm using plain text, I use Outlook's built-in email editor rather than Word.

After I printed seven pages of an email today, I decided to finally write some code. I put a button on my new email commandbar and hooked it up to this procedure:

Sub PrintOnePage()
   
    Dim mi As MailItem
    Dim sBody As String
    Dim wdApp As Word.Application
   
    Const sORIG As String = "> -----Original Message-----"
   
    If TypeName(Application.ActiveInspector.CurrentItem) = "MailItem" Then 'only mail
        'create a forward to get the header
        Set mi = Application.ActiveInspector.CurrentItem.Forward
        sBody = mi.Body
        sBody = Mid(sBody, InStr(1, sBody, sORIG), 5000) 'Remove inserted signature
        Set wdApp = New Word.Application
       
        With wdApp.Documents.Add
            .Range.Text = sBody
            .PageSetup.LeftMargin = 18 '.25"
            .PageSetup.RightMargin = 18
            .PageSetup.TopMargin = 18
            .PrintOut False, False, wdPrintFromTo, "", "1", "1"
        End With
       
        wdApp.Quit False 'don't save changes
        Set wdApp = Nothing
        mi.Close olDiscard 'don't save changes
        Set mi = Nothing
    End If
   
End Sub

I didn't want to automate Word to do this, but I struggled with other options to limit it to one page. I know that printing out of Outlook puts 60 lines on the first page. However, when I tried to limit the text to the first 60 vbNewLine's, it didn't quite work out. I started to think that maybe Outlook doesn't put a vbNewLine after each line, but rather after each paragraph. I'm still not sure why that didn't work. So I resigned to automate Word and use it's page range feature to limit the print out.

I limit this to MailItems although it may work on other objects. I didn't want to test it. The MailItem I work with is a forwarded copy of the original. When I forward an email it puts the header information at the top of each email in the thread, so I get some needed information on my print out. Unfortunately, it also puts my signature in there, so I have to strip that part out by starting the string at the Original Message part. And I limit the string to 5,000 because that should be more than one page and there's no need to transfer more than that.

I kept getting a type mismatch error when using Word's PrintOut method. At first I thought it was because I was omitting optional arguments, but that really shouldn't be the case when I'm early binding. I seem to remember a problem with optional arguments using late binding - specifically that you have to include all optional arguments up until the one you want to include, then none after that. But I was still getting the error. Inexplicably, Word wants Strings for page numbers. You'll notice that my page numbers are in quotes.

Finally, I close Word without saving changes and discard the forwarded copy of the email.

Copy Paste to External Application

Sometimes I find myself copy-pasting between Excel and another application.
In this example, I have a table of three columns: First Name, Last Name, Birth Date.
My external application has 3 text boxes, one for each of those values.

I can't just copy the 3 cells from Excel and paste them to my App, because they would all end up in the first text box!
But, by running VBA SendKeys with a specially crafted string, I can send keystrokes for tabbing to the 2nd and 3rd text boxes.

I also need to activate the SendKeys procedure only when my cursor is positioned correctly, or things could get messy.

My approach is to run a macro that sits there listening for F6 before activating SendKeys.
I've also included listening for the Esc key, just in case I change my mind.

Declare Function GetAsyncKeyState Lib "user32" (ByVal vKey As Long) As Integer
 
' Virtual-Key Codes http://msdn2.microsoft.com/en-us/library/ms645540(VS.85).aspx
Const VK_F6 = &H75
Const VK_ESC = &H1B
 
Sub Scanning()
    Dim lngRow As Long, str As String
 
    lngRow = Selection.row
    str = Cells(lngRow, 1) & vbTab & Cells(lngRow, 2) & vbTab & _
            Format(Cells(lngRow, 3), "dd-mmm-yyyy")
 
    MsgBox "Click OK, then click the First Name box on the external application, then press F6 on the keyboard"
    WaitAndSend str, VK_F6, VK_ESC
End Sub
 
Sub WaitAndSend(SendString As String, ExecuteKey As Long, CancelKey As Long)
    Do
        DoEvents
        If GetAsyncKeyState(CancelKey) <> 0 Then Exit Do
        If GetAsyncKeyState(ExecuteKey) <> 0 Then
            SendKeys SendString, True
            Exit Do
        End If
    Loop
End Sub

CommandButtons via Visual Basic 6.0

From Ashton. When I run this code in a VB6 program, it puts an OLEObject on an Excel sheet.

Private Sub Command1_Click()
   
    Dim xlApp As Excel.Application
   
    Set xlApp = Excel.Application
    xlApp.Visible = True
    xlApp.Workbooks.Add
   
    xlApp.ActiveSheet.OLEObjects.Add "Forms.Commandbutton.1"
   
    Set xlApp = Nothing
   
End Sub

But in Excel, it doesn't act like an OLEObject. Look at these context menus.

The first one was put there by the VB6 program. I added the second one manually from the Control Toolbox in Excel.

Show Picture toolbar button? What the heck is that? Does anyone know why there's a difference?

Beta testing request

Hi everyone,

I am busy building my very first COM addin for Excel and I've now come to the stage that I need some beta testers.

Who would be willing to run some tests on my new "Excel Formula Reference Auditing Utility" (see screenshot below)?
excelreftool.gif
If interested, send me an email:
info@jkp-ads.com

What's in it for you? a free copy of the tool once the beta is finished.

###EDIT Oct 29, 2007###
I'd like to thank everyone who has volunteered for beta testing. For now, I have sufficient people doing testing, so the subscription is closed.

Regards,

Jan Karel Pieterse
www.jkp-ads.com

VB(A) and InternetExplorer and XMLHttp

Over the past few weeks, I've had reason to explore the use of VBA to access information on web pages and through web services using both InternetExplorer and XMLHttp. While my study of the two is far from exhaustive, I decided to document the research for general consumption expecting it to take a few hours. As I wrote more issues cropped up and the "few hours" project took several days. But, it's finally uploaded at
VBA & web services
http://www.tushar-mehta.com/publish_train/xl_vba_cases/vba_web_pages_services/index.htm

Excel as a Really Bad Database

Do you know what's worse than having a bunch of data in an Excel workbook that really should be in a relational database? Having a bunch of data in multiple Excel workbooks that really should be in a relational database. It's the Excel-workbook-as-a-record model and it's how we currently store our quotes.

Each quote is a separate Excel file and all the files are stored in a folder. I need some information out of these files, so I have to create my own table. I set a reference to the Microsoft Scripting Runtime dll so that I can use the FileSystemObject.

tools references excel scripting runtime

Now I can loop through all the files in the folder.

Sub GetQuotes()
   
    Dim fso As Scripting.FileSystemObject
    Dim dtOldest As Date
    Dim oFile As Scripting.File
    Dim sPath As String
    Dim wb As Workbook
   
    Set fso = New Scripting.FileSystemObject
    dtOldest = Date - 30
    sPath = "S:\AIMUSA\Sales\Quotes\AIM Quotes\"
   
    For Each oFile In fso.GetFolder(sPath).Files
        If UCase(Right$(oFile.Name, 3)) = "XLS" Then
            If oFile.DateCreated> dtOldest Then
                WriteQuotes oFile
            End If
        End If
    Next oFile
 
End Sub

I only want Excel files, and get them by looking at the last three characters of the file name. I also only want those files that were created in the last month, or so. This was the primary reason why I didn't use Application.FileSearch. FileSearch has a LastModified property, but it doesn't seem very flexible. I could use msoLastModifiedLastMonth, and that would have worked well today, but it would not have worked so well on, say, August 15th. I'm also not really that interested in the date it was last modified and I didn't see any facility for creation date in FileSearch.

The down side to using the FileSystemObject is that I end up looping through 2,500 files. I'm not sure how to limit the number it loops through.

Here's the WriteQuotes sub, although not really that interesting:

Sub WriteQuotes(oFile As Scripting.File)
   
    Dim wb As Workbook
    Dim rStart As Range
    Dim rQuoteParts As Range
    Dim rCell As Range
    Dim lRow As Long
   
    Set wb = Workbooks.Open(oFile.Path)
    Set rStart = wshQuotes.Range("A65536").End(xlUp).Offset(1, 0)
    Set rQuoteParts = wb.Sheets(1).Range("A21", wb.Sheets(1).Range("A45").End(xlUp))
    lRow = 0
   
    For Each rCell In rQuoteParts.Cells
        rStart.Offset(lRow, 0).Value = wb.Sheets(1).Range("C9").Value
        rStart.Offset(lRow, 1).Value = oFile.DateCreated
        rStart.Offset(lRow, 2).Value = rCell.Offset(0, 1).Value
        rStart.Offset(lRow, 3).Value = rCell.Offset(0, 7).Value
        lRow = lRow + 1
    Next rCell
   
    wb.Close False
   
End Sub

Note that the Path property of the Scripting.File object includes the filename, which is different than the Path property of the Excel.Workbook object.