• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 765
  • Last Modified:

How to load HTML into browser object?

Currently, I follow a URL, render that in the browser object and get the width.  For example:

Me.AxWebBrowser1.Navigate("www.abc.com")

' after the above loads, I get the width

Dim oBody As mshtml.HTMLBody = DirectCast(AxWebBrowser1.Document.body, mshtml.HTMLBody)
        Me.Label1.Text = "Width = " & oBody.scrollWidth & vbCrLf & "Height = " & oBody.scrollHeight

I don't want to reach a site anymore.  I have an HTML page that I'd like to load into the browser object and get the width.  All the HTML is in a string variable.  So I'm replacing traversing a URL for loading HTML content.  No user's will see this so it is ok to be in a class for DLL compilation.  How do I render the HTML content in the browser objecct?

Thanks,
Brett
0
brettr
Asked:
brettr
  • 27
  • 18
1 Solution
 
S-TwilleyCommented:
in the form load... just to initialize the document object of the web browser

AxWebBrowser1.Navigate2("")  

'this will give you the familiar no page found... so you may want to have the browser invisible at startup


Then to insert your own HTML:

        Dim thisDoc As mshtml.IHTMLDocument2 = DirectCast(AxWebBrowser1.Document, mshtml.IHTMLDocument2)
        thisDoc.open()
        thisDoc.writeln("<html><body>blah</body></html>")
        thisDoc.close()
0
 
brettrAuthor Commented:
I see the line
   AxWebBrowser1.Navigate2("")  
must be in form load(), as you mentioned.  If I put it in a button event immediately followed by the other code you've posted, it gives an object not set error.  I suppose that is because AxWebBrowser1 object hasn't had time to complete its loading...correct?

Also, if I'm going to put everything in a class, how can I leave enough time for the AxWebBrowser1 object to render before calling the other code?  Everything will go in a DLL.  Or can that be done?  Again, no user will ever see this.  I'm running a back end test.

Thanks,
Brett
0
 
S-TwilleyCommented:
when you say you're putting it all in a class... do you mean you're extending the WebBrowser control?

As for giving it enough time... you could have an initial flag set to False... and on the DocumentComplete event of the WebBrowser... set the flag to true.. then we know that the document object of the control has been initialized

I'll check this works
0
Free Tool: IP Lookup

Get more info about an IP address or domain name, such as organization, abuse contacts and geolocation.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

 
S-TwilleyCommented:
or you could just check:

If AxWebBrowser1.Document Is Nothing Then
     ' don't try to write to it
Else
     ' ok to change
End If
0
 
brettrAuthor Commented:
I'm not extending the class, just not involving a form.  It sounds as though I will need one.  That's ok.

Here's what I'm doing and the problem it presents.  I look through a data reader 5 times because there are always 5 records:

        While DRMessageBody.Read
            HTMLContent = DRMessageBody.Item("HTML_body")

            thisDoc.open()
            thisDoc.writeln(HTMLContent)
            thisDoc.close()

            If Me.AxWebBrowser1.Document Is Nothing Then
                Me.Label1.Text = "Width = " & oBody.scrollWidth & vbCrLf & "Height = " & oBody.scrollHeight
            End If
           
            'insert width value back into database for current record.

        End While

Given the above code, my if statement occurs to soon for the width to have a value.  I need this value because it will go back into the database for this record.  I could have the current thread.sleep(1000) while Me.AxWebBrowser1.Document gets values.  Then the if statement will go onto another thread and send back a value that is captured by the spawning thread once it wakes up.  Sounds a little to elaborate though.  Any suggestions?

Thanks,
Brett
0
 
S-TwilleyCommented:
If you don't want to use a form... but want some sort of browser... then you can use an Internet Explorer instance... as for data readers, don't know anything about them, but you're code... you should only write to the document object when it's ok... as for checking the width and such, it probably hasn't had time to render.... also, each time you read from DRMessageBody... is it a different "page" or is it all part of one large page which should be built up?

        While DRMessageBody.Read
            HTMLContent = DRMessageBody.Item("HTML_body")

            If Me.AxWebBrowser1.Document Is Nothing Then
                thisDoc.open()
                thisDoc.writeln(HTMLContent)
                thisDoc.close()

                Me.Label1.Text = "Width = " & oBody.scrollWidth & vbCrLf & "Height = " & oBody.scrollHeight
            End If
           
            'insert width value back into database for current record.

        End While
