Convert CHtmlView to CHtmlCtrl

首先,這裏有兩個難點需要解決!
             一:既然最後的產物是CHtmlCtrl,如何能象其他控件(比如Button)隨意的丟到對話框裏呢? 給CStatic套上個SubclassDlgItem就可以當成我們的CHtmlCtrl用了;
            二:View的確和Frame有着千絲萬縷的聯繫。MFC是個半定製的框架,微軟已做了很多手腳,說不定你在View裏啪啪點幾下,就有幾個類似WM_MICROSPACE這樣的消息傳到了Frame裏。然而控件是沒有Frame可言的,而且控件也從不需要知道自己被放到了哪個容器裏!!

明確CHtmlView和我們最終生成的CHtmlCtrl到底有什麼區別:

其實,區別僅僅是它們被使用的方法不同。控件通常是對話框裏的子窗口---當然你可以把它作爲任何窗口的子窗口。然而View卻是專門爲了實現MFC 文檔視圖結構而設計的。一個View有一個指向Document的指針並且被固定在框架窗口(CFrameWnd)。對於Document來說,CView是它可以從形態上被表現的場作。但,指向Document的指針m_pDocument可能是NULL,所以每當我們在View裏處理Document的時候,這麼做是明智的:

If(m_pDocument!=NULL) { // Do something here! } 
所以,View並不正真的需要一個Document,CHtmlView也不需要。你可能認爲在CHtmlView裏的Document就是一個HTML文件,但實際上,CHtmlView是使用IWebBrowser2實現的,而且它對MFC的文檔視圖結構一無所知。
那麼需要一個Frame嗎? 如果你仔細研究過相關的代碼,就會發現:只是在極少地方,View知道自己屬於一個Frame。大多數文檔視圖結構是在更高一級的類比如Frame本身和CDocTemplate裏實現的,它們把Frame,View,Document緊緊的粘在了一起。View並不知道外面發生了什麼,這樣設計的文檔視圖系統有很多優點。理論上來說,View是被動地受Frame控制,而且沒有其他途徑,所以認爲View知道它自己的父窗口是錯誤的。有兩處CView( 也是CHtmlView的父類 )會假想自己在一個Frame裏。第一處是CView::onMouseActive,它是WM_MOUSEACTIVE消息的處理函數,當你在View裏點擊鼠標以後,它會做很多細緻的工作。這些細緻的工作是不重要的,但重要的是View調用了GetParentFrame以得到它的父窗口框架,然後CFrameWnd::GetActiveView激活當前的活動視圖---所有的這一切,都建立在假設View是CFrameWnd的一個子窗口的基礎之上。
另外一處是在CView::OnDestroy裏:
void CView::OnDestroy() 
{
CframeWnd* pFrame = GetParentFrame();
If(pFrame!=NULL&&pFrame->GetActiveView==this)
// deactive during death
pFrame->SetActiveView(NULL);
CWnd::OnDestroy();
}
在這裏,View先讓自己處於非活動狀態。從另一個角度考慮,我們完全可以通過向父窗口發通知消息(Notification)代替C++方法調用從而回避掉View對Frame的依賴!! GetParentFrame可以返回一個CWnd,而非CFrameWnd,因爲該函數只是強調了父窗口,而不是一定要返回一個特定的類。View可以通過發送一個類似WM_MICROSPACE的通知消息取代對CFrameWnd的方法調用,這些Notification會被"Frame"(或是CFrameWnd或是CfooWnd)正確的響應。 但是,無論如何,你必須清楚:當View被激活或進入非活動狀態時,是Frame決定該如何響應,而不是View本身。任何系統都是如此;函數向下調用(從父到子),事件向上傳遞(從子到父)。一個子類從來都不會知道自己所在的容器!! 生活本身就不是完美的! 但幸運的是我們將會很容易的克服MFC給我們帶來的難題! CHtmlCtrl類( here! )正是我們需要的:一個HTML "View" ,你可以在對話框或任何窗口裏使用。CHtmlCtrl重載了onMouseActive和OnDestroy從而回避CView那些給我們惹麻煩的代碼。
int CHtmlCtrl::onMouseActive(…) 
{
// bypass CView doc/frame stuff
return CWnd::onMouseActive(…);
}
void CHtmlCtrl::OnDestroy() 
{
// bypass CView doc/frame stuff
CWnd::OnDestroy();
}
除此之外,CHtmlCtrl也重載了PostNcDestroy
void CHtmlCtrl::PostNcDestroy() { // do nothing } 
CView正常的PostNcDestroy實現是使用delete this銷燬View。對於Views來說,這是正常的處理方式,因爲View是直接在堆裏建立的。但是,控件一般是作爲另一個窗口對象的成員存在的。這時你就不要delete了,它會被父對象delete掉。 通過以上的修改(onMouseActive,OnDestroy和PostNcDestroy),CHtmlCtrl能順利的在對話框裏工作了!! 爲此,偶寫了個小程序:AboutHtml( 見源代碼)顯示一個About框(如圖1)。那是完全用HTML寫的。 HTML的資源:圖片,音頻文件(對了!甚至可以有聲音)都被存儲到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頁面裏,如果你這麼做:vckcom.gif 就需要把vckcom.gif放到當前目錄下。對於訪問一個存儲在EXE文件裏的資源,同樣也要這樣。這種情況下,你必須使用下面的代碼幫助瀏覽器尋找你的HTML元素:
 
瀏覽器就會知道當前的"目錄"是res://AboutHtml.exe,所以當它遇到vckcom.gif,它會自動找到res://AboutHtml.exe/vckcom.gif,否則,它將在HTML文件所在的當前目錄下尋找。

abouthtml.jpg
圖一

一般來說,你可以使用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,大小和位置。這麼做很方便,很有效!:)
BOOL CHtmlCtrl::CreateFromStatic(UINT nID, CWnd* pParent) 
{
CStatic wndStatic;
if (!wndStatic.SubclassDlgItem(nID, pParent))
return FALSE;
// Get static control rect
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::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可以做任何合法的事情:
  
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。所以,儘管麻煩,我們還是很樂意嘗試。 這個也不難!! 我們同樣使用子類化的原理就能實現。
發佈了0 篇原創文章 · 獲贊 0 · 訪問量 25萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章