Streaming HTML content into a webbrowser control from VB

From HashVB
Jump to: navigation, search
 This article is based on Visual Basic 6. Find other Visual Basic 6 articles.

NOTE: For .NET, it is possible to do this directly in the language, and you do not have to create your own external library.

One of the many useful tools available to you as a VB developer is the WebBrowser control from Microsoft. This control forms the platform on top of which the entirety of Internet Explorer is built. Clearly, this is a powerful resource, offering you the ability to deliver rich content and a highly satisfying experience for your users. Unfortunately, it is impossible to directly put HTML content into a WebBrowser control from VB. There are many available techniques, such as caching your HTML on disk and using the Navigate() methods to open the page, or setting document.outerHTML from the WebBrowser's DOM. However, these methods are wholly unsatisfactory, and leave much to be desired. There has do be a better way, does there not?

The answer is yes, there does, and there is. As I said, it is impossible to do this from VB. However, if you have a C++ compiler (there are many available free from ... well, everywhere) and a couple of minutes on your hands, it is perfectly simple to create a small DLL that will allow you to stream your HTML content into the WebBrowser via an interface called IPersistStreamInit, that it implemented by the IHTMLDocument interface in order to give users the ability to stream content in and out (huzzah!).

The following code is somewhat outside the scope of this wiki, and so little explanation will be given for the code other than the comments within it. In fact no explanation will be given other than that. So here it is.

This code is from the file 'WebBrowserUtilVB.cpp':

   #include "stdafx.h"
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
       return TRUE;
   bool __stdcall StreamIn(IDispatch *pDisp, const wchar_t *szData, size_t len)
       bool bRet = false;
       IPersistStreamInit *pPersistStreamInit;
       // Query for IPersistStreamInit
       HRESULT hr = pDisp->QueryInterface(IID_IPersistStreamInit, (void**)&pPersistStreamInit);
       if (SUCCEEDED(hr))
           // Try to initialize the new document
           hr = pPersistStreamInit->InitNew();
           if (SUCCEEDED(hr))
               // Allocate global memory to copy the string into
               HGLOBAL hGlob = GlobalAlloc(GMEM_MOVEABLE, len * sizeof(wchar_t));
               if (hGlob)
                   // Lock the memory
                   void *pData = GlobalLock(hGlob);
                   if (pData)
                       // Copy string into global memory
                       wcsncpy((wchar_t*)pData, szData, len);
                       IStream *pStream = NULL;
                       // Create a stream object from the global memory
                       hr = CreateStreamOnHGlobal(hGlob, TRUE, &pStream);
                       if (SUCCEEDED(hr))
                           // Load the stream into the webbrowser
                           // Succeeded!
                           bRet = true;
                       // Clean up
       return bRet;

This code is from 'stdafx.h':

   #pragma once
   #define WIN32_LEAN_AND_MEAN     // Exclude rarely-used stuff from Windows headers
   // Windows Header Files:
   #include <windows.h>
   #include <mshtml.h>
   bool __stdcall StreamIn(IDispatch *pDisp, const wchar_t *szData, size_t len);

This code is from the exports file 'exports.def':

   LIBRARY WebBrowserUtilVB

When you compile this with VC7.1 in the release build configuration, it spits out a DLL of around 40kb, that should be included with your VB application.

So how do you use this code from VB? The following example shows you exactly what to do:

   Option Explicit
   Private Declare Function WebBrowser_StreamIn Lib "WebBrowserUtilVB.dll" Alias "StreamIn" (ByVal pDisp As Long, ByVal szData As Long, ByVal slen As Long) As Boolean
   Dim bStreamIn As Boolean
   Private Sub Form_Load()
       bStreamIn = True
       WebBrowser1.Navigate "about:blank"
   End Sub
   Private Sub WebBrowser1_DocumentComplete(ByVal pDisp As Object, URL As Variant)
       If (bStreamIn) Then
           Dim sHtml As String
           sHtml = "<html><body>test!!<img src=""""></body></html>"
           WebBrowser_StreamIn ObjPtr(WebBrowser1.Document), StrPtr(sHtml), Len(sHtml)
           bStreamIn = False
       End If
   End Sub

A little explanation: On the form is a WebBrowser control called 'WebBrowser1' (in case you didn't get that...). When the form loads, we tell the browser to navigate to "about:blank". The reasoning for this is that, if, when you try to stream data into the WebBrowser, there is no document already loaded, the program will simply crash. And we don't want that now, do we? Since it's a pain to mess around with trying to pass pointers from VB to C++ other than just using the *Ptr functions (see Pointers and CopyMemory for an explanation of how to use these mystical functions), I have just passed the pointers byval as long. One additional advantage of this is that it enables you to pass a Unicode string to the function rather than just ANSI (once again, see the aforementioned article). The flag 'bStreamIn' is used to make sure the StreamIn function is only called once when we need it to be. Every time you stream data into a document, you will receive another DocumentComplete event. If you then call StreamIn in said event without a flag, you will just create a recursive loop and eventually get a stack overflow (or it could just crash instantly - which is just as bad, or possibly worse).

And that is it. You can now safely and happily put any HTML content in a WebBrowser that you wish. Happy coding.

Another Method

Its is actually can be done with VB6 alone (

 Option Explicit
    Dim modifiedHTML As Boolean
    Private Sub Form_Load()
        modifiedHTML = False
        WebBrowser1.Navigate "about:blank"
    End Sub
    Private Sub WebBrowser1_BeforeNavigate2(ByVal pDisp As Object, URL As Variant, Flags As Variant, TargetFrameName As Variant, PostData As    Variant, Headers As Variant, Cancel As Boolean)
        modifiedHTML = False
    End Sub
    Private Sub WebBrowser1_DocumentComplete(ByVal pDisp As Object, URL As Variant)
        Dim htmlSrcStr As String
        htmlSrcStr = WebBrowser1.Document.body.innerhtml
        If modifiedHTML = False Then
            htmlSrcStr = "<p>some <b>text</b></p>" & htmlSrcStr
            WebBrowser1.Document.body.innerhtml = htmlSrcStr
            modifiedHTML = True
        End If

    End Sub

That's it.