How To Use the Microsoft WebBrowser Control to Render HTML from Memory

Microsoft has a WebBrowser control that is actually an Internet Explorer control that you can use to display HTML in your own applications. More information about this WebBrowser control can be found on MSDN. By using this control it’s very easy to display online or offline webpages. However, it’s not immediately obvious how to make it display HTML that you might have in a memory buffer. Of course, one solution is to write the HTML to a temporary file and then load that file using the WebBrowser control, but obviously there is a better way for doing this which I will explain below.

The first thing you have to do is to add the WebBrowser control to your dialog. So, in Visual Studio, open the resource editor and then open the dialog onto which you want to put the WebBrowser control. Once the dialog is opened in the resource editor, right click on an empty space on the dialog and select “Insert ActiveX Control…”. This will open a new window in which you can select “Microsoft Web Browser” and then click OK. Visual Studio will automatically create a wrapper class for this ActiveX control which will probably be called explorer.h and explorer.cpp while the wrapper class will most likely be called CExplorer.

Now, right click the WebBrowser control on your dialog and select “Add Variable”. Make a variable with category set to “Control” and with the variable type set to the wrapper class “CExplorer” and hit OK.

Now we can start writing code. The first thing required is to load up some basic document; I use about:blank. Do this in your OnInitDialog handler as follows.

COleVariant loc(L"about:blank");
m_explorer.Navigate2(loc, NULL, NULL, NULL, NULL); 

The above is very important. If you don’t load an initial document, the WebBrowser control will not render any HTML that you try to push to it. This also means that before you can start writing HTML from memory in the WebBrowser control, you have to wait until the initial document has been fully loaded. This can be done with the “DocumentComplete” event. In the dialog editor, right the WebBrowser control and click on “Add Event Handler…”. Select “DocumentComplete” as message type, select the appropriate class and click “Add and Edit”. You can use that handler to change a boolean variable in your code to mark whether the document has been fully loaded. When it is fully loaded you can start writing HTML from memory to it.

Once that is finished, you can add the following helper function:

#include <MsHTML.h>
void CMyDlg::WriteHTML(const wchar_t* html)
{
	IDispatch* pHtmlDoc = m_explorer.get_Document();
	if (!pHtmlDoc)
		return;
	CComPtr<IHTMLDocument2> doc2;
	doc2.Attach((IHTMLDocument2*)pHtmlDoc);
	if (!doc2)
		return;
	 // Creates a new one-dimensional array
	SAFEARRAY* psaStrings = SafeArrayCreateVector(VT_VARIANT, 0, 1);
	if (!psaStrings)
		return;
	BSTR bstr = SysAllocString(html);
	if (bstr)
	{
		VARIANT* param;
		HRESULT hr = SafeArrayAccessData(psaStrings, (LPVOID*)&param);
		if (SUCCEEDED(hr))
		{
			param->vt = VT_BSTR;
			param->bstrVal = bstr;
			hr = SafeArrayUnaccessData(psaStrings);
			if (SUCCEEDED(hr))
			{
				doc2->write(psaStrings);
				doc2->close();
			}
		}
	}
	// SafeArrayDestroy calls SysFreeString for each BSTR!
	if (psaStrings)
		SafeArrayDestroy(psaStrings);
}

With the above function, it’s very easy to dynamically create and display HTML from memory. For example:

WriteHTML(L"<html><body><h1>My Header</h1><p>Some text below the header</p></body></html>");

Note that the above code is expecting a Unicode build. If you don’t use Unicode, you need to change the wchar_t types and you need to change the way how you allocate the BSTR variable.

That’s it. Pretty easy if you know how to do it, but it took me some time to figure it out.

[ Update: Fixed some typos and added mshtml.h reference. ]

[ Update 2: Added a call to “doc2->close();” after “doc2->write()” and added code to check the result of the doc2.Attach() call. ]

Share