0
 
S-TwilleyCommented:
... and if you just want functionality of a document object... but not even a browser... then use an instance of a htmldocument object (there's a bug involved in this), so if you dont want ANYTHING visible to the user... use this, i'll let you know how if this is the case
0
 
brettrAuthor Commented:
I think the above code is fine but it's a matter of timing.  Each loop through the data reader brings in a new HTML page.  The steps of each iteration are:

1. read in HTML
2. render HTML
3. get width
4. insert width

You're right about it not having time to render.  Even in the code you posted above, there won't be enough time for it to render.  That's where I'm stuck.  There's needs to be a stall in the loop while the HTML render.  Any ideas on how to fix that?

Thanks,
Brett
0
 
S-TwilleyCommented:
how about...

1 - load the different html strings into a collection (or a similar implementation)
2 - start the upcoming process
3 - if list is empty, exit
4 - load first item (a html string) into document
5 - wait for render (i.e. when document is ready)
6 - get width... and return it to user
7 - remove 1st item from collection
8 - goto 3

===========

old school pseudo code!
0
 
brettrAuthor Commented:
Right but how do you do #5?  Which object provides a status of ready?  Meaning the HTML has completely rendered.

Thanks,
Brett
0
 
S-TwilleyCommented:
if you're using the webbrowser control... it should be on the DocumentComplete event

if you're using a solo HTMLDocument object... it's when the ReadyState = "ready" (I think)... and there is an event for the HTMLDocument object which captures a readystate change
0
 
S-TwilleyCommented:
so just to double check... you want all this "measuring" to be invisible from the user i.e. you don't want to display the pages which are rendered from this HTML code?
0
 
brettrAuthor Commented:
The pages can display but no one will ever see them.  That's the least of my worries though.

Here's how the readystate check looks:

            Do

            Loop Until Me.AxWebBrowser1.ReadyState = SHDocVw.tagREADYSTATE.READYSTATE_COMPLETE

That doesn't help though because the form is tied up in a loop, preventing the page from ever completely loading.   There's got to be a way to have the thread stall but still allow the page to load.  I need just enough time to get the oBody.scrollWidth value.
0
 
S-TwilleyCommented:
if seeing the rendered document is not important, then here's my suggestion:

add a module to ur project, name doesn't matter... and then overwrite it with this (including the Module block):

Imports System.Runtime.InteropServices

<ComVisible(True), ComImport(), Guid("7FD52380-4E07-101B-AE2D-08002B2EC713"), InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)> Public Interface IPersistStreamInit
    ' IPersist interface
    Sub GetClassID(ByRef pClassID As Guid)

    <PreserveSig()> Function IsDirty() As Integer
    <PreserveSig()> Function Load(ByVal pstm As UCOMIStream) As Integer
    <PreserveSig()> Function Save(ByVal pstm As UCOMIStream, ByVal ByValByValfClearDirty As Boolean) As Integer
    <PreserveSig()> Function GetSizeMax(<InAttribute(), Out(), MarshalAs(UnmanagedType.U8)> ByRef pcbSize As Long) As Integer
    <PreserveSig()> Function InitNew() As Integer
End Interface

====================
All the above code is to fix a problem in mshtml
==================



declare these outside of your form load event (i.e. scope is across the whole form):

      Private docInit As New mshtml.HTMLDocument
      Private WithEvents doc1, doc2, doc3, doc4, doc5 As mshtml.HTMLDocument

in your form load event:

        Dim ips As IPersistStreamInit
        ips = DirectCast(docInit, IPersistStreamInit)
        ips.InitNew()

        doc1 = docInit.createDocumentFragment
        doc2 = docInit.createDocumentFragment
        doc3 = docInit.createDocumentFragment
        doc4 = docInit.createDocumentFragment
        doc5 = docInit.createDocumentFragment
====================

this is assuming you've loaded the htmlcode into some strings:

         doc1.body.innerHTML = someCode1
         doc2.body.innerHTML = someCode2
         doc3.body.innerHTML = someCode3
         doc4.body.innerHTML = someCode4
         doc5.body.innerHTML = someCode5

