2

I am writing an app that prints out a (rendered) HTML page to every printer that is installed on the computer the app is running on. I do this by creating a WebBrowser, calling print() on it, and then closing the form. I do this for each printer that is installed on that computer.

My problem is that when I call attempt to close the form, the page does not get printed. Any ideas on how I can make the printing an independent background process, so that I can close the form while it is still printing?

This is the code that I currently have in the form:

    public PrintForm()
    {
        InitializeComponent();

        string doc = "C:\\Path\\To\\file.htm";
        browser.Url = new Uri(doc);
    }

    private void PrintForm_Shown(object sender, EventArgs e)
    {
        Thread t = new Thread(browser.Print);
        t.IsBackground = false;
        t.Start();

        this.Close();
    }

I have another class that toggles each of the installed printers as the default printer (the only way to programmatically bypass a print dialogue) and then calls

Applicated.Run(new PrintForm());

Really pretty simply code. Just having a bit of trouble with it.

jnevelson
  • 2,092
  • 2
  • 21
  • 31

2 Answers2

2

Yes, this cannot work. The first obstacle you'll face is that WebBrowser is an apartment threaded COM component. Calling its Print method on a thread doesn't actually work, COM honors the component's threading requirements and marshals the call to the thread it was created on, your UI thread.

Your code bombs because the form's Close() call also disposes all of its child controls. Including the WebBrowser. You can remove it from its Control collection to prevent this from happening, but you still have to call its Dispose() method when it is done printing.

Solving this is not technically impossible, you'd have to create an STA thread that pumps a message loop. Check my code in this thread for the approach.

Community
  • 1
  • 1
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Is there a way to achieve what I am after then? I went to the linked thread and used (and slightly modified) the code you had, and again, nothing prints out. – jnevelson Nov 12 '10 at 19:23
0

You need to change IsBackground = true to IsBackground = false

When IsBackGround is true that thread can't prevent the process from ending. When all the foreground threads end the process will end regardless of the status of any background threads. By setting IsBackground to false you make the print thread capable of supporting the process after main/user interface thread exits.

The UI thread will end but the process (visisble in control panel) will remain running until the print thread (and all other threads where IsBackground = false) complete. The process won't end until all foreground threads (including printing) exist.

On Edit:

Example of creating WebBrowser inside of the print thread. You likely don't even need to create and close a windows form if you don't need any user input.

Thread t = new Thread (new ParameterizedThreadStart(PrintUrl));
t.IsBackground = false;
t.Start((object)@"http://www.stackoverflow.com");

static void PrintUrl(object url)
{
    WebBrowser browser = new WebBrowser();
    browser.Url = new Uri((string)url);
    browser.Print();
}
Gerald Davis
  • 4,541
  • 2
  • 31
  • 47
  • That does not change anything. Whether isBackground is true or false, the document still does not print. The form opens and then closes a few seconds later, but still does not print anything out. When I do not call this.Close(), the document is printed out. The problem is that I need the form to close before I can open the next form (for the next printer). – jnevelson Nov 12 '10 at 16:27
  • Disregard my previous comment. When I run the app, I get an exception saying: "RaceOnRCWCleaup was detected. An attempt has been made to free an RCW that is in use. The RCW is in use on the active thread or another thread. Attempting to free an in-use RCW can cause corruption or data loss." This exception is raised on the Dispose() method in the form's Designer class. – jnevelson Nov 12 '10 at 16:44
  • Hmm. I may need to see more of the code but I believe the browser object is going out of scope. You should be creating the browser object inside the thread. – Gerald Davis Nov 12 '10 at 16:50
  • I added a few more details to my post. The form itself is extremely simply. I am unsure of what you mean by creating the browser inside the thread. The form takes care of initializing it for me. – jnevelson Nov 12 '10 at 17:02
  • I tried that, completely forgoing the Form that I had. I had to set the ApartmentState to STA to get it to run. However, nothing printed out. At this point I'm not even sure of where the problem lies. – jnevelson Nov 12 '10 at 17:14
  • BTW, before I forget to mention it, I really do appreciate the help. I am still in the process of learning C# and .NET (still in school, working an internship) so a lot of this is still a bit over my head. – jnevelson Nov 12 '10 at 17:16