You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何等待WebBrowser控件中IWebBrowser2::ExecWB()打印完成?

解决WebBrowser异步打印时窗口关闭终止打印的问题

这个问题我之前也遇到过——异步打印时用户随手关掉窗口,打印直接中断,确实挺闹心的。给你两个靠谱的解决方案,优先推荐第一种:

方案1:监听WebBrowser的打印完成事件(推荐)

WebBrowser控件本身会触发打印相关的COM事件,我们可以通过监听AfterPrint事件来标记打印完成状态,然后在窗口关闭时检查这个状态,阻止打印中的窗口关闭。

步骤:

  1. 让你的对话框类继承IDispEventImpl,用于处理WebBrowser的COM事件
  2. 添加BeforePrint和AfterPrint事件的处理函数,维护一个打印状态标志位
  3. 重写窗口的关闭查询函数,阻止打印中的窗口关闭
  4. 在对话框初始化和销毁时,分别连接/断开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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 04:35:09