Subject: Problems using MSHTML
Posted By: maccas Post Date: 11/1/2004 9:40:37 AM
Hi all,

I'm currently working on an application based in Excel's VBA which uses the little-documented MSHTML COM tlb. The app logs onto a webiste for me, navigates through various pages and then downloads certain information from the website and uploads the information into my spreadsheet.

The program used to run absolutely fine, looping through all the buttons on the download page until it found the desired download button and then telling the website that the button had been clicked by using htmlButton.Click(). However Windows XP SP 2 detects this sort of behaviour as programatic downloading, which is now specifically blocked. For various reasons I'd rather not modify my security settings such that this behaviour is no longer blocked.

So I came up with the idea of using Win API calls to move the mouse over the button and force a click (Interet Explorer can't distinguish this behaviour from user action and so this is not blocked). I have worked a solution where I position and size the Internet Explorer window on my screen such that I know which pixel location need to move the mouse to in order to find the download button. The problem is that the layout of the website might change.

I've had a look through the object browser and found the TransformPoint method of the IDisplayServices interface which would appear to give me the ability to get a run-time co-ordinate location of any webpage element. However, I can't initialise the IDisplayServices object and consequently can't use its methods. Could anyone point me in the right direction or suggest a better way round this?

NB the MSDN website seems to suggest I need to use the QueryInterface method on IHTMLDocument2 but I get a compile error: Interface method restricted when I try to use it.

Any help would be appreciated,
Maccas
Reply By: maccas Reply Date: 11/3/2004 7:04:48 AM
I'm not sure if anyone cares but for completenesses sake I thought I'd post the answer to my earlier query.

It looks like the IDisplayServices interface can't be accessed when scipting with VBA - you have to be using C / C++. There were sevral more obstacles to getting this done but in answer to my specific query I needed to use the getBoundingClientRect mthod on the HTMLInputElement.

Below is some sample code which demonstrates the desired method by opening an internet explorer window, navigating to Google, putting something in the search box, finding the location of the search button, moving the move over the button & simulating a mouse click.

To get the code running in a VBA project you'll need to add references to Microsoft HTML Object Library (MSHTML) & Microsoft Internet Controls (SHDocVW).

Anyway, enjoy ...

Option Explicit

Private Type myRECT
    Left As Long
    Top As Long
    Right As Long
    Bottom As Long
End Type

Private Enum CoordType
    Absolute
    Pixels
End Enum

' User32 API functions used to determine screen resolution
Declare Function GetDesktopWindow Lib "User32" () As Long
Declare Function GetWindowRect Lib "User32" (ByVal hWnd As Long, rectangle As myRECT) As Long

' User32 API Mouse functions
Private Declare Sub mouse_event Lib "User32" (ByVal dwFlags As Long, ByVal dx As Long, ByVal dy As Long, ByVal cButtons As Long, ByVal dwExtraInfo As Long)

' Mouse Event Flags
Const MOUSEEVENTF_LEFTDOWN = &H2
Const MOUSEEVENTF_LEFTUP = &H4
Const MOUSEEVENTF_MOVE = &H1
Const MOUSEEVENTF_ABSOLUTE = &H8000

Sub Test()

Dim IExp As SHDocVw.InternetExplorer

Dim hDoc As MSHTML.HTMLDocument
Dim hCol As MSHTML.IHTMLElementCollection
Dim hInp As MSHTML.HTMLInputElement
Dim hPoint As MSHTML.tagPOINT

    Set IExp = New SHDocVw.InternetExplorer
    IExp.Visible = True
    IExp.navigate "http://www.google.co.uk"
    
    Do Until IExp.Busy = False
        DoEvents
    Loop
    
    Set hDoc = IExp.document
    
    ' Find the "search for" input box on the page
    Set hCol = hDoc.getElementsByTagName("input")
    For Each hInp In hCol
        
        If hInp.Name = "q" Then
            hInp.Value = "Test" ' Put in something to look for
            Exit For
        End If
        
    Next hInp
    
    ' Find the search button on the page
    Set hCol = hDoc.getElementsByTagName("input")
    For Each hInp In hCol
          
        If hInp.DefaultValue = "Google Search" Then
            
            ' Scroll IE to top left hand corner
            hDoc.parentWindow.scroll 0, 0
            
            ' Get coordinate of button
            hPoint = GetCoord(hDoc, hInp, Absolute)
            
            ' Move mouse
            mouse_event MOUSEEVENTF_ABSOLUTE + MOUSEEVENTF_MOVE, hPoint.X, hPoint.Y, 0, 0
            
            ' Simulate click
            mouse_event MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0
            mouse_event MOUSEEVENTF_LEFTUP, 0, 0, 0, 0
            
            Exit For
            
        End If
        
    Next hInp
    

End Sub

Private Function GetCoord(hDoc As MSHTML.HTMLDocument, hEle As MSHTML.HTMLInputElement, Output As CoordType) As MSHTML.tagPOINT

Dim ScreenRes As MSHTML.tagPOINT
Dim hIRectEle As MSHTML.IHTMLRect
Dim Point As MSHTML.tagPOINT
    
    ' Get the screen resolution
    ScreenRes = GetScreenResolution
    
    Set hIRectEle = hEle.getBoundingClientRect
    
    ' Find middle of input element
    Point.X = (hIRectEle.Left + hIRectEle.Right) / 2
    Point.Y = (hIRectEle.Top + hIRectEle.Bottom) / 2
    
    ' Add in offset for where the internet explorer window is located on screen
    Point.X = Point.X + hDoc.parentWindow.screenLeft
    Point.Y = Point.Y + hDoc.parentWindow.screenTop
    
    ' Convert to absolute coords, if necessary
    If Output = Absolute Then
        Point.X = (Point.X / ScreenRes.X) * 65000
        Point.Y = (Point.Y / ScreenRes.Y) * 65000
    End If
    
    GetCoord = Point

End Function

Function GetScreenResolution() As MSHTML.tagPOINT

Dim R As myRECT
Dim hWnd As Long
Dim RetVal As Long
    
    ' Win API calls
    hWnd = GetDesktopWindow()
    RetVal = GetWindowRect(hWnd, R)
    
    GetScreenResolution.X = (R.Right - R.Left)
    GetScreenResolution.Y = (R.Bottom - R.Top)
    
End Function
Reply By: savigans@yahoo.com Reply Date: 2/15/2006 10:32:23 AM
Hello Maccas,

First of, that was a great article, very useful to me. Thanks!

I'm having some problem with it which I was wondering if you could help me out with. I got the following exception when trying to get the parent window of the current document. Any ideas why? Your help is greatly appreciated.

Thanks,
-Savitha

Unhandled Exception: System.Runtime.InteropServices.COMException (0x80040154): Class not registered (Exception from HRESULT: 0x80040154 (REGDB_E_CLASSNOTREG))
   at System.RuntimeType.ForwardCallToInvokeMember(String memberName, BindingFlags flags, Object target, Int32[] aWrapperTypes, MessageData& msgData)
   at mshtml.HTMLDocumentClass.get_parentWindow()
   at ConsoleApplication1.Module1.GetCoord(HTMLDocument hDoc, HTMLInputElement hEle, CoordType Output) in C:\New\ConsoleApplication1\ConsoleApplication1\Module1
.vb:line 95
   at ConsoleApplication1.Module1.Test() in C:\New\ConsoleApplication1\ConsoleApplication1\Module1.vb:line 64
   at ConsoleApplication1.Module1.Main() in C:\New\ConsoleApplication1\ConsoleApplication1\Module1.vb:line 110
Press any key to continue . . .

Go to topic 39806

Return to index page 369
Return to index page 368
Return to index page 367
Return to index page 366
Return to index page 365
Return to index page 364
Return to index page 363
Return to index page 362
Return to index page 361
Return to index page 360