基于文档/视图模式实现网页浏览以及打印功能

概述

《VC Web打印解决方案概述》这篇博文里面,简单介绍了在windows下html离线、在线网页打印解决方案,针对的是特殊需求。经过好几周的研究终于完整的实现与应用配合进行打印的功能。实际应用的时候没用这种方式,用的是基于对话框模式。
这里记录一下实现的一些功能,权当学习、熟悉一下MFC编程。实现的功能有以下几点:

  • View界面拆分
  • 各界面功能实现
  • 隐藏界面为后台进程

拆分界面

先看一下效果图:
基于文档/视图模式实现网页浏览以及打印功能_第1张图片
就是一个非常简陋的界面,先拆分为两行,然后第二行再拆分为两列。
ComBox控件可输入网址或者从下拉列表里面选择历史记录,从文件菜单打开一个URL之后,会将URL添加到ComBox的下拉列表里面,点击【go】按钮,即可在左下方显示网页内容。粗陋的界面先这样。

粗陋界面拆分实现:
在CMainFrame类里面添加如下定义,用于拆分界面:

    CSplitterWnd m_wndSplitCols;
    CSplitterWnd m_wndSplitRows;

插入两个MFC类,分别为CTopView、CLeftView,基类均为CView,连着新建工程时的view,总共3个view,在CMainFrame类的OnCreateClient方法里面实现窗口的拆分:

// create splitter window
//创建两行一列
if (!m_wndSplitRows.CreateStatic(this, 2, 1))
    return FALSE;
//创建第一行view
if (!m_wndSplitRows.CreateView(0,0,RUNTIME_CLASS(CTopView),CSize(0,20),pContext))
{
    return FALSE;
}
//在第二行创建两列
if (!m_wndSplitCols.CreateStatic(&m_wndSplitRows, 1, 2,WS_CHILD| WS_VISIBLE,m_wndSplitRows.IdFromRowCol(1,0)))
    return FALSE;
//添加view
if (!m_wndSplitCols.CreateView(0, 0, RUNTIME_CLASS(CLeftView), CSize(100, 0), pContext) ||
        !m_wndSplitCols.CreateView(0, 1, RUNTIME_CLASS(CWebBrowserDocumentView), CSize(0, 0), pContext))
{
    m_wndSplitCols.DestroyWindow();
    return FALSE;
}

在CMainFrame类中定义三个view的对象,方便后续使用:

    CWebBrowserDocumentView *pWebView;
    CTopView       *pTopView;
    CLeftView      *pLeftView;

在OnCreateClient方法中赋值如下:

pWebView = (CWebBrowserDocumentView*)this->m_wndSplitCols.GetPane(0,1);
pTopView = (CTopView*)this->m_wndSplitRows.GetPane(0,0);
pLeftView = (CLeftView*)this->m_wndSplitCols.GetPane(0,0);

各界面功能实现

CTopView功能实现

CTopView的功能主要就是输入URL,点击Go按钮(或者捕获ENTER按键)之后在CWebBrowserDocumentView显示网页,菜单打开文件之后将URL加入ComBox下拉列表。

实现如下:

    #define ID_LABEL 100
    #define ID_COMBOX 101
    #define ID_BT_GO 102
    //定义三个变量
    CButton     label;
    CButton     btGo;
    CComboBox   combox;

//在OnCreate函数中创建各个控件
int CTopView::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
    if (CView::OnCreate(lpCreateStruct) == -1)
        return -1;  
    // TODO: Add your specialized creation code here 
    label.Create(_T(" Address:"),
        WS_CHILD|WS_VISIBLE,
        CRect(0,0,103,22),
        this,
        ID_LABEL);
    label.ShowWindow(SW_SHOW);

    CRect rect;
    this->GetClientRect(&rect); 
    combox.Create(WS_CHILD|WS_VISIBLE|CBS_DROPDOWN|CBS_HASSTRINGS|WS_VSCROLL,       CRect(105,rect.top,rect.Width()-50,200),this,ID_COMBOX);
    combox.SetCurSel(0);

    btGo.Create(_T("GO"),
        WS_CHILD|WS_VISIBLE,
        CRect(rect.Width()-52,rect.top,rect.right+2,22),
        this,
        ID_BT_GO);
    btGo.ShowWindow(SW_SHOW);
    return 0;
}   

//为了适应窗口改变,重写OnSize方法,使ComBox以及Go按钮的随窗口大小改变。
void CTopView::OnSize(UINT nType, int cx, int cy) 
{
    CView::OnSize(nType, cx, cy);

    // TODO: Add your message handler code here
    //label.set
    CRect rect;
    CWnd *pWnd = GetDlgItem(ID_COMBOX);
    if (pWnd != NULL)
    {
        this->GetClientRect(&rect);

        rect.right = rect.Width()-50;       
        rect.left = 105; 
        pWnd->MoveWindow(rect);
    }
    pWnd = GetDlgItem(ID_BT_GO);
    if (pWnd != NULL)
    {       
        this->GetClientRect(&rect);
        rect.right = rect.right+2;
        rect.left = rect.right-52;
        rect.bottom = 22;
        pWnd->MoveWindow(rect);
    }
}

//往ComBox中插入URL
void CTopView::SetAddress(CString szaddr)
{
    int index = combox.GetCurSel();
    //combox中不存在时才插入,避免重复
    if (combox.FindString(0,szaddr) == CB_ERR)
    {
        combox.AddString((LPCTSTR)szaddr);
        combox.SetCurSel(index+1);
        combox.SetWindowText(szaddr);
    }
}

下面问题来了,如何为combox添加下拉列表项添加点击事件呢?
其实应该叫选择改变事件,在TopView.cpp文件的消息映射添加如下映射:

ON_CBN_SELCHANGE(ID_COMBOX,OnSelChanged)

在CTopView中添加消息处理函数:

afx_msg void OnSelChanged();

实现如下:

void CTopView::OnSelChanged()
{
    //MessageBox("sakfjla");
    int index = combox.GetCurSel();
    CString selURL = _T("");
    combox.GetLBText(index,selURL);
    combox.SetWindowText(selURL);
    OnGoClick();
}

OnGoClick方法就是Go按钮的消息处理函数,按同样的方式为Go按钮添加消息映射:

ON_BN_CLICKED(ID_BT_GO,OnGoClick)

void CTopView::OnGoClick()
{
    CString strURL = GetAddress();

    if (strlen((LPSTR)(LPCTSTR)strURL) > 0)
    {
        SetAddress((LPCTSTR)strURL);
        //循环判断view是不是CHtmlView类
        CWebBrowserDocumentDoc *pDoc = (CWebBrowserDocumentDoc *)GetDocument();
        CView *pView ;
        POSITION pos = pDoc->GetFirstViewPosition();
        while(pos != NULL)
        {
            pView = pDoc->GetNextView(pos);
            if (pView->IsKindOf(RUNTIME_CLASS(CWebBrowserDocumentView)))
            {
                break;
            }
        }
        //如果是,就导航显示网页
        if(pView->IsKindOf(RUNTIME_CLASS(CWebBrowserDocumentView)))
            ((CWebBrowserDocumentView*)pView)->Navigate2(strURL,NULL,NULL);
    }       
}

GetAddress方法只是获取当前编辑框里面的值。

那如何监听键盘的按键消息呢?通过PreTranslateMessage,这个方法是窗口消息处理函数,每个CView类及子类都继承了该方法,重写该方法即可捕获窗口消息,下面是实现:

BOOL CTopView::PreTranslateMessage(MSG* pMsg)
{
    if(pMsg->message == WM_KEYDOWN)
    {
        if (pMsg->wParam == VK_RETURN)
        {
            OnGoClick();
        }
    }
    return CView::PreTranslateMessage(pMsg);
}

是不是非常简单!!!
CTopView的功能基本实现了。下面是CWebBrowserDocumentView的功能。

网页显示及打印/打印预览的实现

先分析一下CWebBrowserDocumentView的功能,主要是显示页面(可以是一个本地的html页面,也可以是一个网址);打印及打印预览显示的页面;如果网页中有超级连接,当点击超级连接的时候,希望将连接的URL加入combox。
显示页面,直接调用CHtmlView的Navigate2方法即可。
至于打印,为“文件”菜单下面的打印命令添加消息处理函数,在CWebBrowserDocumentView类添加如下消息映射:

ON_COMMAND(ID_FILE_PRINT, CHtmlView::OnFilePrint)

但是这样会调用系统的CPrintDialog来进行打印,如果不想弹出打印对话框,就需要重写OnFilePrint方法:

ON_COMMAND(ID_FILE_PRINT, OnFilePrint)

void CWebBrowserDocumentView::OnFilePrint() 
{
    // TODO: Add your command handler code here
    IHTMLDocument2 *pDoc = NULL;
    IDispatch *pID = this->GetHtmlDocument();
    HRESULT hr = NULL;
    if (pID != NULL)
    {
        hr = pID->QueryInterface(IID_IHTMLDocument2,(void**)&pDoc);
        if (SUCCEEDED(hr))
        {
            IOleCommandTarget *pCmdTarget;
            pDoc->QueryInterface(IID_IOleCommandTarget,(void**)&pCmdTarget);
            hr = pCmdTarget->Exec(&CGID_MSHTML,IDM_PRINTPREVIEW,OLECMDEXECOPT_DONTPROMPTUSER,
                NULL,NULL);
        }
    }
    pID->Release();
}

至于打印预览,CHtmlView本身没有实现打印预览的功能,所以需要自己实现,同样添加消息映射:

ON_COMMAND(ID_FILE_PRINTPREVIEW, OnFilePrintpreview)

void CWebBrowserDocumentView::OnFilePrintpreview() 
{
    // TODO: Add your command handler code here
    LPOLECOMMANDTARGET lpTarget = NULL;
    LPDISPATCH lpDisp = GetHtmlDocument();

    if (lpDisp != NULL)
    {
        // the control will handle all printing UI

        if (SUCCEEDED(lpDisp->QueryInterface(IID_IOleCommandTarget,
            (LPVOID*) &lpTarget)))
        {
            lpTarget->Exec(NULL, OLECMDID_PRINTPREVIEW, OLECMDEXECOPT_DONTPROMPTUSER, NULL, NULL);
            lpTarget->Release();
        }
        lpDisp->Release();
    }
}

超级连接点击时,将超级连接的URL加入combox,那如何得知点击了超级连接呢?
点击超级连接时,会调用OnNavigateComplete2方法,所以可以重写该方法,在其中进行处理:

void CWebBrowserDocumentView::OnNavigateComplete2(LPCTSTR strURL) 
{
    // TODO: Add your specialized code here and/or call the base class
    CMainFrame* pMFrame = ((CMainFrame*)AfxGetMainWnd());
    pMFrame->pTopView->SetAddress(strURL);
    CHtmlView::OnNavigateComplete2(strURL);
}

CMainFrame补充功能

为文件打开命令添加消息处理函数,先添加如下消息映射:

ON_COMMAND(ID_FILE_OPEN, OnFileOpen)

然后实现OnFileOpen方法:

void CMainFrame::OnFileOpen() 
{
    // TODO: Add your command handler code here
    //文件过滤
    static TCHAR BASED_CODE szHTMLFilter[] = 
        _T("ALL Files(*.*)|*.*|HTML Files(*.html)|*.html|HTML Files(*.htm)|*.htm||");
    CFileDialog dlgHtml(TRUE,_T("htm"),NULL,OFN_EXPLORER,szHTMLFilter,this);
    dlgHtml.m_ofn.lpstrTitle = _T("Open HTML Files");
    if (dlgHtml.DoModal() != IDOK)
    {
        return;
    }
    CString strUrl(_T(""));
    strUrl += dlgHtml.GetPathName();
    this->m_strURL = strUrl;
    //导航显示网页
    pWebView->Navigate2(strUrl,NULL,NULL);
    //将网页添加到CTopView的combox
    pTopView->SetAddress(m_strURL);
}

隐藏界面为后台进程

如何让这个程序隐藏界面在后台运行呢?
应该能够想到,在创建窗口的地方设置窗口的可见性为隐藏。
开始你可能会想到在CMainFrame里面,因为窗口都是在这里面创建的,并且可能会搞错,跑去OnCreateClient里面设置各个子窗口的可见性。
其实实际上设置整个程序窗口的可见性,要到CWinApp类型的InitInstance()方法里面设置窗口的可见性,因为程序的主窗口是属于程序的CWinApp类的,该类拥有m_pMainWnd窗口对象,所以设置他的可见性为隐藏,那么程序的窗口就不可见了。如下:

m_pMainWnd->ShowWindow(SW_SHOW);

窗口分割先这样粗糙的处理了,所以分割之后分割线可以拖动,如果有更好的方法,期望指教。

另外,CLeftView本来是用树形控件来显示网页标签的,但有些网页没有标签,再加上点击标签进行导航的功能没法实现,所以就没搞了。。。

你可能感兴趣的:(基于文档/视图模式实现网页浏览以及打印功能)