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 \
			   BORDER=0 ALT=\"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:input@"+ 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">    
	 
<img name="okImage" src="okup.gif" align="right" border="0" width="80"     
height="20"></a>  

<a onMouseDown="OnClickOK(2);"     
   onMouseUp="OnClickOK(3);"      
   onMouseOut="OnClickOK(3);" href="app:another">    
	 
<img name="okImage" src="webex.jpg" align="right" border="0" 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"><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。所以,儘管麻煩,我們還是很樂意嘗試。 這個也不難!! 我們同樣使用子類化的原理就能實現。但不是現在,而是將來的某個時候!! :)



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章