Streaming HTML content into a webbrowser control from VB
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" BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { return TRUE; } bool __stdcall StreamIn(IDispatch *pDisp, const wchar_t *szData, size_t len) { bool bRet = false; CoInitialize(NULL); 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 pPersistStreamInit->Load(pStream); pStream->Release(); // Succeeded! bRet = true; } // Clean up GlobalUnlock(hGlob); GlobalFree(hGlob); } } } pPersistStreamInit->Release(); } CoUninitialize(); 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 EXPORTS StreamIn
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=""http://www.google.co.uk/intl/en_uk/images/logo.gif""></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 (http://www.xtremevbtalk.com/showthread.php?t=219102&highlight=Putting+Stream+HTML+WebBrowser):
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.