第一章:
首先介绍一下类 :
CHtmlView利用这个类,我们可以实现在对话框的控制中显示HTML文件。CHtmlView和CListView做一个比较,通过比较这两个类,我们会发现一些有趣的差别。首先,MFC中CListView有一个对应的CListCtrl类,而CHtmlView却没有一个CHtmlCtrl类与之对应;其次,CListView的使用依赖于MFC的文档/视结构,而CHtmlView的实现是基于COM的。通过IWebBrowser2接口来实现,而且IWebBrowser2与MFC文档/视图结构之间没有任何关系。为了实现在对话框的控制中显示HTML文件,我们也可以为CHtmlView创建一个对应的类CHtmlCtrl。
这里有两个难点需要解决! 一是:既然最后的产物是CHtmlCtrl,如何能象其他控件(比如Button)随意的丢到对话框里呢? CStatic可以随便被使用,加上一个SubclassDlgItem不就可以当成我们的CHtmlCtrl使用! 然后是:View的确和Frame有着千丝万缕的联系。MFC是个半定制的框架,微软已做了很多手脚,说不定你在View里啪啪点几下,就有几个类似WM_MICROSPACE这样的消息传到了Frame里。然而控件是没有Frame可言的,而且控件也从不需要知道自己被放到了哪个容器里!!
1。主要函数介绍:
// 还记得我们刚开始谈到的CStatic吗? 我们打算用它来代表CHtmlCtrl控件,它将从CStatic建立一个CHtmlCtrl控件对象,这是一个子类化的过程,该对象将和CStatic有同样的ID,大小和位置。这么做很方便,很有效!:)
1.1 创建一个静态控制(也可以是其他控制),这个控制的ID及大小位置与界面上的控制相同。
BOOL CHtmlCtrl::CreateFromStatic(UINT nID, CWnd* pParent)
{
CStatic wndStatic;
if (!wndStatic.SubclassDlgItem(nID, pParent))
return FALSE;
// Get static control rect, convert to parent's client coords.
CRect rc;
wndStatic.GetWindowRect(&rc);
pParent->ScreenToClient(&rc);
wndStatic.DestroyWindow();
// create HTML control (CHtmlView)
return Create(NULL, // class name
NULL, // title
(WS_CHILD | WS_VISIBLE ), // style
rc, // rectangle
pParent, // parent
nID, // control ID
NULL); // frame/doc context not used
}
CHtmlCtrl::CreateFromStatic是个很简单的函数,它用于简化对话框的设计。因为用插入COM对象的方法太麻烦,所以我在对话框中插入了一个静态控件,改变它的缺省ID号。然后调用CreateFromStatic,以完全相同的ID号、大小、位置创建一个静态CStatic对象。然后在调用DestroyWindow,这个方法很有效。 我们打算用它来代表CHtmlCtrl控件,它将从CStatic建立一个CHtmlCtrl控件对象,这是一个子类化的过程,该对象将和CStatic有同样的ID,大小和位置。这么做很方便,很有效!:)
1.2
为了实现“app:” 伪协议,重载导航处理器OnBeforeNavigate2()。传递“app:”链接到一个虚拟协议处理器。因为app:是假协议,所以在浏览器取消掉这个导航。同时如果传递过来的lpszURL是一个带着数据的字符串,完成对数据的解析存在vector<string>datas当中。
void CHtmlCtrl::OnBeforeNavigate2( LPCTSTR lpszURL,
DWORD nFlags,
LPCTSTR lpszTargetFrameName,
CByteArray& baPostedData,
LPCTSTR lpszHeaders,
BOOL* pbCancel )
{
const char APP_PROTOCOL[] = "app:";
int len = _tcslen(APP_PROTOCOL);
//deal the data and the OnappCmd
vector<string> dates;
LPCTSTR appCmd;
string delimiter("@");
CStringSplit::StringSplit1(lpszURL,dates,delimiter);
//set the tokens to the token
SetTokens(dates);
vector<string>::iterator vister = dates.begin();
string tempOnAppCmd;
tempOnAppCmd = *vister;
appCmd = tempOnAppCmd.c_str();
//acquire the data of the vister
/*string tempStr;
for(; vister != dates.end();vister++)
{
tempStr = *vister;
cout<<tempStr<<endl;
}*/
if (_tcsnicmp(appCmd, APP_PROTOCOL, len)==0) {
OnAppCmd(appCmd + len);
*pbCancel = TRUE;
}
}
重载OnAppCmd(),处理app:命令,当浏览器准备导航到“app:foo”时,这个函数被调用,参数lpszWhere的值为“foo”。 void CHtmlCtrl::OnAppCmd(LPCTSTR lpszWhere){ // default: do nothing}
1.3
当传递消息到OnAppCmd时,函数做的处理是遍历一遍m_cmdmap中的消息,如果发现有相同的消息则发送消息。 当浏览器试图导航到 "app:foo" 时调用该函数.
默认的处理例程查找"foo"命令的命令映射,并向找到的父窗口发送
WM_COMMAND 消息。调用 SetCmdMap 设置命令映射。如果要实现更
复杂的处理,只要重写这个函数即可.
void CHtmlCtrl::OnAppCmd(LPCTSTR lpszWhere)
{
// default: do nothing
if (m_cmdmap){
for (int i=0; m_cmdmap[i].name; i++)
{
if (_tcsicmp(lpszWhere,m_cmdmap[i].name) ==0 )
{
GetParent()->PostMessage(WM_COMMAND, m_cmdmap[i].nId);
}
}
}
}
在chtmlctrl类的头文件当中定义了 消息结构体--HTMLCMDMAP
// define the struct that map the
// ID_APP_ABOUT & "about"
// and the "about" is a entrance of the <a> in file of html
// so if you click the ID_APP_ABOUT will call the CHtmlCtrl::SetCmdMAp
struct HTMLCMDMAP {
LPCTSTR name;
UINT nId;
};
1.4
通过串设置 HTML 文档内容
HRESULT CHtmlCtrl::SetHTML(LPCTSTR strHTML)
{
HRESULT hr;
// 获得文档对象
SPIHTMLDocument2 doc = GetHtmlDocument();
// 创建只有一个元素(串)的 BSTR 数组元素
// IHTMLDocument2::write.
CComSafeArray<VARIANT> sar;
sar.Create(1,0);
sar[0] = CComBSTR(strHTML);
// 打开文档并写入
LPDISPATCH lpdRet;
HRCHECK(doc->open(CComBSTR("text/html"),
CComVariant(CComBSTR("_self")),
CComVariant(CComBSTR("")),
CComVariant((bool)1),
&lpdRet));
HRCHECK(doc->write(sar)); // write contents to doc
HRCHECK(doc->close()); // close
lpdRet->Release(); // release IDispatch returned
return S_OK;
}
下面我们一步一步来分析实现过程,首先必须获取 IHTMLDocument2 接口: SPIHTMLDocument2 doc = GetHtmlDocument();
SPIHTMLDocument2 与 CComQIPtr<IHTMLDocument2> 一样是一个指向 IHTMLDocument2 的ATL智能指针,(当今 Windows 编程已进入 COM 时代,作为一名编写 Windows 应用程序的开发人员,如果你使用 COM 技术,但没有用过智能指针,那么这段代码会对你有所裨益),接着,必须创建一个SAFEARRAY,以便存放作为 BSTR 数组唯一元素的 HTML 串,SAFEARRAY是一个 COM 数据结构,其作用是在不同平台之间安全地传递数组数据,ATL提供了 CComBSTR 和 CComSafeArray 两个类,为开发人员在处理 BSTRs 和安全数组时减轻了许多痛苦: // strHTML is LPCTSTR
CComSafeArray<VARIANT> sar;
sar.Create(1,0);
sar[0] = CComBSTR(strHTML);
如果不借助于 CComSafeArray 和 CComBSTR,而是用下列这些 API 函数来实现相同的处理,如 SafeArrayCreateVector,SafeArrayAccessData, 和 SafeArrayUnaccessData,那么至少还得写10-20行无聊的代码。一旦你上手了智能指针,你会觉得ATL的这些东西用起来真的很爽。
现在有了文档对象以及在安全数组中的内容,接下来便可以打开文档,进行写入操作,关闭文档等等。IHTMLDocument2::write需要 VARIANTS 和 BSTRs 类型的数据,这里ATL又一次显示了它的优势: LPDISPATCH lpdRet;
doc->open(CComBSTR("text/html"), // MIME type
CComVariant(CComBSTR("_self")), // open in same window
CComVariant(CComBSTR("")), // no features
CComVariant((bool)1), // replace history entry
&lpdRet)); // IDispatch returned
doc->write(sar); // write it
doc->close(); // close
lpdRet->Release();
1.5
FormatWindowListHTML完成的是将得到的vector<string>dates的数据动态的组装成为html格式的文件。
//
//Function:
//to format the string to the html
//
CString CHtmlCtrl::FormatWindowListHTML(vector<string> &dates)
{
CString html = _T("<HTML><BODY STYLE=\"font-family:Verdana;\" \
link=\"#02B7B7\" vlink=\"#02B7B7\">\n");
//CHtmlView \
html += _T("<font size=\"3\">\
The demo of the html invoked by C++ application</font><b><font size=\"3\">CHtmlView</font></b><font size=\"3\">derive the new class</font> \
<b><font size=\"3\">CHtmlCtrl</font></b><font size=\"3\">In the about dialog, embeded the web,and introduce the method how to embed the html \
files in the EXE or DLL. can control the dailog and any other window like the other control. besides This demo give a method how to create \
html document dramatic.");
html += _T("<p></p><TABLE WIDTH=\"100%\">\n\
<TR><TD VALIGN=top><A target=\"new\" HREF=\"http://wikicentral.cisco.com/display/SZPTRA/CSG+Suzhou+PT+Section\"><IMG \
PT demo\" SRC=\"https://go.webex.com/mw0306lc/mywebex/html/img/webexbrand.gif\"></A></TD>\
<TD COLSPAN=2><B>AboutHtml3 demo --people</B>\
<SMALL><A target=\"new\" \
HREF=\"http://wikicentral.cisco.com/display/SZPTRA/CSG+Suzhou+PT+Section\"> PT wiki </A></SMALL></TD></TR>\n\
<TR><TD><B>appname</B></TD><TD><B>NAME</B></TD><TD WIDTH=75%><B>SECTION</B></TD><TR>\n");
//dramatic add the people's information
string PName;
string pHomeTown;
string pSection;
vector<string>::iterator vister = dates.begin();
//vister++;
if(vister != dates.end())
{
PName = *vister;
vister++;
}
if(vister != dates.end())
{
pHomeTown = *vister;
vister++;
}
if(vister != dates.end())
{
pSection = *vister;
vister++;
}
const char *pn = PName.c_str();
const char *ph = pHomeTown.c_str();
const char *ps = pSection.c_str();
CString addData;
addData.Format(_T("<TR><TD>%s </TD><TD>%s</TD><TD>%s</TD></TR>\n"),
pn, ph, ps);
html += addData;
// append bottom matter. note commands app:about and app:exit
html += _T("</TABLE>\n\
<P><B>[<A HREF=\"app:about\">关于</A>] \
[<A HREF=\"app:exit\">退出</A>]</B>\n\
</BODY><br>\n\
</HTML>");
return html;
}
二。网页部分的触发函数介绍
2.1HTML的资源:图片,音频文件(对了!甚至可以有声音)都被存储到EXE文件里。 // in AboutHtml.rc
ABOUT.HTML HTML DISCARDABLE "res\\about.htm"
VCKBCOM.GIF HTML DISCARDABLE "res\\vckcom.gif"
OKUP.GIF HTML DISCARDABLE "res\\okup.gif"
OKDN.GIF HTML DISCARDABLE "res\\okdn.gif"
MOZART.WAV HTML DISCARDABLE "res\\mozart.wav"
在一般的Web页面里,如果你这么做:<IMG src="vckcom.gif"> 就需要把vckcom.gif放到当前目录下。对于访问一个存储在EXE文件里的资源,同样也要这样。这种情况下,你必须使用下面的代码帮助浏览器寻找你的HTML元素: <BASE url="res://AboutHtml.exe/about.htm">
浏览器就会知道当前的"目录"是res://AboutHtml.exe,所以当它遇到<IMG src="vckcom.gif">,它会自动找到res://AboutHtml.exe/vckcom.gif,否则,它将在HTML文件所在的当前目录下寻找。
2.2在about.html文件当中发送信息。
有两种方法
一个是<a>超链接当中的超链接时间,由onbeforenavigate监听得到。
第二种方式取得数据的方式是,放松window.navigate()的方法。
function OnClickInput(inputbox){
var namecontent = document.getElementById("name").value;
var teamcontent = document.getElementById("team").value;
window.navigate("app:[email protected]"+ namecontent + "@" + teamcontent);
}
<p>Your Name:
<input type=text style="width:100px;" id="name" >
<p>Your Team:
<input type=text style="width:100px;" id="team" ></p>
<input type=BUTTON value="Submit" onClick="OnClickInput()" style="width:15%" style="float:right;">
<a onMouseDown="OnClickOK(1);"
onMouseUp="OnClickOK(0);"
onMouseOut="OnClickOK(0);" href="app:ok" target="_blank" rel="external nofollow" >
<img name="okImage" src="okup.gif" align="right" width="80"
height="20"></a>
<a onMouseDown="OnClickOK(2);"
onMouseUp="OnClickOK(3);"
onMouseOut="OnClickOK(3);" href="app:another" target="_blank" rel="external nofollow" >
<img name="okImage" src="webex.jpg" align="right" width="80"
height="20"></a>
三。一个交互的流程
一个网页怎么与win32 application的应用程序交互呢?----》“app:”伪协议来创建HTML链接(也就是锚点)与应用程序通信。例如:你可以象下面这样添加一个链接: <A HREF="app:about">About</A>
然后,CHtmlCtrl::OnBeforeNavigate2 会识别出“app:”伪协议并以“about”作为参数调用专门的虚函数 CHtmlCtrl::OnAppCmd 。你可以创建自己的命令并在派生类中改写 OnAppCmd 来处理自己建立的命令。使用了 CHtmlCtrl 一段时间后。我发现经常需要派生 CHtmlCtrl 类,每次都得改写这个函数,自己感觉很麻烦!为了简化这个过程,我发明了一个简单的命令映射机制,利用这种机制可以轻松将“app:command”之类的转换为通常熟知的 WM_COMMAND 命令 ID: HTMLCMDMAP MyHtmlCmds[] = {
{ _T("about"), ID_APP_ABOUT },
{ _T("exit"), ID_APP_EXIT },
{ NULL, 0 },
};
这个映射机制的使用方法是象下面这样调用 CHtmlCtrl::SetCmdMap 函数: m_wndHtmlCtrl.SetCmdMap(MyHtmlCmds);
这样一来,当用户单击“app:about”链接时,CHtmlCtrl::OnAppCmd 便会搜索命令映射,找到“about”入口,然后将与ID_APP_ABOUT 对应的 WM_COMMAND 消息发送到其父窗口,这个技巧主要是仰仗MFC神奇的命令路由通道实现的,借助此通道,任何窗口都可以处理此命令。真是爽啊!本文例子程序正是用这种特性将“关于”和“退出”命令作为HTML链接直接添加到主窗口中。
一般来说,你可以使用res://modulename访问任何存储在EXE和DLL里的资源。res:是一个类似http:,ftp:,file:,或mailto:的协议。它告诉浏览器资源的路径和名字,细节工作浏览器知道如何去做!:) 为实现About对话框,偶写了个类,CAboutDialog,它有个CHtmlCtrl类型的成员m_page。我们来看看CaboutDialog的初始化过程: BOOL CaboutDialog::OnInitDialog()
{
VERIFY(Cdialog::OnInitDialog());
VERIFY(m_page.CreateFromStatic(IDC_HTMLVIEW,this));
m_page.LoadFromResource(_T("about.htm"));
return TRUE;
}
你可能对CHtmlCtrl::CreateFromStatic有点迷惑。 还记得我们刚开始谈到的CStatic吗? 我们打算用它来代表CHtmlCtrl控件,它将从CStatic建立一个CHtmlCtrl控件对象,这是一个子类化的过程,该对象将和CStatic有同样的ID,大小和位置。这么做很方便,很有效!:)
然后是使用CHtmlCtrl::LoadFromResource打开页面,它从CHtmlView继承而来。当然偶也可以这样打开页面:res://AboutHtml.exe/about.html OK! 偶已经向你展示了CHtmlCtrl如何通过回避CView而顺利代替frame在dialog里显示!。偶也介绍了如何如何在资源文件里定位HTML文件和图象文件。并且告诉你如何打开一个Web页面。 但还有一个极为精彩的处理没有告诉你!:) ,能猜到是什么吗? 哇哈哈哈!!! 看到About对话框里的OK按钮了吧? 它并不是一个按钮!!仅仅是HTML文件里的一副图片! 偶使用了Javascript使得它在被单击时有up和down两种状态,但是它是如何和我们的对话框程序通讯的呢??? 你说好玩不?
如果你搞过DHTML,你可能会想到DHTML文档层可使用COM发现IMG元素然后监听它的OnClick事件。但是那样做对于偶这样的COM半文盲是way,way,way,WAY痛苦和麻烦的工作!!:( 其实,有一个更为简单的方法。假设你让这个"button"链接到一个叫做ok的文档: 现在,当用户单击它,浏览器将转到ok文件。但在它这么做以前,控制权交给 CHtmlCtrl::OnBeforeNavigate2。这时CHtmlCtrl可以做任何合法的事情: <A href="ok" target="_blank" rel="external nofollow" ><IMG …></A>
void CmyHtmlCtrl::OnBeforeNavigate2(
LPCTSTR lpszURL,…,BOOL *pbCancel)
{
if(_tcscmp(lpszURL,_T("ok"))==0)
{
//"ok" clicked
*pbCancle=TRUE; // abort
// will close dialog
GetParent()->SendMessage(WM_COMMAND,IDOK);
}
}
[这是多么振奋人心的消息?? 想一想,我们几乎可以让对话框做几乎所有能做的事情! 而且我们可以将Web页面处理的更为美观!!:]] 所以,ok并不正真的是另一个文件,而CHtmlCtrl正是利用它来解释OK按钮!! 太完美了! 为了让这个想法更紧凑,偶引入了一个伪协议! 叫做:app:。用它来代替使用ok,在about.htm里正真的链接是app:ok。当CHtmlCtrl发现浏览器试图导航到app:somewhere时,它调用一个新的虚函数,CHtmlCtrl::OnAppCmd,它用somewhere作为参数,并cancels调航(navigation),所以CmyHtmlCtrl并没有重载OnNavigate2,而是重载了OnAppCmd: void CmyHtmlCtrl::OnAppCmd(LPCTSTR lpszWhere)
{
if(_tcsicmp(lpszWhere,_T("ok"))==0)
{
GetParent()->SendMessage(WM_COMMAND,IDOK);
}
}
你可以在HTML文件里使用其他链接,比如app:cancel,app:refresh,或者app:whatever!:) 并同时使用OnAppCmd函数寻找相应的字串,"cancel","refresh",以及"whatever"。 好了!! 可以做你自己想做的事去了!…在你疯狂的code之前,提醒几句:加载IE DLLs需要极少的等待,但是如果加载About对话框超过10秒并且搬出来个沙漏晃来晃去,用户将感到非常不舒服!:)。 最后,当你在About对话框里单击鼠标右键,会弹出个标准的浏览器快捷菜单,你可能觉得这是多余的,或者出于保护你的源代码的目的,你会买力的屏蔽调右键的功能。其实这很简单,你仅仅需要在HTML的 标签里加入一句脚本代码……但我们毕竟是在玩VC。所以,尽管麻烦,我们还是很乐意尝试。 这个也不难!! 我们同样使用子类化的原理就能实现。但不是现在,而是将来的某个时候!! :)