Ok... here is where we will handle when the docs are rendered... it's not ideal as im seperating them into their own events to avoid confusion

    Sub doc1_ReadyChange() Handles doc1.onreadystatechange
        Dim thisWidth As Single = GetDocWidth(doc1)
    End Sub

    Sub doc2_ReadyChange() Handles doc2.onreadystatechange
        Dim thisWidth As Single = GetDocWidth(doc1)
    End Sub

    Sub doc3_ReadyChange() Handles doc3.onreadystatechange
        Dim thisWidth As Single = GetDocWidth(doc1)
    End Sub

    Sub doc4_ReadyChange() Handles doc4.onreadystatechange
        Dim thisWidth As Single = GetDocWidth(doc1)
    End Sub

    Sub doc5_ReadyChange() Handles doc5.onreadystatechange
        Dim thisWidth As Single = GetDocWidth(doc1)
    End Sub

    Function GetDocWidth(ByVal someDoc As mshtml.HTMLDocument) As Single
        'put in code here which get's someDoc's width and return it
    End Function

===========

this is non tested so let me know of any errors
0
 
S-TwilleyCommented:
oops... sorry, change the readystate change events like this

    Sub doc1_ReadyChange() Handles doc1.onreadystatechange
        If doc1.readyState = "complete" Then
            Dim thisWidth As Single = GetDocWidth(doc1)
            'do what you want with thisWidth
        End If
    End Sub

for each change (i.e. change the numbers)
0
 
brettrAuthor Commented:
Ok, I created a new project and have the code you supplied set.  I'm getting this error:

An unhandled exception of type 'System.NullReferenceException' occurred in widthTest2.exe

Additional information: Object reference not set to an instance of an object.

Here:
  doc1.body.innerHTML = "<html><body>blah...blah...blah</body></html>"
Is that related to the bug you mentioned?

I also thought of another approach. I put the database code into a class.  In the loop when it's time to call the HTML rendering code, I send it to the form, which has the browser component.  The class thread sleeps for 4 or 5 seconds.  The form completes the rendering.  Then a timer on the form fires after 1 second and sets a shared public var with the width.  The class thread wakes up, picks up that value, puts it in the database and the next interation occurs.  What do you think?

Will try to get your code going first.

Thanks,
Brett
0
 
S-TwilleyCommented:
ok... try changing:

Private WithEvents doc1, doc2, doc3, doc4, doc5 As mshtml.HTMLDocument

to

Private WithEvents doc1, doc2, doc3, doc4, doc5 As New mshtml.HTMLDocument


and change the onload stuff for:

        Dim ips1 As IPersistStreamInit
        ips1 = DirectCast(doc1, IPersistStreamInit)
        ips1.InitNew()

        Dim ips2 As IPersistStreamInit
        ips2 = DirectCast(doc2, IPersistStreamInit)
        ips2.InitNew()

        Dim ips3 As IPersistStreamInit
        ips3 = DirectCast(doc3, IPersistStreamInit)
        ips3.InitNew()

        Dim ips4 As IPersistStreamInit
        ips4 = DirectCast(doc4, IPersistStreamInit)
        ips4.InitNew()

        Dim ips5 As IPersistStreamInit
        ips5 = DirectCast(doc5, IPersistStreamInit)
        ips5.InitNew()



0
 
brettrAuthor Commented:
Still get the same error on the same line.
0
 
S-TwilleyCommented:
ok... hold on, i'll try and test it by setting some dummy html
0
 
S-TwilleyCommented:
ok, i can get it to add html code to the object... and access the structure, but it doesn't seem to be updating the scroll width property ... and after all that!

sorry for leading you down a wrong road... seems like i have... is it poss you could post up a sample chunk of your html being read... and i'll try and find the tidiest way of gettin the width of  the renderred page
0
 
S-TwilleyCommented:
common sense coming on here... htmldocument is like a virtual object... since it's now housed in some browser... which has a width, how's it going  to have a scroll width... im so dim sometimes...

ok, i'll wait for your sample html, and i'll give u code soon... again, sorry for the run-around
0
 
S-TwilleyCommented:
just so you know... using those htmldocuments are good when you're interested in knowing the structure of a webpage when you give it some htmlcode... or pass a url to download the document
0
 
brettrAuthor Commented:
Thanks so much Twilley.  You've been so helpful.  Just copy Microsoft or Yahoo's sites.  Those are good samples.  