13 Comments so far »

  1. Kevin King said,

    Wrote on March 28, 2010 @ 10:41 pm

    In memory html doesn’t seem to work with anchors. Any idea how to get them to work without having to first write a document out to a temporary file? Say jump to an anchor on load.

  2. Marc Gregoire said,

    Wrote on March 29, 2010 @ 10:09 am

    What exactly are you trying to do?
    Can you post the HTML you are trying to use?

  3. Kevin King said,

    Wrote on April 5, 2010 @ 3:55 am

    I use a regular expression to colorize source code and I want the browser to jump to a line number onload by wrapping the relevant line in an anchor. Omitting the other lines from this sample (span elements above and below the span show below), the html doc is as such:

    .linenbr {color:black; background-color:#eeeeee;}
    .comment {color:#008800;}
    .number {color:#800080;}
    .string {color:#008080;}
    .keyword {color:#0000ff;}
    .operator {color:#0000ff;}
    .highlight {color:black; background-color:yellow;}

    d:\depot\main\product\src\mediasystem.cpp
    0171: TraceScope;

    As you can see, I use the onload event to reference the anchor and I assume this doesn’t work in memory because you’re sample already navigated to “about:blank”. I work around it by writing to a temporary file and navigate to it instead with file:///, but this is slow.

  4. Marc Gregoire said,

    Wrote on April 5, 2010 @ 9:38 am

    It seems this commenting system has messed up with your example code and I cannot really see any onload for example.
    You could send it to my email which you can find in the right bar of this blog.

  5. Marc Gregoire said,

    Wrote on April 5, 2010 @ 11:39 am

    I did some research and it’s a bit more complicated than I thought, so I wrote a new blog post:
    How To Navigate to an Anchor in the Microsoft WebBrowser Control when Rendering HTML from Memory

    I hope it helps…

  6. Jim Beveridge said,

    Wrote on May 3, 2010 @ 8:19 am

    I researched this problem in Oct 2008 and I reached the conclusion that your technique works well if you know the HTML ahead of time (such as for a dialog window), but is not usable in the general case because of its reliance on BSTR. Most HTML data has the character set embedded into the metadata. For exampe, this web page is UTF-8. You can’t convert the single byte or multibyte HTML data to Unicode/BSTR without knowing that charset, but the only straightforward way to find and handle the charset is to load the MSHTML control. Catch 22. (I’ll ignore unreliable hacks like regex parsers that try to blindly find the charset information.)

    The solution is to use another method to load the data. This should be easy to do with IPersistStreamInit::Load(), but that function has a well known bug (Q323569) that prevents it from being usable.

    My final solution uses IPersistMoniker, which is complicated. See my blog for the details:

    How to load MSHTML with data.

    If you come to a different conclusion than I did, I’d be interested to hear. It would be nice to find a simpler solution to this problem.

    One advantage to the IPersistMoniker technique is that it appears to allows you to intercept requests for linked files, such as JPG or CSS.

  7. Display HTML page instead text said,

    Wrote on November 20, 2013 @ 3:45 am

    […] Your question is not very clear. Do you want to view the HTML source as text OR view the HTML text in a browser? Have you looked at WebBrowser controls? WebBrowser Control Generating HTML web page from a text stream of HTML code; How To Use the Microsoft WebBrowser Control to Render HTML from Memory « Marc Gregoire’s Blog […]

  8. Kerry Hampton said,

    Wrote on March 4, 2014 @ 10:16 am

    Nice article.

    I have been experimenting with writing HTML + Script from memory via the technique you have described in this article. I am building my own replacement for mshta.exe and loading local html file content. The content is not in .htm files – it is in binary files that are converted to strings in memory. I load them into a IHTMLDocument2 pDoc as you have described, then display that doc in my own application window. Every thing works fine until I try to launch (spawn) a .htm file into a popup – for instance: var ret=window.open(c:\\myfile.htm) from this “memory created page”. I get access denied because the base URL is considered to be about:blank. Hence, SOP (same orgin policy). Have you encountered this problem and perhaps have a sugestion or two? Thanks!

  9. Marc Gregoire said,

    Wrote on March 4, 2014 @ 8:24 pm

    Kerry, I’ve never done something like that myself.
    How about something like this. Instead of using javascript to launch a new windows, use your own custom protocol and URL. For example, if you want a link to open a popup, use href=”MyPopup://myfile.htm”.
    Custom protocols are not hard to implement, see my article about it: http://www.nuonsoft.com/blog/2010/04/05/how-to-handle-custom-url-protocols-with-the-microsoft-webbrowser-control/
    When handling this custom protocol you create a popup window yourself that again renders HTML from memory.

  10. Kerry Hampton said,

    Wrote on March 10, 2014 @ 5:05 am

    Just a quick follow up for those who may encounter the same trouble as what I previously described. I was able to solve my problem with using the “Mark Of The Web”.

  11. Michael Haephrati said,

    Wrote on June 14, 2017 @ 9:18 pm

    doc2->write() seems to crash (access violation)

  12. KP said,

    Wrote on April 27, 2018 @ 7:20 pm

    Used your method in one project, it worked well. When I tried to use it in another project, add member wizard in VC stopped working (VC is so buggy), it doesn’t generate the wrapper class. I made one browser control in a form view work by copying everything over from the first project, but couldn’t make any other browser controls work, they are in different dialogs or formviews.

    I have made sure WriteHTML was called after Document Complete event. When tracing into WriteHTML, sometimes it failed at getDocument(), returned null, sometimes it looked perfect, but in either case nothing was displayed, only a blank control, no scroll bar.

    So now, still only one browser works, I’ve been using the same wrapper class, should it be different for each browser control? I have stuck for days, any suggestions would be greatly appreciated.

  13. KP said,

    Wrote on April 27, 2018 @ 8:47 pm

    OK, somehow I made another browser control work by repeating the copy process, I don’t think I have done any differently than the first time, this is a very simple dialog with only a browser control and OK button. But I still couldn’t make other two work. It seems like the browser control wasn’t created or initialized successfully. Even I remove Write WriteHTML and DocumentComplete event handler, the browser control still doesn’t show up.

Comment RSS · TrackBack URI

Leave a Comment

Name: (Required)

E-mail: (Required)

Website:

Comment: