65.9K
CodeProject 正在变化。 阅读更多。
Home

自动IE另存为MHTML

starIconstarIconstarIconstarIconstarIcon

5.00/5 (27投票s)

2002年9月5日

3分钟阅读

viewsIcon

514181

downloadIcon

7925

本文演示了如何自动化IE的另存为功能。

引言

本文的目的是展示如何自动化 Internet Explorer 的完整“另存为 HTML”功能,该功能通常对使用 Internet Explorer API 的用户隐藏。将当前文档保存为 MHTML 格式只是可用选项之一,包括

  • 另存为 MHTML(整个网页,图像,... 在一个文件中)
  • 另存为完整 HTML(用于图像的附加文件夹,...)
  • 仅保存 HTML 代码
  • 另存为文本

使用 Internet Explorer API 静默保存为 HTML

事实上,在 C++ 中,无需显示任何对话框即可保存当前网页以供存储的功能已经对所有人可用,使用以下代码,但有一个重要的限制

LPDISPATCH lpDispatch = NULL;
IPersistFile *lpPersistFile = NULL;

// m_ctrl is an instance of the Web Browser control
lpDispatch = m_ctrl.get_Document();
lpDispatch->QueryInterface(IID_IPersistFile, (void**)&lpPersistFile);

lpPersistFile->Save(L"c:\\htmlpage.html",0);
lpPersistFile->Release();
lpDispatch->Release();

(以上代码的标题) 仅保存 HTML 代码,没有对话框

限制是,我们只讨论 HTML 代码,而不是网页。当然,有趣的是能够访问包含图像等的完整 HTML 档案。

因为没有“公开”或已知的方法可以在不显示一个或多个来自 Internet Explorer 的对话框的情况下请求此功能,所以我们要做的是钩住操作系统以监听所有窗口创建,包括对话框。然后我们将要求 Internet Explorer 提供该功能并覆盖对话框中的文件路径,而不会被看到。最后,我们将模拟用户单击“保存”按钮以验证对话框并取消挂钩自己。完成了!

钩住 Internet Explorer 以在不弹出对话框的情况下另存为 HTML

这是一个简短的工作流程,但有一些技巧可以解决,并且本文是一个深入研究的独特机会。顺便说一句,该代码源自微软的一篇文章,内容是关于如何通过钩住“打印”对话框来定制 Internet Explorer 打印;请参阅 此处此处。在我们的应用程序中,我们有自己的“另存为”功能

m_wbSaveAs.Config( CString("c:\\htmlpage.mhtml"), SAVETYPE_ARCHIVE );
m_wbSaveAs.SaveAs();

// where the second parameter is the type of HTML needed :
typedef enum _SaveType
{
    SAVETYPE_HTMLPAGE = 0,
    SAVETYPE_ARCHIVE,
    SAVETYPE_HTMLONLY,
    SAVETYPE_TXTONLY
} SaveType;

我们通过安装钩子开始 SaveAs() 的实现

// prepare SaveAs Dialog hook
//
g_hHook = SetWindowsHookEx(WH_CBT, CbtProc, NULL, GetCurrentThreadId());
if (!g_hHook)
    return false;

// make SaveAs Dialog appear
//
// cmd = OLECMDID_SAVEAS (see ./include/docobj.h)
g_bSuccess = false;
g_pWebBrowserSaveAs = this;
HRESULT hr = m_pWebBrowser->ExecWB(OLECMDID_SAVEAS, 
    OLECMDEXECOPT_PROMPTUSER, NULL, NULL);

// remove hook
UnhookWindowsHookEx(g_hHook);
g_pWebBrowserSaveAs = NULL;
g_hHook = NULL;

钩子回调过程只是硬核代码;自己看看吧

LRESULT CALLBACK CSaveAsWebbrowser::CbtProc(int nCode, 
    WPARAM wParam, LPARAM lParam) 
{  
    // the windows hook sees for each new window being created :
    // - HCBT_CREATEWND : when the window is about to be created
    //      we check out if it is a dialog box (classid = 0x00008002, 
    //      see Spy++)
    //      and we hide it, likely to be the IE SaveAs dialog
    // - HCBT_ACTIVATE : when the window itself gets activited
    //      we run a separate thread, and let IE do his own init steps in 
    //      the mean time
    switch (nCode)
    {
        case HCBT_CREATEWND:
        {
            HWND hWnd = (HWND)wParam;
            LPCBT_CREATEWND pcbt = (LPCBT_CREATEWND)lParam;
            LPCREATESTRUCT pcs = pcbt->lpcs;
            if ((DWORD)pcs->lpszClass == 0x00008002)
            {
                g_hWnd = hWnd;          // Get hwnd of SaveAs dialog
                pcs->x = -2 * pcs->cx;  // Move dialog off screen
            }
            break;
        }    
        case HCBT_ACTIVATE:
        {
            HWND hwnd = (HWND)wParam;
            if (hwnd == g_hWnd)
            {
                g_hWnd = NULL;
                g_bSuccess = true;

                if (g_pWebBrowserSaveAs->IsSaveAsEnabled())
                {
                    g_pWebBrowserSaveAs->SaveAsDisable();

                    CSaveAsThread *newthread = new CSaveAsThread();
                    newthread->SetKeyWnd(hwnd);
                    newthread->Config( g_pWebBrowserSaveAs->GetFilename(), 
                        g_pWebBrowserSaveAs->GetSaveAsType() );
                    newthread->StartThread();
                }
            }
            break;
        }
    }
    return CallNextHookEx(g_hHook, nCode, wParam, lParam); 
}

在我们的线程中,我们等待 Internet Explorer “另存为”对话框准备好并填充数据

switch(    ::WaitForSingleObject( m_hComponentReadyEvent, m_WaitTime) )
{
     ...
     if ( ::IsWindowVisible(m_keyhwnd) )
     {
         bSignaled = TRUE;
         bContinue = FALSE;
     }

     MSG msg ;
     while( PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) )
     {
         if (msg.message == WM_QUIT)
         {
              bContinue = FALSE ;
              break ;
         }
         TranslateMessage(&msg);
         DispatchMessage(&msg);
     }
     ...
}

// relaunch our SaveAs class, but now everything is ready to play with
if (bSignaled)
{
    CSaveAsWebbrowser surrenderNow;
    surrenderNow.Config( GetFilename(), GetSaveAsType() );
    surrenderNow.UpdateSaveAs( m_keyhwnd );
}

// kill the thread, we don't care anymore about it
delete this;

现在我们可以覆盖适当的数据

void CSaveAsWebbrowser::UpdateSaveAs(HWND hwnd)
{
    // editbox : filepath (control id = 0x047c)
    // dropdown combo : filetypes (options=complete page;
    //     archive;html only;txt) (control id = 0x0470)
    // save button : control id = 0x0001
    // cancel button : control id = 0x0002


    // select right item in the combobox
    SendMessage(GetDlgItem(hwnd, 0x0470), CB_SETCURSEL, 
        (WPARAM) m_nSaveType, 0);
    SendMessage(hwnd, WM_COMMAND, MAKEWPARAM(0x0470,CBN_CLOSEUP), 
        (LPARAM) GetDlgItem(hwnd, 0x0470));

    // set output filename
    SetWindowText(GetDlgItem(hwnd, 0x047c), m_szFilename);

    // Invoke Save button
    SendMessage(GetDlgItem(hwnd, 0x0001), BM_CLICK, 0, 0);  
}

在上面的代码中,有趣的是要指出,为了选择我们想要的 HTML 类型(完整 HTML、档案、仅代码或文本格式),我们不仅选择组合框中的相应条目,我们还向 Internet Explorer 发送一个组合框 CloseUp 通知。这是因为 Internet Explorer 订阅了这个通知,以了解我们想要这种类型的 HTML。这种行为是通过提示和试用得知的。

结论

本文介绍了一种技术,用于访问 Internet Explorer 提供的完整的“另存为 HTML”功能。我从未在网上看到过关于此主题的文章,但很容易看出这对于构建 Web 应用程序的开发人员来说是一个引人注目的功能。您可以从提供的源代码中使用的文件是

  • SaveAsWebBrowser.h, *.cpp: 钩子过程;填充对话框数据
  • SaveAsThread.h, *.cpp: 用于与 Internet Explorer 同步的辅助线程

该应用程序只是一个简单的基于 MFC 的 CHtmlView 应用程序,嵌入了 Web 浏览器控件。

© . All rights reserved.