However, check my above plan on multi threading (posted - Date: 04/06/2005 09:10PM EDT) this and let me know what you think.  I believe that approach will work well.
0
 
S-TwilleyCommented:
it should work... im just not happy about the thread sleeping that like that and just hoping all is ok... im already working on a version which uses an invisible IE instance... just need to know, what should the instance's width be (i.e. what was your web browser width)....

have two choices here.... create one invisible instance each for the different html codes... and then get the widths from them... or create one, get the width when its rendered.... then set it to the next bit of code... and so on.

I'll put it all in it's own thread so not to freeze up the form
0
 
brettrAuthor Commented:
You're right.  I'll just be hoping all is ok with the sleep.    700 is good for a width.  I may just wait and see what you come up with.

Thanks,
Brett
0
 
S-TwilleyCommented:
ok... i got it working with  the htmlcode from the yahoo page... now, i can get the scrollwidth... what do you want done with that exactly?
0
 
brettrAuthor Commented:
I just need that value to update the database.
0
 
S-TwilleyCommented:
ok... here's the class I made (will post how to use it in a moment):


Public Class ScrollWidthGrabber
    Implements IDisposable

    Public HTMLList As New Collection
    Public ScrollWidths As New Collection
    Public Ready As Boolean = False

    Private bInProgress As Boolean = False

    Private bStartCapture As Boolean = False
    Private bFirstNavError As Boolean = False
    Private bFirstNavComplete As Boolean = False

    Private iCurrentItem As Integer = 1

    Private WithEvents IE_Inst As New SHDocVw.InternetExplorer
    Private WithEvents ieDoc As mshtml.HTMLDocument
    Private ieDoc2 As mshtml.IHTMLDocument2

    Public Sub New(Optional ByVal ClientWidth As Integer = 700, Optional ByVal DisplayIE As Boolean = False)
        IE_Inst.Navigate2("")
        IE_Inst.Visible = DisplayIE
        IE_Inst.Width = ClientWidth
        IE_Inst.Resizable = False
        IE_Inst.Silent = True
    End Sub

    Private Sub IENavComplete(ByVal pDisp As Object, ByRef URL As Object) Handles IE_Inst.NavigateComplete2
        If Ready = False Then
            bFirstNavComplete = True
        End If
    End Sub

    Private Sub IENavError(ByVal pDisp As Object, ByRef URL As Object, ByRef Frame As Object, ByRef StatusCode As Object, ByRef Cancel As Boolean) Handles IE_Inst.NavigateError
        If Ready = False Then
            bFirstNavError = True
        End If
    End Sub

    Private Sub IEDocComplete(ByVal pDisp As Object, ByRef URL As Object) Handles IE_Inst.DocumentComplete
        If Ready And bInProgress Then
            'This captures when the any document is complete... whether it's top or part of loop
            'not sure if i need it or not, but it's here
        Else
            If bFirstNavError And bFirstNavComplete And (Not Ready) Then
                Ready = True
                ieDoc = DirectCast(IE_Inst.Document, mshtml.HTMLDocument)
                ieDoc2 = DirectCast(IE_Inst.Document, mshtml.HTMLDocument)
            End If
        End If
    End Sub

    Private Sub docReadyChange() Handles ieDoc.onreadystatechange
        'This captures when the top document is complete

        If ieDoc.readyState.ToLower = "complete" Then
            Dim ieBody As mshtml.HTMLBody
            ieBody = DirectCast(ieDoc.body, mshtml.HTMLBody)
            ScrollWidths.Add(ieBody.scrollWidth.ToString)
            iCurrentItem += 1
            LoadNextCode()
        End If
    End Sub

    Public Sub Start()
        If bInProgress Then Exit Sub
        If Not Ready Then Exit Sub
        bInProgress = True
        ScrollWidths = New Collection
        iCurrentItem = 1
        LoadNextCode()
    End Sub

    Public Sub WriteToDoc(ByVal newhtmlcode As String)
        ieDoc2.open()
        ieDoc2.writeln(newhtmlcode)
        ieDoc2.close()
        IE_Inst.Refresh2()
    End Sub


    Sub LoadNextCode()
        If iCurrentItem <= HTMLList.Count Then
            WriteToDoc(HTMLList.Item(iCurrentItem))
        Else
            RaiseEvent AllWidthsGrabbed(Me)
            bInProgress = False
        End If
    End Sub

    Public Sub Dispose() Implements System.IDisposable.Dispose
        Try
            IE_Inst.Quit()
        Catch ex As Exception

        End Try
        Try
            ieDoc = Nothing
        Catch ex As Exception

        End Try
    End Sub

    Public Event AllWidthsGrabbed(ByVal sender As Object)
