如何等待WebBrowser控件中IWebBrowser2::ExecWB()打印完成?
解决WebBrowser异步打印时窗口关闭终止打印的问题
这个问题我之前也遇到过——异步打印时用户随手关掉窗口,打印直接中断,确实挺闹心的。给你两个靠谱的解决方案,优先推荐第一种:
方案1:监听WebBrowser的打印完成事件(推荐)
WebBrowser控件本身会触发打印相关的COM事件,我们可以通过监听AfterPrint事件来标记打印完成状态,然后在窗口关闭时检查这个状态,阻止打印中的窗口关闭。
步骤:
- 让你的对话框类继承
IDispEventImpl,用于处理WebBrowser的COM事件 - 添加
BeforePrint和AfterPrint事件的处理函数,维护一个打印状态标志位 - 重写窗口的关闭查询函数,阻止打印中的窗口关闭
- 在对话框初始化和销毁时,分别连接/断开WebBrowser的事件
代码示例:
对话框头文件(.h):
#include <afxdisp.h> #include <exdisp.h> #include <mshtml.h> class CMyWebDlg : public CDialogEx, public IDispEventImpl<1, CMyWebDlg, &DIID_DWebBrowserEvents2, &LIBID_SHDocVw, 1, 1> { DECLARE_DYNAMIC(CMyWebDlg) public: CMyWebDlg(CWnd* pParent = nullptr); virtual ~CMyWebDlg(); enum { IDD = IDD_MYWEB_DIALOG }; protected: virtual void DoDataExchange(CDataExchange* pDX); virtual BOOL OnInitDialog(); virtual void OnDestroy(); // 重写关闭查询函数,阻止打印时关闭 virtual BOOL OnQueryCloseWindow(); DECLARE_MESSAGE_MAP() private: CWebBrowser2 m_browser; // 你的WebBrowser控件变量 BOOL m_bIsPrinting; // 标记是否正在打印 // COM事件处理函数 void __stdcall OnBeforePrint(); void __stdcall OnAfterPrint(); // 事件映射 BEGIN_SINK_MAP(CMyWebDlg) SINK_ENTRY_EX(1, DIID_DWebBrowserEvents2, DISPID_BEFOREPRINT, OnBeforePrint) SINK_ENTRY_EX(1, DIID_DWebBrowserEvents2, DISPID_AFTERPRINT, OnAfterPrint) END_SINK_MAP() // 获取IWebBrowser2指针(你的原有函数) IWebBrowser2* GetIWebBrowser2(); };
对话框实现文件(.cpp):
#include "pch.h" #include "CMyWebDlg.h" IMPLEMENT_DYNAMIC(CMyWebDlg, CDialogEx) CMyWebDlg::CMyWebDlg(CWnd* pParent) : CDialogEx(IDD_MYWEB_DIALOG, pParent), m_bIsPrinting(FALSE) { } CMyWebDlg::~CMyWebDlg() { } void CMyWebDlg::DoDataExchange(CDataExchange* pDX) { CDialogEx::DoDataExchange(pDX); DDX_Control(pDX, IDC_WEBBROWSER, m_browser); } BEGIN_MESSAGE_MAP(CMyWebDlg, CDialogEx) END_MESSAGE_MAP() BOOL CMyWebDlg::OnInitDialog() { CDialogEx::OnInitDialog(); // 连接WebBrowser的COM事件 LPUNKNOWN pUnk = m_browser.GetControlUnknown(); if (pUnk) { HRESULT hr = DispEventAdvise(pUnk); if (FAILED(hr)) { AfxMessageBox(_T("无法绑定WebBrowser打印事件!")); } } return TRUE; } void CMyWebDlg::OnDestroy() { CDialogEx::OnDestroy(); // 断开WebBrowser的COM事件 LPUNKNOWN pUnk = m_browser.GetControlUnknown(); if (pUnk) { DispEventUnadvise(pUnk); } } BOOL CMyWebDlg::OnQueryCloseWindow() { if (m_bIsPrinting) { AfxMessageBox(_T("打印正在进行中,请等待完成后再关闭窗口!")); return FALSE; // 返回FALSE阻止窗口关闭 } return CDialogEx::OnQueryCloseWindow(); } // BeforePrint事件:打印开始时设置标志位 void __stdcall CMyWebDlg::OnBeforePrint() { m_bIsPrinting = TRUE; // 可选:禁用窗口关闭按钮,避免用户误点 ModifyStyle(WS_SYSMENU, 0); } // AfterPrint事件:打印完成后重置标志位 void __stdcall CMyWebDlg::OnAfterPrint() { m_bIsPrinting = FALSE; // 恢复窗口关闭按钮 ModifyStyle(0, WS_SYSMENU); } // 你的原有打印函数(无需修改标志位,交给事件处理) BOOL CMyWebDlg::PrintDocument(BOOL bDoPreview) { BOOL bRes = FALSE; CComPtr<IWebBrowser2> pWebBrowser = GetIWebBrowser2(); if(pWebBrowser) { HRESULT hr; COleVariant varNull; if(SUCCEEDED(hr = pWebBrowser->ExecWB( bDoPreview ? OLECMDID_PRINTPREVIEW : OLECMDID_PRINT, OLECMDEXECOPT_PROMPTUSER, varNull, varNull))) { bRes = TRUE; } } return bRes; } // 你的原有GetIWebBrowser2函数(修正了重复Release的问题) IWebBrowser2* CMyWebDlg::GetIWebBrowser2() { IWebBrowser2* pBrowser = NULL; LPUNKNOWN unknown = m_browser.GetControlUnknown(); if(unknown) { unknown->QueryInterface(IID_IWebBrowser2,(void **)&pBrowser); unknown->Release(); } return pBrowser; }
方案2:通过打印作业状态轮询(备选)
如果COM事件监听有问题,也可以通过Windows打印API轮询打印作业状态,直到打印完成。不过这个方案会阻塞UI线程(需要加消息循环避免假死),或者需要放到工作线程中。
核心代码示例:
#include <winspool.h> #pragma comment(lib, "winspool.lib") // 检查指定打印机是否有活跃打印作业 BOOL HasActivePrintJobs(LPCWSTR szPrinterName) { HANDLE hPrinter = nullptr; if (!OpenPrinter(const_cast<LPWSTR>(szPrinterName), &hPrinter, nullptr)) return FALSE; BOOL bHasActive = FALSE; DWORD dwBytesNeeded = 0; DWORD dwReturned = 0; // 获取缓冲区大小 EnumJobs(hPrinter, 0, 100, 2, nullptr, 0, &dwBytesNeeded, &dwReturned); if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { BYTE* pBuffer = new BYTE[dwBytesNeeded]; if (EnumJobs(hPrinter, 0, 100, 2, pBuffer, dwBytesNeeded, &dwBytesNeeded, &dwReturned)) { JOB_INFO_2* pJobs = reinterpret_cast<JOB_INFO_2*>(pBuffer); for (DWORD i = 0; i < dwReturned; i++) { // 检查作业是否未完成 if (!(pJobs[i].Status & (JOB_STATUS_COMPLETE | JOB_STATUS_DELETED | JOB_STATUS_CANCELED))) { bHasActive = TRUE; break; } } } delete[] pBuffer; } ClosePrinter(hPrinter); return bHasActive; } // 同步打印函数(会阻塞UI,建议放工作线程) BOOL CMyWebDlg::PrintDocumentSync(BOOL bDoPreview) { BOOL bRes = FALSE; CComPtr<IWebBrowser2> pWebBrowser = GetIWebBrowser2(); if(pWebBrowser) { HRESULT hr; COleVariant varNull; if(SUCCEEDED(hr = pWebBrowser->ExecWB( bDoPreview ? OLECMDID_PRINTPREVIEW : OLECMDID_PRINT, OLECMDEXECOPT_PROMPTUSER, varNull, varNull))) { if (!bDoPreview) { WCHAR szDefaultPrinter[MAX_PATH] = {0}; DWORD dwSize = MAX_PATH; if (GetDefaultPrinter(szDefaultPrinter, &dwSize)) { m_bIsPrinting = TRUE; // 轮询直到没有活跃作业 while (m_bIsPrinting) { if (!HasActivePrintJobs(szDefaultPrinter)) { m_bIsPrinting = FALSE; break; } // 处理消息循环,避免UI假死 MSG msg; while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } Sleep(1000); // 每秒检查一次 } } } bRes = TRUE; } } return bRes; }
注意事项:
- 方案2需要处理默认打印机的情况,如果用户选择了非默认打印机,需要额外获取用户选择的打印机名称(可以通过打印对话框回调获取)
- 长时间轮询会让UI响应变慢,最好把轮询逻辑放到工作线程中,通过自定义消息通知主线程打印完成
内容的提问来源于stack exchange,提问作者c00000fd
相关产品推荐
相关产品推荐

