天天看点

win32 applications invoke the html files--3

第一章:

首先介绍一下类 :

win32 applications invoke the html files--3

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。所以,尽管麻烦,我们还是很乐意尝试。 这个也不难!! 我们同样使用子类化的原理就能实现。但不是现在,而是将来的某个时候!! :)

继续阅读