//
you're reading...
.NET, Programming Issues/Tips

Using the WebBrowser Control to Print Documents

1. Introduction

1.1 Recently, a junior colleague approached me for some advise.

1.2 As part of his project work, he had need to programmatically perform printing using C#.

1.3 The document he was assigned to print includes a combination of text and images.

1.4 He had no prior experience in this task and was daunted by the PrintDocument Class.

1.5 We discussed for a while and I tried to determine his current level of skill. He shared with me that he has working knowledge of HTML.

1.6 Upon hearing this, I advised him to use the WebBrowser class (from the System.Windows.Forms namespace) to do the printing.

1.7 To cut a long story short, my happy colleague completed his task successfully.

1.8 This blog provides a summary of the techniques that we used to successfully perform document printing using the WebBrowser class.

2. The WebBrowser Class

2.1 The System.Windows.Forms.WebBrowser class represents a full-fledged web browser control that can be used in a windows form application to provide visual web browsing capabilities. For an example of this use of the WebBrowser control, see How to: Add Web Browser Capabilities to a Windows Forms Application.

2.2 It is a control indeed. However, it can be used non-visually too. For an example of this, see How to: Print with a WebBrowser Control.

2.3 As my colleague would agree, the WebBrowser control is ideal for quick printing of documents that can be expressed as a HTML page. Display and printing of images are second nature to the WebBrowser control. Interfacing to the printer dialog is also possible through a simple call to the WebBrowser.ShowPrintDialog() method. Other useful methods include WebBrowser.ShowPrintPreviewDialog().

2.4 Although the “How to: Print with a WebBrowser Control” example already demonstrates how to perform printing using a WebBrowser control, this article aims to provide more value by demonstrating :

  • How to do so from inside a thread.
  • How to determine that the printing has ended in order to terminate the thread.
  • How to properly call the WebBrowser control’s Dispose() method.

The above were requirements that my colleague had to fulfill.

2.5 As a small value-add, I have provided a sample code that demonstrates how to use the HtmlTextWriter class to dynamically create a HTML file.

3. The Example Codes.

3.1 Provided in this section is a complete listing of a C# application that performs the required printing operation.

3.2 It is a summarized version of the application that my colleague eventually emerged. The following is a synopsis of the program :

  • This program begins a new thread on startup.
  • In the thread, a WebBrowser control is created.
  • A simple HTML file (that simply states “Print Out !”) is then written.
  • The WebBrowser control is then instructed to navigate to the HTML.
  • When the WebBrowser has completely processed the HTML, it is instructed to print it.
  • The thread stays running until the printing operation has completed.
  • When the printing has completed, the WebBrowser control is shutdown and the thread is terminated.

3.3 The code is listed below :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms; // In order to reference the WebBrowser class.
using System.Threading;  // In order to perform threading.
using System.IO;
using System.Web.UI; // Need to reference System.Web assembly.

// This console project needs to reference 3 assemblies :
// 1. System.Windows.Forms.dll
// 2. SHDocVw.dll (the "Microsoft Internet Controls" interop assembly)
// 3. System.Web.dll

namespace CSWebBrowserPrint
{
    class Program
    {
        private static void WB_OnDocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
        {
            // Print the document now that it is fully on the WebBrowser control.
            Console.WriteLine("Now printing document.");

            // Reference the WebBrowser control which fired this event.
            WebBrowser wb = sender as WebBrowser;

            // Obtain the InternetExplorer object from the WebBrowser object.
            // In order to use the InternetExplorer object, this project must
            // reference SHDocVw.dll. Do this by invoking the "Add Reference" 
            // dialog box and then selecting the "Microsoft Internet Controls"
            // from the "COM" tab.
            SHDocVw.InternetExplorer ie = (SHDocVw.InternetExplorer)(wb.ActiveXInstance);
            // Thereafter, setup event handlers for its print related events.
            // The PrintTemplateInstantiation event is fired when the print job is started.
            ie.PrintTemplateInstantiation += 
              new SHDocVw.DWebBrowserEvents2_PrintTemplateInstantiationEventHandler(IE_OnPrintTemplateInstantiation);
            // The PrintTemplateTeardown event is fired when the print job is done.
            ie.PrintTemplateTeardown += 
              new SHDocVw.DWebBrowserEvents2_PrintTemplateTeardownEventHandler(IE_OnPrintTemplateTeardown);

            ie.PutProperty("WebBrowserControl", (object)wb);

            // Now perform the printing.
            wb.Print();

            // Do not call Dispose() at this point.
            // It is too soon and will interfere with
            // the firing of the PrintTemplateTeardown 
            // event.
            /*wb.Dispose();*/

            // Do not call Application.ExitThread() here.
            // It is also too soon and may cause the print job to 
            // be cancelled. Call Application.ExitThread() 
            // in IE_OnPrintTemplateTeardown().
            /*Application.ExitThread()*/
        }

