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