How do I load HTML into Webbrowser control for WPF?

17,089

Solution 1

Actually, there is such a thing as an HTML page with embedded images.

You can embed both images and CSS inside an HTML string, using the data URI scheme. However, not all versions of IE support this, and the WebBrowser control is really IE. If your code might run on a machine with IE < 8, this approach won't help you.

Another option would be to store your images and CSS as embedded resources, write them out to temp files, and then insert absolute URL's to the temp files when you generate your HTML.

Solution 2

Since you are generating the HTML, you can set the base url of all script/image by generating a base element which points to somewhere you have control over, like file:/// url pointing to a folder on the disk, a url pointing to localhost where your local web server is listening (too many http server samples on the internet so I don't get into details here), or your web site on the www.

When you navigate to string, the HTML would be rendered in the same interenet zone as about:blank. Depending on the client's IE security zone settings, your HTML may not have have permission to load files in certain locations (like script files on the computer if the page is in the Internet zone).

You can use the Mark of the Web to change your HTML page's internet zone.

Solution 3

For the reference, I've stumbled upon this question and used the Joel Mueller answer, but decided to automate parsing the image paths. Maybe somebody will found my code useful for a starter of a similar solution - it's quite rough and dirty, but seems to do the job well.

I am using C# resources, inside my html files I only put image filenames. It also allows me to open the file in a normal browser for testing how it'll look. The code automatically looks for the resource with the same name as filename inside the html, saves it under application data directory, and substitutes the path.

It's also worth noting that the application will overwrite the file if it differs from the already saved one. It was mainly needed for the development, but I didn't really had many of those files, so I didn't care about the performance loss here. Omitting this check and assuming the files are up-to-date should enhance the performance.

Settings.Default.ApplicationId is a simple string with an application name, used for the directory name inside application data.

That's how my class ended up looking:

    class MyHtmlImageEmbedder
    {
        static protected string _appDataDirectory =
            Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
                Settings.Default.ApplicationId);

        static public string getHtml()
        {
            String apiHelp = Resources.html_file;
            Regex regex = new Regex(@"src=\""(.*)\""", RegexOptions.IgnoreCase);
            MatchCollection mc = regex.Matches(apiHelp);
            foreach (Match m in mc)
            {
                string filename = m.Groups[1].Value;
                Image image = Resources.ResourceManager.GetObject(Path.GetFileNameWithoutExtension(filename)) as Image;
                if (image != null)
                {
                    var path = getPathTo(Path.GetFileNameWithoutExtension(filename) + ".png", imageToPngByteArray(image));
                    apiHelp = apiHelp.Replace(filename, path);
                }
            }
            return apiHelp;
        }

        static public string getPathTo(string filename, byte[] contentBytes)
        {
            Directory.CreateDirectory(_appDataDirectory);
            var path = Path.Combine(_appDataDirectory, filename);
            if (!File.Exists(path) || !byteArrayCompare(contentBytes, File.ReadAllBytes(path)))
            {
                File.WriteAllBytes(path, contentBytes);
            }
            return path;
        }

        [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
        static extern int memcmp(byte[] b1, byte[] b2, long count);

        public static bool byteArrayCompare(byte[] b1, byte[] b2)
        {
            // Validate buffers are the same length.
            // This also ensures that the count does not exceed the length of either buffer.  
            return b1.Length == b2.Length && memcmp(b1, b2, b1.Length) == 0;
        }

        public static byte[] imageToPngByteArray(Image image)
        {
            MemoryStream ms = new MemoryStream();
            image.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
            return ms.ToArray();
        }
    }

Please note that the byteArrayCompareFunction uses memcmp only for performance reasons, but it can be easily substituted with a simple compare loop.

Then, I'm just calling browser.NavigateToString(MyHtmlImageEmbedder.getHtml());.

Share:
17,089
Tony
Author by

Tony

Updated on July 20, 2022

Comments

  • Tony
    Tony almost 2 years

    My WPF client application is building a custom HTML page, I can load the HTML like this,

    this.myWebBrowser.NavigateToString("<html><body><p>test page</p></body></html>");
    

    The problem I have is how do I load custom HTML into the Webbrowser control, if the HTML has images and css file in it?

    How do you reference the images and css? Can the images and css be embedded into the application, or do they need to be in a directory somewhere?

    Thanks for the help.

  • Tony
    Tony over 13 years
    My application is generating the HTML, It does not exist on a url.
  • John Saunders
    John Saunders over 13 years
    But like every other HTML page in the world, any images or CSS must be referenced via URL. There's no such thing as an HTML page with embedded images.
  • John Saunders
    John Saunders over 13 years
    Yes, I guess so. Did you think this through, at all? Maybe it's time to stop and read a tutorial on HTML?
  • John Saunders
    John Saunders over 13 years
    @Tony: really, go read that HTML tutorial. It will answer all of these questions. The answer to this one is: it won't work.
  • Tony
    Tony over 13 years
    Does a webbrowser control always use the local IE, or does it get compiled into my application so that it will always work, even if the pc only has ie6 on it?
  • Joel Mueller
    Joel Mueller over 13 years
    I'm pretty sure it always uses the local IE. Otherwise it would be easier to install multiple versions of IE on a single machine for testing.
  • Stephan
    Stephan over 8 years
    How does this answer the question? He already uses NavigateToString and I doubt that passing "EnterHtmlString" solves the problem with the css and image files.