        private static void IE_OnPrintTemplateInstantiation(object pDisp)
        {
            // The PrintTemplateInstantiation event is fired when the print job is started.
            Console.WriteLine("Printing started.");
        }

        private static void IE_OnPrintTemplateTeardown(object pDisp)
        {
            // The PrintTemplateTeardown event is fired when the print job is done.
            Console.WriteLine("Printing ended.");

            SHDocVw.IWebBrowser2 iwb2 = pDisp as SHDocVw.IWebBrowser2;
            WebBrowser wb = (WebBrowser)(iwb2.GetProperty("WebBrowserControl"));

            // Call Dispose() here.
            // This is the most appropriate place to release
            // all resources associated with the WebBrowser
            // control.
            wb.Dispose();

            // Call Application.ExitThread() here.
            // This is the most appropriate place
            // to end the current printing thread.
            Application.ExitThread();   // Stops the thread
        }

        private static string GenerateHTMLDoc()
        {
            // Initialize StringWriter instance.
            StringWriter stringWriter = new StringWriter();

            // Put HtmlTextWriter in using block because it needs to call Dispose.
            using (HtmlTextWriter writer = new HtmlTextWriter(stringWriter))
            {
                writer.RenderBeginTag
                ("!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"");

                writer.AddAttribute("xmlns", "http://www.w3.org/1999/xhtml");
                writer.RenderBeginTag(HtmlTextWriterTag.Html); // Begin <html>

                writer.RenderBeginTag(HtmlTextWriterTag.Head); // Begin <head>
                writer.RenderBeginTag(HtmlTextWriterTag.Title); // Begin <title>
                writer.Write("Untitled Page");
                writer.RenderEndTag(); // End </title>
                writer.RenderEndTag(); // End </head>

                writer.RenderBeginTag(HtmlTextWriterTag.Body); // Begin <body>

                writer.RenderBeginTag(HtmlTextWriterTag.P); // Begin <p>
                writer.Write("Print Out !");
                writer.RenderEndTag(); // End </p>

                writer.RenderEndTag(); // End </body>

                writer.RenderEndTag(); // End </html>
            }
            // Return the result.
            return stringWriter.ToString();
        }

        static void Main(string[] args)
        {
            // The following code starts a new thread.
            Thread th = new Thread(() =>
            {
                // Create a new WebBrowser control to be used for printing.
                WebBrowser wb = new WebBrowser();
                // Set WB_OnDocumentCompleted as the delegate for
                // the DocumentCompleted event.
                wb.DocumentCompleted += WB_OnDocumentCompleted;

                // Generate a HTML string. 
                string strHTML = GenerateHTMLDoc();
                // Display the generated HTML string.
                Console.WriteLine(strHTML);

                // Create a HTML file and fill it with the HTML string just generated.
                File.WriteAllText(System.IO.Directory.GetCurrentDirectory() + "\\output.html", strHTML);

                // Tell the WebBrowser control to navigate to the HTML file just created.
                wb.Navigate(System.IO.Directory.GetCurrentDirectory() + "\\output.html");

                // The call to Application.Run() will insert a windows message pump 
                // in the new thread. This is necessary for the proper running of the
                // WebBrowser control which needs to run in an STA.
                Application.Run();
            });

            th.SetApartmentState(ApartmentState.STA);
            th.Name = "WebBrowserPrint";
            th.Start();

            Console.ReadLine();
        }
    }
}

3.4 We will begin exploring the important parts of the code in the next section.

4. The Main() Function.

4.1 The Main() function starts off with a lamba expression that begins a thread :

// The following code starts a new thread.
Thread th = new Thread(() =>
{
    // Create a new WebBrowser control to be used for printing.
    WebBrowser wb = new WebBrowser();
    // Set WB_OnDocumentCompleted as the delegate for
    // the DocumentCompleted event.
    wb.DocumentCompleted += WB_OnDocumentCompleted;

    // Generate a HTML string. 
    string strHTML = GenerateHTMLDoc();
    // Display the generated HTML string.
    Console.WriteLine(strHTML);

    // Create a HTML file and fill it with the HTML string just generated.
    File.WriteAllText(System.IO.Directory.GetCurrentDirectory() + "\\output.html", strHTML);

    // Tell the WebBrowser control to navigate to the HTML file just created.
    wb.Navigate(System.IO.Directory.GetCurrentDirectory() + "\\output.html");

    // The call to Application.Run() will insert a windows message pump 
    // in the new thread. This is necessary for the proper running of the
    // WebBrowser control which needs to run in an STA.
    Application.Run();
});

The following is a summary of what gets done in the thread :

