天天看點

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。是以,盡管麻煩,我們還是很樂意嘗試。 這個也不難!! 我們同樣使用子類化的原理就能實作。但不是現在,而是将來的某個時候!! :)

繼續閱讀