End Class
0
 
S-TwilleyCommented:
   Dim WithEvents blah As ScrollWidthGrabber

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        blah = New ScrollWidthGrabber(, True)
        blah.HTMLList.Add(htmlYahoo)       ' loaded elsewhere, contains htmlcode from a yahoo page... use a database value
        blah.HTMLList.Add(htmlMicrosoft)   ' as above
    End Sub

    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
        blah.Start()
    End Sub

    Private Sub Grabbed(ByVal sender As Object) Handles blah.AllWidthsGrabbed
        TextBox1.Text = ""
        Dim s As String

        For Each s In blah.ScrollWidths
            TextBox1.Text &= s & vbCrLf
        Next
    End Sub
0
 
S-TwilleyCommented:
the above will actually show the IE instance... just so you can see the progress... to make it invisible:

blah = New ScrollWidthGrabber(, False)
0
 
brettrAuthor Commented:
Twilley,

It looks like you are initiating things on user clicks.  Is that true?  If so, no users will be interacting with the form.  This all needs to be completely automated.
0
 
S-TwilleyCommented:
no, the click thing there was just a button on a form to test it.
0
 
brettrAuthor Commented:
Oh, ok.  Will be trying the code this morning.  I'll keep you posted on the results.

Thanks,
Brett
0
 
brettrAuthor Commented:
I started implementing the database code and had the widths coming through in the collection.  After a few changes, these last two lines aren't executing in the debugger:

    Private WithEvents IE_Inst As New SHDocVw.InternetExplorer
    Private WithEvents ieDoc As mshtml.HTMLDocument
    Private ieDoc2 As mshtml.IHTMLDocument2\

When I get to this sub:
   Public Sub WriteToDoc(ByVal newhtmlcode As String)
        ieDoc2.open()
        ieDoc2.writeln(newhtmlcode)
        ieDoc2.close()
        IE_Inst.Refresh2()
    End Sub

I get an error about object reference.  Reason is because of the declare line is skipped.  I can't even click those last two privates to put a breakpoint.  Any idea what's happening?

Also, I don't use collections I suppose mainly because I don't fully understand their benefit.  Why didn't you use an array instead of a collection?

Thanks,
Brett
0
 
S-TwilleyCommented:
only used them for quick ease of use... since you're only using 5 values you can use an array.

what were the changes you made?
0
 
brettrAuthor Commented:
The flow is still the same.  I combined the button events into the timer event, which fires once:

        Me.Timer1.Enabled = False
        blah = New GetScrollWidth(, True)

        Me.InitailizeDatabaseConnections()

        Me.cnDR.Open()
        'loop through returned records
        Dim DRMessageBody As SqlDataReader
        DRMessageBody = Me.SqlCmd_SELECT_MessageWidth.ExecuteReader()

        Dim MessageID As Integer
        Dim HTMLContent As String

        While DRMessageBody.Read
            HTMLContent = DRMessageBody.Item("body")
            MessageID = DRMessageBody.Item("id")
            blah.HTMLList.Add(HTMLContent)
        End While

        Me.cnDR.Close()
        Me.cnDR.Dispose()

        blah.Start()

Before Start(), HTMLList does have 5 items.  The debugger goes into Start() and follows the same path as before then errors in WriteToDoc().  I don't know what change could have caused to two privates to stop declaring.  Any ideas?

Thanks,
Brett
0
 
S-TwilleyCommented:
where's your declaration for "blah"...

and where's your initialization of it
0
 
S-TwilleyCommented:
if the html code from your database isn't sensitive, I'd say post it to me and I'll test it with my version of hte program to see if it's a bug in my code, or a minor change you've made when copy pasting or changes you've made...