  • A new instance of the WebBrowser class is created.
  • The delegate function WB_OnDocumentCompleted() is set as the event handler for the WebBrowser’s DocumentCompleted event.
  • The GenerateHTMLDoc() method is called to generate a HTML string which is then displayed and also saved onto an external file.
  • The WebBrowser control is then told to navigate to the newly generated HTML file.
  • The Application.Run() method is called to setup a message pump for the new thread.
  • Note that this Application.Run() method is of the Application class from the System.Windows.Forms namespace.
  • It is not the Windows Presentation Foundation (WPF) Application class which is from the System.Windows namespace.

4.2 The thread thus defined is not started until the Thread.Start() method is called. This is where the next part of Main() comes in :

th.SetApartmentState(ApartmentState.STA);
th.Name = "WebBrowserPrint";
th.Start();
  • The apartment of the thread is set to STA (the Single-Threaded Apartment).
  • This is important for the WebBrowser control. It is an ActiveX control and needs to survive in COM STA apartment.
  • The name of the thread is given and the Start() method is called to begin execution of the thread.

5. The WB_OnDocumentCompleted() Event Handler.

5.1 The WB_OnDocumentCompleted() method is the event handler for the DocumentCompleted event of the WebBrowser control.

5.2 This event is fired when the WebBrowser control has completely read and processed the page that it has navigated to.

5.3 Let’s examine this method more closely :

private static void WB_OnDocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
    // Print the document now that it is fully on the WebBrowser control.
    Console.WriteLine("Now printing document.");

    // Reference the WebBrowser control which fired this event.
    WebBrowser wb = sender as WebBrowser;

    // Obtain the InternetExplorer object from the WebBrowser object.
    // In order to use the InternetExplorer object, this project must
    // reference SHDocVw.dll. Do this by invoking the "Add Reference" 
    // dialog box and then selecting the "Microsoft Internet Controls"
    // from the "COM" tab.
    SHDocVw.InternetExplorer ie = (SHDocVw.InternetExplorer)(wb.ActiveXInstance);
    // Thereafter, setup event handlers for its print related events.
    // The PrintTemplateInstantiation event is fired when the print job is started.
    ie.PrintTemplateInstantiation += 
      new SHDocVw.DWebBrowserEvents2_PrintTemplateInstantiationEventHandler(IE_OnPrintTemplateInstantiation);
    // The PrintTemplateTeardown event is fired when the print job is done.
    ie.PrintTemplateTeardown += 
      new SHDocVw.DWebBrowserEvents2_PrintTemplateTeardownEventHandler(IE_OnPrintTemplateTeardown);

    ie.PutProperty("WebBrowserControl", (object)wb);

    // Now perform the printing.
    wb.Print();

    // Do not call Dispose() at this point.
    // It is too soon and will interfere with
    // the firing of the PrintTemplateTeardown 
    // event.
    /*wb.Dispose();*/

    // Do not call Application.ExitThread() here.
    // It is also too soon and may cause the print job to 
    // be cancelled. Call Application.ExitThread() 
    // in IE_OnPrintTemplateTeardown().
    /*Application.ExitThread()*/
}
  • The “sender” parameter is the WebBrowser control which has fired the event.
  • We cast it into a WebBrowser type represented by “wb”.
  • Through the ActiveXInstance property, we obtain the underlying SHDocVw.InternetExplorer object of the WebBrowser control.
  • This is done in order to access the DWebBrowserEvents2 events of the WebBrowser control.
  • We then setup 2 event handlers of the SHDocVw.InternetExplorer object : PrintTemplateInstantiation and PrintTemplateTeardown.
  • We shall look into why these event handlers are important later in point 5.4 below.
  • Next, the IWebBrowser2::PutProperty() method of the internet explorer control is used to set a private property named “WebBrowserControl”.
  • Again, we shall look into why this is important later in point 5.6 below.
  • The WebBrowser control is then commanded to print the document that it has loaded.

5.4 Now it is important at this point to mention 2 codes that were commented out in the above listing :

// Do not call Dispose() at this point.
// It is too soon and will interfere with
// the firing of the PrintTemplateTeardown 
// event.
/*wb.Dispose();*/

// Do not call Application.ExitThread() here.
// It is also too soon and may cause the print job to 
// be cancelled. Call Application.ExitThread() 
// in IE_OnPrintTemplateTeardown().
/*Application.ExitThread()*/
  • The calls to wb.Dispose() and the Application.ExitThread() calls have been commented out.
  • The reasons are clearly explained in the comments.
  • Essentially, we are concerned with properly terminating the “WebBrowserPrint” thread.
  • In order to do so, Application.ExitThread() must be called.
  • And Application.ExitThread() must not be called here because the printing thread may thus be terminated before completion.
  • The best place to call Application.ExitThread() would be at the PrintTemplateTeardown event handler since we know that when that event is fired the print job has completed.
  • Consequently, the Dispose() method must not be called here because it may disrupt the calling of the PrintTemplateTeardown event.
  • Dispose() can be called in the PrintTemplateTeardown event handler.

It is for these reasons that we setup the handler for the PrintTemplateTeardown events. The handler is : IE_OnPrintTemplateTeardown().

5.5 The PrintTemplateInstantiation event handler, IE_OnPrintTemplateInstantiation(), is invoked when printing is about to start. It does not provide any useful functionality for our example. It is provided for demonstrative purposes.

5.6 The other important parts of WB_OnDocumentCompleted() is the setting of a custom property named “WebBrowserControl” via the internet explorer object’s IWebBrowser2::PutProperty() method. This is important for the following reasons :

  • The WebBrowser control is a managed control that wraps the InternetExplorer ActiveX.
  • While it is possible to obtain from a WebBrowser control the InternetExplorer ActiveX via the WebBrowser.ActiveXInstance property, it is not possible to reverse obtain from the InternetExplorer ActiveX to the managed WebBrowser control.
  • This is why we need to insert the WebBrowser control as a custom property using the InternetExplorer ActiveX’s IWebBrowser2::PutProperty() method.
  • When stored as a custom property, the WebBrowser control’s IDispatch interface pointer will be used.
  • Later, in the IE_OnPrintTemplateTeardown() event handler, we retrieve this IDispatch interface pointer and cast it into the WebBrowser control.

6. The IE_OnPrintTemplateTeardown() Method.

6.1 This event handler is called when the WebBrowser control has completed printing :

