程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> View與Frame的分離

View與Frame的分離

編輯:關於VC++

Wow!! 幾篇讓人拍案的文章,啃完之後大呼過瘾!想不到微軟也有如此精通windows編程的家伙?! 此時此刻,俺想到的是分享給KBASE裡的兄弟們啊! 沒的說,掌聲伺候!!!!

[NOTE]:

羅頭說了,最好不要把Frame/Doc/View拆的妻離子散。是啊,本來好好的一家人,誰會那麼殘忍呢!? 嘿嘿,偶只是給他們弄了個遠房的親戚。:)

Now, Stop 費話ing!! Let''s go on the stuff…

首先,這裡有兩個難點需要解決! 一是:既然最後的產物是CHtmlCtrl,如何能象其他控件(比如Button)隨意的丟到對話框裡呢? COM->ActiveX?? 你說的,你自己做去吧!偶可是個COM稀裡糊塗者!! 偶要比你想象地懶的多(鼓勵程序員鍛煉一下這種惰性! 好處多多)。偶想,何不拉個替死鬼呢? 對了,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頁面裡,如果你這麼做:<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文件所在的當前目錄下尋找。

圖一

一般來說,你可以使用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可以做任何合法的事情:

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

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved