《VC Web打印解决方案概述》这篇博文里面,简单介绍了在windows下html离线、在线网页打印解决方案,针对的是特殊需求。经过好几周的研究终于完整的实现与应用配合进行打印的功能。实际应用的时候没用这种方式,用的是基于对话框模式。
这里记录一下实现的一些功能,权当学习、熟悉一下MFC编程。实现的功能有以下几点:
先看一下效果图:
就是一个非常简陋的界面,先拆分为两行,然后第二行再拆分为两列。
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的功能主要就是输入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);
}
为文件打开命令添加消息处理函数,先添加如下消息映射:
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本来是用树形控件来显示网页标签的,但有些网页没有标签,再加上点击标签进行导航的功能没法实现,所以就没搞了。。。