private static void IE_OnPrintTemplateTeardown(object pDisp)
{
    // The PrintTemplateTeardown event is fired when the print job is done.
    Console.WriteLine("Printing ended.");

    SHDocVw.IWebBrowser2 iwb2 = pDisp as SHDocVw.IWebBrowser2;
    WebBrowser wb = (WebBrowser)(iwb2.GetProperty("WebBrowserControl"));

    // Call Dispose() here.
    // This is the most appropriate place to release
    // all resources associated with the WebBrowser
    // control.
    wb.Dispose();

    // Call Application.ExitThread() here.
    // This is the most appropriate place
    // to end the current printing thread.
    Application.ExitThread();   // Stops the thread
}
  • We begin by casting the pDisp parameter to its IWebBrowser2 interface.
  • This is done in order to call the IWebBrowser2::GetProperty() method of the WebBrowser control.
  • We do so with the property name “WebBrowserControl”.
  • This enables us to obtain the original WebBrowser control created in the startup code for the “WebBrowserPrint” thread.
  • Since when IE_OnPrintTemplateTeardown() is called, we know for sure that printing has completed, we can proceed to call the WebBrowser control’s Dispose() method.
  • We can also call Application.ExitThread() to officially end the “WebBrowserPrint” thread.

6.2 An important point to note about the call to Application.ExitThread() is that once it is called, the Application.Run() statement in the thread startup code (in Main()) will return :

Thread th = new Thread(() =>
{
    // Create a new WebBrowser control to be used for printing.
    WebBrowser wb = new WebBrowser();
    // Set WB_OnDocumentCompleted as the delegate for
    // the DocumentCompleted event.
    wb.DocumentCompleted += WB_OnDocumentCompleted;

    ...
    ...
    ...

    // The call to Application.Run() will insert a windows message pump 
    // in the new thread. This is necessary for the proper running of the
    // WebBrowser control which needs to run in an STA.
    Application.Run();
    // Application.Run() will return when Application.ExitThread() 
    // is called on the same thread.
});

This is because Application.Run() creates a message pump which will perpetuate continuously (making the thread stay alive indefinitely) until Application.ExitThread() is called.

7. The GenerateHTMLDoc() Method.

7.1 This method demonstrates the use of the HtmlTextWriter class to perform the writing of a HTML string.

7.2 Of course the whole idea of using the WebBrowser control to print out a document requires the HTML document itself.

7.3 This was where my colleague was most comfortable about his project task. He was adept at writing HTML files.

7.4 I have provided the GenerateHTMLDoc() to demonstrate how HTML files could be produced programmatically.

7.5 This routine will produce the following HTML string :

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
	<html xmlns="http://www.w3.org/1999/xhtml">
		<head>
			<title>
				Untitled Page
			</title>
		</head><body>
			<p>Print Out !</p>
		</body>
	</html>

The print out will simply be :

Print Out !

7.6 For more information, see HtmlTextWriter Class on MSDN.

8. In Conclusion.

8.1 Document printing using the WebBrowser control is certainly convenient.

8.2 However, to do so in a separate thread requires some further work which may not be intuitive as this article shows.

8.3 In this article, I have demonstrated the use of the WebBrowser’s ActiveXInstance property and from there hooking up to DWebBrowserEvents2 events.

8.4 I have also shown how to navigate from the WebBrowser’s ActiveXInstance property to obtaining the use of the IWebBrowser2’s PutProperty() and GetProperty() methods.

8.5 I hope this article will open the reader to new possibilities.

Advertisements

About Lim Bio Liong

I've been in software development for nearly 20 years specializing in C , COM and C#. It's truly an exicting time we live in, with so much resources at our disposal to gain and share knowledge. I hope my blog will serve a small part in this global knowledge sharing network. For many years now I've been deeply involved with C development work. However since circa 2010, my current work has required me to use more and more on C# with a particular focus on COM interop. I've also written several articles for CodeProject. However, in recent years I've concentrated my time more on helping others in the MSDN forums. Please feel free to leave a comment whenever you have any constructive criticism over any of my blog posts.

Discussion

2 thoughts on “Using the WebBrowser Control to Print Documents

  1. Thank you so much for this. I was struggling with the webbrowser control for days. You are the only one that explained the usage of IE_OnPrintTemplateTeardown.

    Posted by Althea23 | October 28, 2016, 12:50 pm
  2. Work on console application but not un service. IE_OnPrintTemplateTeardown is never called

    Posted by yaume2 | March 2, 2017, 2:20 pm

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: