Reputation: 7880
I have loaded some html text in WebBrowser
control (its uri becomes "about:blank"). Now I want to set its Uri to something else without navigating to that link.
How can I do that?
Upvotes: 2
Views: 2166
Reputation: 125197
There are two ways to set a URL for custom html content which you load into web browser control:
<base>
html tagIMoniker
interface (Git Repo: [r-aghaei/WebBrowserHtmlContentBaseUrl]Download
The first one is really simple and straight forward. For the second solution, I created a woring example. You can find it here:
Important Debug Note: To debug the application, disable
NotImplementedException
by going to Debug menu → Windows → Exception Settings → Search for System.NotImplementedException→ Clear the checkmark. Or if it throws exception, you can uncheck the exception in the exception window. If you press Ctrl+F5 you should not see any exception.
Notes
Using <base>
tag is pretty easier and more straightforward, however I couldn't load relative address of @font-face
with them. Rest of the things were fine.
The IMoniker
solution needs adding a reference to SHDocVw
which could be found as "Microsoft Internet Controls" in COM tab of Reference manager window.
Credits: Thanks to the author of this post, this post and Sheng Jiang for the other answer.
<base>
tagThe HTML <base>
element specifies the base URL to use for all relative URLs contained within a document. There can be only one element in a document.
So it's enough to inject a <base>
tag into <head>
like this:
html = html.Replace(@"<head>", $@"<head><base href=""{new Uri(Application.StartupPath)}/""/>");
webBrowser1.DocumentText = html;
It means all the relative addresses will be resolved using href attribute of <base>
.
IMoniker
You can implement IMoniker
interface and providing non-trivial implementation just for GetDisplayName
and BindToStorage
. Then using IWebBrowser2
from SHDocVw
you can load the document and set a base url for that.
Her I've created an extension method for WebBrowser
control having the following method:
void SetHtmlContent(string html, string baseUrl)
With these parameters:
html
: HTML content to load in web browserbaseUrl
: Base url to use for the document. It will be used like a real Url for resolving relative addresses.You can easily use it like this:
webBrowser1.SetHtmlContent(html, $@"{new Uri(Application.StartupPath)}/");
Just add a reference to SHDocVw
and use the paste the following code in your project:
using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using SHDocVw;
public static class WebBrowserExtensions
{
public static void SetHtmlContent(this System.Windows.Forms.WebBrowser webBrowser, string html, string baseUrl)
{
webBrowser.Navigate("about:blank");
var browser = (IWebBrowser2)webBrowser.ActiveXInstance;
var result = CreateStreamOnHGlobal(Marshal.StringToHGlobalAuto(html), true, out IStream stream);
if ((result != 0) || (stream == null))
return;
var persistentMoniker = browser.Document as IPersistMoniker;
if (persistentMoniker == null)
return;
IBindCtx bindContext = null;
CreateBindCtx((uint)0, out bindContext);
if (bindContext == null)
return;
var loader = new Moniker(baseUrl, html);
persistentMoniker.Load(1, loader, bindContext, (uint)0);
stream = null;
}
[DllImport("ole32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
static extern int CreateStreamOnHGlobal(IntPtr hGlobal, bool fDeleteOnRelease, out IStream istream);
[DllImport("ole32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
static extern int CreateBindCtx([MarshalAs(UnmanagedType.U4)] uint dwReserved, [Out, MarshalAs(UnmanagedType.Interface)] out IBindCtx ppbc);
[ComImport, ComVisible(true)]
[Guid("79EAC9C9-BAF9-11CE-8C82-00AA004BA90B")]
[InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
interface IPersistMoniker
{
void GetClassID([In, Out] ref Guid pClassID);
[return: MarshalAs(UnmanagedType.I4)]
[PreserveSig]
int IsDirty();
void Load([In] int fFullyAvailable, [In, MarshalAs(UnmanagedType.Interface)] IMoniker pmk, [In, MarshalAs(UnmanagedType.Interface)] Object pbc, [In, MarshalAs(UnmanagedType.U4)] uint grfMode);
void SaveCompleted([In, MarshalAs(UnmanagedType.Interface)] IMoniker pmk, [In, MarshalAs(UnmanagedType.Interface)] Object pbc);
[return: MarshalAs(UnmanagedType.Interface)]
IMoniker GetCurMoniker();
}
class Moniker : IMoniker
{
public static Guid IID_IStream = new Guid("0000000c-0000-0000-C000-000000000046");
string baseUrl;
IStream stream;
public Moniker(string baseUrl, string content)
{
this.baseUrl = baseUrl;
CreateStreamOnHGlobal(Marshal.StringToHGlobalAuto(content), true, out stream);
}
public void GetDisplayName(IBindCtx pbc, IMoniker pmkToLeft, out string ppszDisplayName)
{
ppszDisplayName = this.baseUrl;
}
public void BindToStorage(IBindCtx pbc, IMoniker pmkToLeft, ref Guid riid, out object ppvObj)
{
ppvObj = null;
if (riid.Equals(IID_IStream))
ppvObj = (IStream)stream; ;
}
public void GetClassID(out Guid pClassID)
{
throw new NotImplementedException();
}
public int IsDirty()
{
throw new NotImplementedException();
}
public void Load(IStream pStm)
{
throw new NotImplementedException();
}
public void Save(IStream pStm, bool fClearDirty)
{
throw new NotImplementedException();
}
public void GetSizeMax(out long pcbSize)
{
throw new NotImplementedException();
}
public void BindToObject(IBindCtx pbc, IMoniker pmkToLeft, ref Guid riidResult, out object ppvResult)
{
throw new NotImplementedException();
}
public void Reduce(IBindCtx pbc, int dwReduceHowFar, ref IMoniker ppmkToLeft, out IMoniker ppmkReduced)
{
throw new NotImplementedException();
}
public void ComposeWith(IMoniker pmkRight, bool fOnlyIfNotGeneric, out IMoniker ppmkComposite)
{
throw new NotImplementedException();
}
public void Enum(bool fForward, out IEnumMoniker ppenumMoniker)
{
throw new NotImplementedException();
}
public int IsEqual(IMoniker pmkOtherMoniker)
{
throw new NotImplementedException();
}
public void Hash(out int pdwHash)
{
throw new NotImplementedException();
}
public int IsRunning(IBindCtx pbc, IMoniker pmkToLeft, IMoniker pmkNewlyRunning)
{
throw new NotImplementedException();
}
public void GetTimeOfLastChange(IBindCtx pbc, IMoniker pmkToLeft, out System.Runtime.InteropServices.ComTypes.FILETIME pFileTime)
{
throw new NotImplementedException();
}
public void Inverse(out IMoniker ppmk)
{
throw new NotImplementedException();
}
public void CommonPrefixWith(IMoniker pmkOther, out IMoniker ppmkPrefix)
{
throw new NotImplementedException();
}
public void RelativePathTo(IMoniker pmkOther, out IMoniker ppmkRelPath)
{
throw new NotImplementedException();
}
public void ParseDisplayName(IBindCtx pbc, IMoniker pmkToLeft, string pszDisplayName, out int pchEaten, out IMoniker ppmkOut)
{
throw new NotImplementedException();
}
public int IsSystemMoniker(out int pdwMksys)
{
throw new NotImplementedException();
}
}
}
Upvotes: 2
Reputation: 15261
The webbrowser's document object loads data via a URL moniker. There is a pretty graph in the MSJ 1996 September issue article "Unified Browsing with ActiveX Extensions Brings the Internet to Your Desktop" demonstrating the the relationship about url monikers and the browser.
You can load a moniker or a stream into a document manually via the document's IPersistStreamInit interface. This is what Winform's webbrowser class is doing in its implementation of the DocumentStream and DocumentText properties. The document would call the source's IMoniker::GetDisplayName to get the url. However the load-from-stream implementation in Windows Forms does not implement IMoniker and the loaded document will have a base address of about:blank.
There is a sample on implementing a url moniker at http://www.codeproject.com/KB/miscctrl/csEXWB.aspx. Search LoadHtmlIntoBrowser(string html, string sBaseUrl) on the page.
Upvotes: 3