4

I'm trying to create GUI program that generates HTML invoices, and sends them for printing.
I have this working. However, now I want to introduce threading.

I have a form with a BackgroundWorker. The Background worker runs this code:

#region BackGroundWorker   
private void bg_htmlGeneration_DoWork(object sender, DoWorkEventArgs e)
{
   //SOME MORE CODE..

   foreach (XElement ele in Lib.GetInvoiceElement(inv, ico.Supplier))
   {
      PrintDocument(Lib.CreateHTMLFile());
   }
}
#endregion  


public void PrintDocument(string fileName)
{
    var th = new Thread(() =>
    {
        WebBrowser webBrowserForPrinting = new WebBrowser();
        webBrowserForPrinting.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(PrintDocumentHandler);
        webBrowserForPrinting.Url = new Uri(fileName);
        Application.Run();
    });
    th.SetApartmentState(ApartmentState.STA);
    th.Start();
}

public void PrintDocumentHandler(object sender, WebBrowserDocumentCompletedEventArgs e)
{
    ((WebBrowser)sender).Print();
    ((WebBrowser)sender).Dispose();
    Application.ExitThread();
}

Everything runs through fine. However, the WebBrowser object refuses to print. There are no errors (that are obvious), the program finishes off with nothing sent to the printer.
When I take away the threading, the program works again.

My knowledge of threading is weak, and I'm pretty much teaching myself - so presumably I'm misunderstanding how threading priority is set.

Here's How it should work:

  1. User selects Invoice(s) on Main Form, chooses to print.
  2. Background thread goes away and prints them while user continues on the program.

Any ideas would be appreciated.

Picrofo Software
  • 5,475
  • 3
  • 23
  • 37
James Senior
  • 133
  • 2
  • 8
  • Usually, I would access UI components (like the web browser) only from within the UI thread, not a background thread. In addition, I would not create UI elements from within a background thread. – Uwe Keim Oct 30 '12 at 16:55
  • Thanks for the input. Do you have any advice as to how I would approach this? I need to keep the Main Form available for user input (such as cancelling a print job).. so I need to only be accessing the component from the UI thread for no longer than a second. – James Senior Oct 31 '12 at 09:32
  • You could try having a _hidden_ `WebBrowser` control on your form and tell this one to print (all from within the foreground thread). This _could_ work, you should give it a try first to see. – Uwe Keim Oct 31 '12 at 09:56
  • At the moment, this is what I currently have: Timer on the Form that checks for a new entry in a Singleton Queue, if entry found, print it using a new WebForm. This seems to be working, but it seems like poor practice to be doing this. There must be a library somewhere that allows HTML printing from a thread, i just need to find it. – James Senior Oct 31 '12 at 10:53
  • I would doubt a lot that there are libraries out there. Rendering HTML is a _very_ complex task. Aspose.Pdf for examples allows basic HTML => PDF => Image rendering, but far from perfect. – Uwe Keim Oct 31 '12 at 11:05
  • I'll check that out as I'll need PDFs at a later date. Thanks for your help – James Senior Oct 31 '12 at 11:20

1 Answers1

3

Main problem with your code is WebBrowser wrong using.

WebBrowser supposed to be used for interactive web-browsing, during it user do some things in the internet. But in your case you are using WebBrowser just for the printing after downloading the html. This is wrong by two reasons:

  1. Your code creates whole Windows Forms Control and not using even half of its functionality.
  2. Your code tries to use the WinForms Control in the background thread, which leads to the unexpected behaviour.

BackgroundWorker class supposed to be used for

execute a time-consuming operation (like downloads and database transactions) in the background.

Much more:

You must be careful not to manipulate any user-interface objects in your DoWork event handler. Instead, communicate to the user interface through the ProgressChanged and RunWorkerCompleted events.

Your code will fail in the background thread, because WinForms control is a user-interface object.

Just for the record, WebBrowser.Print method invokes native windows API, so you have no chance that this will work in background. From the disassembly code:

this.AxIWebBrowser2.ExecWB(NativeMethods.OLECMDID.OLECMDID_PRINT, 
  NativeMethods.OLECMDEXECOPT.OLECMDEXECOPT_DONTPROMPTUSER, 
  ref obj, 
  IntPtr.Zero);

So, my suggestion for your code is:

  1. Remove usage of the WebBrowser class in the background. Use HttpWebRequest instead for downloading the web content.
  2. Choose other way for the printing your downloaded content. Options are:

PS: in the comments you've said that you may need the PDF from your html. I did this by C# by two ways:


Some update here:

As we have an async/await and TPL options for the time-consuming operations, I don't recommend you to use the BackgroundWorker class anymore.

Community
  • 1
  • 1
VMAtm
  • 27,943
  • 17
  • 79
  • 125
  • Following some other advice I was given, I've looked at using the IHTMLDocument2 object. This actually allows me to print from a different thread - but I'm having problems. Don't suppose you have any ideas VMAtm? http://stackoverflow.com/questions/13272704/ihtmldocument2-printing-without-dialog-box – James Senior Nov 07 '12 at 15:25