if you are going to send the 5 blocks of html code... save it into a txt file (don't give the html extension)... and attach it to an email and send to the address in profile...

make sure you put a subject in the email as google email doesn't like emails without subjects either
0
 
brettrAuthor Commented:
Twilley,  don't worry about the HTML code I'm using.  I've provided the code without database access for simplicity.  It still gives the same exact error in the same place.  Flow is also the same.  You should be able to post everything into your form.  Just paste the class code into a blank class.

In the form I do this with timer.enabled=true and interval=1000.  Here's the form code:

    Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick

        Me.Timer1.Enabled = False
        ProcessHTML()

    End Sub

    Private Sub ProcessHTML()

        blah.HTMLList.Add("<html><body>blah...blah...blah...1</body></html>")
        blah.HTMLList.Add("<html><body>blah...blah...blah...2</body></html>")

        blah.Start()

    End Sub


    Dim WithEvents blah As New ScrollWidthGrabber(, True)


    Private Sub Grabbed(ByVal sender As Object) Handles blah.AllWidthsGrabbed
        TextBox1.Text = ""
        Dim s As String

        For Each s In blah.ScrollWidths
            TextBox1.Text &= s & vbCrLf
        Next
    End Sub


--------- Here's the class: --------------------

Public Class GetScrollWidth
        Implements IDisposable

        Public HTMLList As New Collection
        Public ScrollWidths As New Collection
        Public Ready As Boolean = False

        Private bInProgress As Boolean = False

        Private bStartCapture As Boolean = False
        Private bFirstNavError As Boolean = False
        Private bFirstNavComplete As Boolean = False

        Private iCurrentItem As Integer = 1

   
        Private WithEvents IE_Inst As New SHDocVw.InternetExplorer
    Private WithEvents ieDoc As mshtml.HTMLDocument 'this line doesn't run for me
    Private ieDoc2 As mshtml.IHTMLDocument2 'this doesn't run either and is the reason I get the object reference error

        Public Sub New(Optional ByVal ClientWidth As Integer = 700, Optional ByVal DisplayIE As Boolean = False)
            IE_Inst.Navigate2("")
        IE_Inst.Visible = DisplayIE
            IE_Inst.Width = ClientWidth
            IE_Inst.Resizable = False
            IE_Inst.Silent = True
        End Sub

        Private Sub IENavComplete(ByVal pDisp As Object, ByRef URL As Object) Handles IE_Inst.NavigateComplete2
            If Ready = False Then
                bFirstNavComplete = True
            End If
        End Sub

        Private Sub IENavError(ByVal pDisp As Object, ByRef URL As Object, ByRef Frame As Object, ByRef StatusCode As Object, ByRef Cancel As Boolean) Handles IE_Inst.NavigateError
            If Ready = False Then
                bFirstNavError = True
            End If
        End Sub

        Private Sub IEDocComplete(ByVal pDisp As Object, ByRef URL As Object) Handles IE_Inst.DocumentComplete
            If Ready And bInProgress Then
                'This captures when the any document is complete... whether it's top or part of loop
                'not sure if i need it or not, but it's here
            Else
                If bFirstNavError And bFirstNavComplete And (Not Ready) Then
                    Ready = True
                    ieDoc = DirectCast(IE_Inst.Document, mshtml.HTMLDocument)
                    ieDoc2 = DirectCast(IE_Inst.Document, mshtml.HTMLDocument)
                End If
            End If
        End Sub

        Private Sub docReadyChange() Handles ieDoc.onreadystatechange
            'This captures when the top document is complete

            If ieDoc.readyState.ToLower = "complete" Then
                Dim ieBody As mshtml.HTMLBody
                ieBody = DirectCast(ieDoc.body, mshtml.HTMLBody)
                ScrollWidths.Add(ieBody.scrollWidth.ToString)
                iCurrentItem += 1
                LoadNextCode()
            End If
        End Sub

    Public Sub Start()
        If bInProgress Then Exit Sub
        If Not Ready Then Exit Sub
        bInProgress = True
        ScrollWidths = New Collection
        iCurrentItem = 1
        LoadNextCode()
        'Return ScrollWidths
    End Sub

    Public Sub WriteToDoc(ByVal newhtmlcode As String)
        ieDoc2.open()
        ieDoc2.writeln(newhtmlcode)
        ieDoc2.close()
        IE_Inst.Refresh2()
    End Sub


    Sub LoadNextCode()
        If iCurrentItem <= HTMLList.Count Then
            WriteToDoc(HTMLList.Item(iCurrentItem))
        Else
            RaiseEvent AllWidthsGrabbed(Me)
            bInProgress = False
        End If
    End Sub

    Public Sub Dispose() Implements System.IDisposable.Dispose
        Try
            IE_Inst.Quit()
        Catch ex As Exception

        End Try
        Try
            ieDoc = Nothing
        Catch ex As Exception

        End Try
    End Sub

    Public Event AllWidthsGrabbed(ByVal sender As Object)

End Class
0
 
S-TwilleyCommented:
working ok for me :\.... I've made a few changes... there is an event for when the object is ready to work... add a handler for that event then add your html code to the collection
=========================

ok, you can declare the grabber as a new object straight away, or stunt it after a while

either:

    Dim WithEvents blah As New ScrollWidthGrabber(, True)

or

    Dim WithEvents blah As ScrollWidthGrabber

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        blah = New ScrollWidthGrabber(, True)
    End Sub

-------------------------------------------------

Also in the form/module:

    Private Sub GrabberReady(ByVal sender As Object) Handles blah.ObjectReady
        blah.HTMLList.Add("<html><body>blah...blah...blah...1</body></html>")
        blah.HTMLList.Add("<html><body>blah...blah...blah...2</body></html>")
        blah.Start()
    End Sub

    Private Sub Grabbed(ByVal sender As Object) Handles blah.AllWidthsGrabbed
        TextBox1.Text = ""
        Dim s As String

        For Each s In blah.ScrollWidths
            TextBox1.Text &= s & vbCrLf
        Next
    End Sub

====================

and the slightly updated class:

Public Class ScrollWidthGrabber
    Implements IDisposable

    Public HTMLList As New Collection
    Public ScrollWidths As New Collection
    Public Ready As Boolean = False

    Private bInProgress As Boolean = False

    Private bStartCapture As Boolean = False
    Private bFirstNavError As Boolean = False
    Private bFirstNavComplete As Boolean = False

    Private iCurrentItem As Integer = 1

    Private WithEvents IE_Inst As New SHDocVw.InternetExplorer
    Private WithEvents ieDoc As mshtml.HTMLDocument
    Private ieDoc2 As mshtml.IHTMLDocument2

    Public Sub New(Optional ByVal ClientWidth As Integer = 700, Optional ByVal DisplayIE As Boolean = False)
        IE_Inst.Navigate2("")
        IE_Inst.Visible = DisplayIE
        IE_Inst.Width = ClientWidth
        IE_Inst.Resizable = False
        IE_Inst.Silent = True
    End Sub

    Private Sub IENavComplete(ByVal pDisp As Object, ByRef URL As Object) Handles IE_Inst.NavigateComplete2
        If Ready = False Then
            bFirstNavComplete = True
        End If
    End Sub

    Private Sub IENavError(ByVal pDisp As Object, ByRef URL As Object, ByRef Frame As Object, ByRef StatusCode As Object, ByRef Cancel As Boolean) Handles IE_Inst.NavigateError
        If Ready = False Then
            bFirstNavError = True
        End If
    End Sub

    Private Sub IEDocComplete(ByVal pDisp As Object, ByRef URL As Object) Handles IE_Inst.DocumentComplete
        If Ready And bInProgress Then
            'This captures when the any document is complete... whether it's top or part of loop
            'not sure if i need it or not, but it's here
        Else
            If bFirstNavError And bFirstNavComplete And (Not Ready) Then
                Ready = True
                ieDoc = DirectCast(IE_Inst.Document, mshtml.HTMLDocument)
                ieDoc2 = DirectCast(IE_Inst.Document, mshtml.HTMLDocument)
                RaiseEvent ObjectReady(Me)
            End If
        End If
    End Sub

    Private Sub docReadyChange() Handles ieDoc.onreadystatechange
        'This captures when the top document is complete

        If ieDoc.readyState.ToLower = "complete" Then
            Dim ieBody As mshtml.HTMLBody
            ieBody = DirectCast(ieDoc.body, mshtml.HTMLBody)
            ScrollWidths.Add(ieBody.scrollWidth.ToString)
            iCurrentItem += 1
            LoadNextCode()
        End If
    End Sub

    Public Sub Start()
        If bInProgress Then Exit Sub
        If Not Ready Then Exit Sub
        bInProgress = True
        ScrollWidths = New Collection
        iCurrentItem = 1
        LoadNextCode()
    End Sub

    Public Sub WriteToDoc(ByVal newhtmlcode As String)
        ieDoc2.open()
        ieDoc2.writeln(newhtmlcode)
        ieDoc2.close()
        IE_Inst.Refresh2()
    End Sub


    Sub LoadNextCode()
        If iCurrentItem <= HTMLList.Count Then
            WriteToDoc(HTMLList.Item(iCurrentItem))
        Else
            RaiseEvent AllWidthsGrabbed(Me)
            bInProgress = False
        End If
    End Sub

    Public Sub Dispose() Implements System.IDisposable.Dispose
        Try
            IE_Inst.Quit()
        Catch ex As Exception

        End Try
        Try
            ieDoc = Nothing
        Catch ex As Exception

        End Try
    End Sub

    Public Event AllWidthsGrabbed(ByVal sender As Object)
    Public Event ObjectReady(ByVal sender As Object)
End Class
0
 
brettrAuthor Commented:
I only updated the class as the form code hasn't really changed.  However, those two "private" statements still don't run.  I am running this on a different machine now.  Will try it on the other this evening.  This code was working on this machine earlier but I don't know why it breaks now or why those two "private" statements are skipped.  Why would they be skipped anyway?  There isn't anything causing the debugger to jump over them.

Thanks,
Brett
0
 
S-TwilleyCommented:
they're created when the class is created

the documents are initialized when the internet explorer has finished doing that first navigation... least that's what should happen

let me know what happens on another machine
0
 
brettrAuthor Commented:
Hi Twilley,

Well, I tried the code on my home machine and same exact thing.  The first line runs but the last two are skipped:


    Private WithEvents IE_Inst As New SHDocVw.InternetExplorer
    Private WithEvents ieDoc As mshtml.HTMLDocument
    Private ieDoc2 As mshtml.IHTMLDocument2

I even moved those two lines higher up in the declarations but they get skipped.  If you walk through the first part of the ScrollWidthGrabber class, does your debugger skip over those two lines?
0
 
S-TwilleyCommented:
yea it does because they're not being initialized yet (the line above has a New operator).

If you're using the latest class I posted... look at these lines

            If bFirstNavError And bFirstNavComplete And (Not Ready) Then
                Ready = True
                ieDoc = DirectCast(IE_Inst.Document, mshtml.HTMLDocument)
                ieDoc2 = DirectCast(IE_Inst.Document, mshtml.HTMLDocument)
                RaiseEvent ObjectReady(Me)
            End If

This is where the two documents are initialized... to make sure this is happening, you could add a breakpoint on the Ready = True line.

Let me know what happens... strange how it's working for me
0
 
brettrAuthor Commented:
The debugger ran through the above code.  I had true, false, false on the condition then the IDE froze.  I see part of an IE window is also open.  I say part because I can see through some of the toolbar.  Seems it didn't finish painting itself or loading.  That happened to me earlier today.  I end the IE process but the IDE is still froze.  Then I have to end task the IDE.  After restarting the IDE, I couldn't start the app because it was still in memory.  So I killed it.

However, I'm getting closer to the problem.  In my form:

    Public Sub New()
        MyBase.New()
        Try
            'This call is required by the Windows Form Designer.
        InitializeComponent()

        'Add any initialization after the InitializeComponent() call

        Catch ex As Exception

        End Try

    End Sub

I get the object reference error right after the debugger leaves the "End Sub" line.  I put the Try/Catch in to see if I could get a detailed error but that didn't help.  I then deleted the AxWebBrowser1 browser object off the form, a label and textbox.   Everything then ran.  One of those had to be the problem but I'm thinking it was the AxWebBrowser1 object not getting set.

I get back the results and everything seems good.  Thanks so much for sticking with it.  Hope you are around next time  :)   Actually, if you are familiar with TCP/IP listener and client, here's another good one I have going 'Why does timer stop ticking?'

Thanks again Twilley,
Brett
0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

Join & Write a Comment

Featured Post

Cloud Class® Course: MCSA MCSE Windows Server 2012

This course teaches how to install and configure Windows Server 2012 R2.  It is the first step on your path to becoming a Microsoft Certified Solutions Expert (MCSE).

  • 27
  • 18
Tackle projects and never again get stuck behind a technical roadblock.
Join Now