首页 > 解决方案 > 从 .NET WebBrowser 打印到多台打印机 - 除非使用 MessageBox,否则程序会挂起

问题描述

WebBrowser.Print方法的限制是不允许调用者指定系统默认打印机以外的打印机。作为一种解决方法,建议[1][2]在调用之前更改系统的默认打印机Print(),但是也有报告[3](我亲身体验过)该WebBrowser实例将继续打印到先前定义的打印机即使在系统默认值被更改后。

为了解决这个问题,建议通过访问托管 WebBrowser 实例的底层 ActiveX 对象并在打印更多文档之前等待事件触发来注册PrintTemplateTeardown事件处理程序[4][5],这就是我的想法试图实施。我将一个更复杂的程序简化为下面介绍的 MVCE。

(该程序是一个 .NET Core 3.1 Windows 窗体应用程序,其中一个窗体只包含一个BackgroundWorker名为 的对象bw。)

Form1.cs

using System;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
using System.Drawing.Printing;
using System.Management;

namespace Demo_1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            bw.RunWorkerAsync();
        }

        private void bw_DoWork(object sender, DoWorkEventArgs e)
        {
            while (true)
            {
                BwPolling();

                Thread.Sleep(20000);
            }
        }

        private void BwPolling()
        {
            string[] htmlStrings = { "test1", "test2" };

            foreach (string html in htmlStrings)
            {
                Invoke((MethodInvoker)delegate
                {
                    PrinterSettings.StringCollection installedPrinters = PrinterSettings.InstalledPrinters;
                    foreach (string printer in installedPrinters)
                    {
                        string[] validPrinterNames =
                        {
                            "Microsoft Print to PDF",
                            "Microsoft XPS Document Writer"
                        };

                        if ( validPrinterNames.Contains(printer) )
                        {
                            SetDefaultPrinter(printer);
                            
                            var wb = new WebBrowser();
                            wb.DocumentText = html;

                            // With inspiration from code by Andrew Nosenko <https://stackoverflow.com/users/1768303/noseratio>
                            // From: https://stackoverflow.com/a/19737374/3258851
                            // CC BY-SA 3.0

                            var wbax = (SHDocVw.WebBrowser)wb.ActiveXInstance;

                            TaskCompletionSource<bool> printedTcs = null;
                            SHDocVw.DWebBrowserEvents2_PrintTemplateTeardownEventHandler printTemplateTeardownHandler =
                            (p)
                                => printedTcs.TrySetResult(true); // turn event into awaitable task

                            printedTcs = new TaskCompletionSource<bool>();
                            wbax.PrintTemplateTeardown += printTemplateTeardownHandler;

                            try
                            {
                                MessageBox.Show("Printing to " + printer);

                                wb.Print();

                                printedTcs.Task.Wait();
                            }

                            finally
                            {
                                wbax.PrintTemplateTeardown -= printTemplateTeardownHandler;
                            }

                            wb.Dispose();
                        }
                    }
                });
            }
        }

        private static bool SetDefaultPrinter(string name)
        {
            // With credits to Austin Salonen <https://stackoverflow.com/users/4068/austin-salonen>
            // From: https://stackoverflow.com/a/714543/3258851
            // CC BY-SA 3.0
            using ( ManagementObjectSearcher objectSearcher = new ManagementObjectSearcher("SELECT * FROM Win32_Printer") )
            {
                using ( ManagementObjectCollection objectCollection = objectSearcher.Get() )
                {
                    foreach (ManagementObject mo in objectCollection)
                    {
                        if ( string.Compare(mo["Name"].ToString(), name, true) == 0 )
                        {
                            mo.InvokeMethod("SetDefaultPrinter", null);
                            return true;
                        }
                    }
                }
            }
            return false;
        }
    }
}

面临的问题是当我从方法中删除消息框时BwPolling(),就在调用之前Print(),即删除此行时:

MessageBox.Show("Printing to " + printer);

然后程序冻结,不打印任何内容,并且最终必须终止该过程。

我相信我可以从表面上理解这个问题:WebBrowser需要一个带有活动消息循环的 STA 线程[6][7];通过printedTcs.Task.Wait();Invoke((MethodInvoker)delegate块内调用(在Form1实例上调用;this.被省略),我阻塞了 STA 线程并且应用程序挂起等待从未触发的事件。这实际上是在我在代码中记入的答案下的评论中提到的。

只是无法弄清楚适当的解决方案是什么。在尝试在辅助线程中运行打印例程时迷路了。也许我的执行过程中有问题,我想我需要帮助。有什么帮助吗?

谢谢。

标签: c#multithreadingasynchronousprintingwebbrowser-control

解决方案


推荐阅读