|
編譯/趙湘寧
在Visual Studio 6.0中出現(xiàn)了一個新類CHtmlView,利用這個類,我們可以實現(xiàn)在對話框的控制中顯示HTML文件。 要想使用CHtmlView類,對它的定義和實現(xiàn)就必須有全面深入的理解。我們不妨拿CHtmlView和CListView做一個比較,通過比較這兩個類,我們會發(fā)現(xiàn)一些有趣的差別。首先,MFC中CListView有一個對應(yīng)的CListCtrl類,而CHtmlView卻沒有一個CHtmlCtrl類與之對應(yīng);其次,CListView的使用依賴于MFC的文檔/視結(jié)構(gòu),而CHtmlView的實現(xiàn)是基于COM的。通過IWebBrowser2接口來實現(xiàn),而且IWebBrowser2與MFC文檔/視圖結(jié)構(gòu)之間沒有任何關(guān)系。 為了實現(xiàn)在對話框的控制中顯示HTML文件,我們也可以為CHtmlView創(chuàng)建一個對應(yīng)的類CHtmlCtrl。以下是類CHtmlCtrl程序源代碼: 創(chuàng)建一個靜態(tài)控制(也可以是其他控制),這個控制的ID及大小位置與界面上的控制相同。 BOOL CHtmlCtrl::CreateFromStatic(UINT nID, CWnd* pParent)
{
CStatic wndStatic;
if (!wndStatic.SubclassDlgItem(nID, pParent))
return FALSE;
// 獲取靜態(tài)控制的矩形區(qū)域并轉(zhuǎn)換為父窗口的客戶區(qū)坐標
CRect rc;
wndStatic.GetWindowRect(&rc);
pParent->ScreenToClient(&rc);
wndStatic.DestroyWindow();
// 創(chuàng)建 HTML 控制 (CHtmlView)
return
Create(NULL, // 類名
NULL, // 標題
(WS_CHILD | WS_VISIBLE ), // 風格
rc, // 矩形區(qū)域
pParent, // /父窗口
nID, // 控制 ID
NULL); ///框架/文檔
}
為了避免主控程序?qū)HtmlView對象看作是文檔/視圖框架,需要重載,CView::OnMouseActivate和CView::OnDestroy。此外,當用戶在控制中單擊時,OnMouseActivate要負責響應(yīng)(WM_MOUSEACTIVATE)。
int CHtmlCtrl::OnMouseActivate(CWnd* pDesktopWnd, UINT nHitTest, UINT msg)
{
//旁路 CView 文檔/框架
return CWnd::OnMouseActivate(pDesktopWnd, nHitTest, msg);
}
void CHtmlCtrl::OnDestroy()
{
if (m_pBrowserApp)
{
m_pBrowserApp->Release();
m_pBrowserApp = NULL;
}
CWnd::OnDestroy(); // 旁路 CView 文檔/框架
}
通常,CHtmlView是在virtual void PostNcDestroy()中釋放空間,但對話框中的控制常常是作為堆棧對象實現(xiàn)的,所以,在PostNcDestroy()中不必在做什么。
virtual void PostNcDestroy() { }
為了實現(xiàn)“app:” 偽協(xié)議,重載導(dǎo)航處理器OnBeforeNavigate2()。傳遞“app:”鏈接到一個虛擬協(xié)議處理器。因為app:是假協(xié)議,所以在瀏覽起重要取消掉這個導(dǎo)航。
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);
if (_tcsnicmp(lpszURL, APP_PROTOCOL, len)==0)
{
OnAppCmd(lpszURL + len);
*pbCancel = TRUE;
}
}
重載OnAppCmd(),處理app:命令,當瀏覽器準備導(dǎo)航到“app:foo”時,這個函數(shù)被調(diào)用,參數(shù)lpszWhere的值為“foo”。
void CHtmlCtrl::OnAppCmd(LPCTSTR lpszWhere){ // default: do nothing}
重載OnMouseActivate, OnDestroy, 和 PostNcDestroy以后,CHtrmlCtrl在對話框中就可以象個控制一樣工作。詳細的使用方法請參見例子程序:AboutHtml。 運行AboutHtml.exe,并打開About對話框……音樂多么美妙!更有趣的是程序所用到的HTML源文件、圖像、聲音等文件都作為資源存儲在EXE文件中:
// in AboutHtml.rc ABOUT.HTM HTML DISCARDABLE "res\\about.htm" PD.JPG HTML DISCARDABLE "res\\pd.jpg" OKUP.GIF HTML DISCARDABLE "res\\okup.gif" OKDN.GIF HTML DISCARDABLE "res\\okdn.gif" MOZART.WAV HTML DISCARDABLE "res\\mozart.wav"注意:用文件的實際名字作為資源名很重要,以便瀏覽器能夠找到他們。在一個普通的Web頁面中,我們使用圖像是用下列語法: <IMG src="pd.jpg">此代碼假設(shè)圖像文件"pd.jpg"存在當前目錄(頁面文件所在目錄)中。 如果圖像文件是作為資源存在EXE文件中,我們?nèi)绾我媚??方法一樣,此時,我們必須告訴瀏覽器Web頁面文件的位置。為此要在Web頁面文件的開頭加上如下代碼: <BASE url="res://AboutHtml.exe/about.htm">這一行代碼告訴瀏覽器當前目錄是“res://AboutHtml.exe”,當瀏覽器遇到代碼<IMG src="pd.jpg">時,它會按照路徑res://AboutHtml.exe/pd.jpg查找。否則,它會在程序文件的路徑查找。 通常用res://modulename可以訪問動態(tài)庫或可執(zhí)行文件中的資源。這里res:的意思與http:,ftp:,file:,及mailto的意思相同。即:“在這個路徑中的第一個名字是一個文件名,第二個名字是文件中的資源名”。其余的工作由瀏覽器完成。 為了實現(xiàn)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是個很簡單的函數(shù),它用于簡化對話框的設(shè)計。因為用插入COM對象的方法太麻煩,所以我在對話框中插入了一個靜態(tài)控件,改變它的缺省ID號。然后調(diào)用CreateFromStatic,以完全相同的ID號、大小、位置創(chuàng)建一個靜態(tài)CStatic對象。然后在調(diào)用DestroyWindow,這個方法很有效。為了加載web頁面,調(diào)用CHtmlCtrl::LoadFromResource函數(shù),它是由CHtmlView繼承而來的。也可以用全路徑res://AboutHtml.exe/about.htm作為參數(shù)。 <A href="ok"><IMG ...></A>當用戶單擊它時,瀏覽器顯示這個“OK”文件,但是在顯示之前,控制先執(zhí)行CHtmlCtrl::OnBeforeNavigate2。CHtmlCtrl能夠在這個函數(shù)中做想做的任何事情。 void CMyHtmlCtrl::OnBeforeNavigate2(
LPCTSTR lpszURL,
...,
BOOL* pbCancel)
{
if (_tcscmp(lpszURL,_T("ok"))==0)
{
// "ok" clicked:
*pbCancel=TRUE; // abort
// will close dialog
GetParent()->SendMessage(WM_COMMAND,IDOK);
}
}
其實“OK”并不是什么文件;它只是一個很特殊的名字,CHtmlCtrl將它看作是“OK”按鈕。為了實現(xiàn)這個想法,程序中創(chuàng)建了一個叫app:的冒充協(xié)議來代替“OK”,在about.htm中實際的鏈接是app:ok。每當瀏覽器導(dǎo)航到app:somewhere的時候,CHtmlCtrl都以“somewhere”為參數(shù)調(diào)用一個虛函數(shù):CHtmlCtrl::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” 、 字符串,它有點象在VB中編程。參照例子程序,將自己的About對話框改進一番吧。如果有興趣的話,您甚至可以利用這個技術(shù)來實現(xiàn)復(fù)活節(jié)彩蛋。 附錄一:CHtmlCtrl類的定義和實現(xiàn)
//定義文件
HtmlCtrl.h
//
class CHtmlCtrl : public CHtmlView {
public:
CHtmlCtrl() { }
~CHtmlCtrl() { }
BOOL CreateFromStatic(UINT nID, CWnd* pParent);
// Normally, CHtmlView destroys itself in PostNcDestroy,
// but we don't want to do that for a control since a control
// is usually implemented as a stack object in a dialog.
//
virtual void PostNcDestroy() { }
// overrides to bypass MFC doc/view frame dependencies
afx_msg void OnDestroy();
afx_msg int OnMouseActivate(CWnd* pDesktopWnd,UINT nHitTest,UINT message);
// override to trap "app:" pseudo protocol
virtual void OnBeforeNavigate2( LPCTSTR lpszURL,
DWORD nFlags,
LPCTSTR lpszTargetFrameName,
CByteArray& baPostedData,
LPCTSTR lpszHeaders,
BOOL* pbCancel );
// override to handle links to "app:mumble...". lpszWhere will be "mumble"
virtual void OnAppCmd(LPCTSTR lpszWhere);
DECLARE_MESSAGE_MAP();
DECLARE_DYNAMIC(CHtmlCtrl)
};
//實現(xiàn)文件
HtmlCtrl.cpp
//
#include "StdAfx.h"
#include "HtmlCtrl.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
IMPLEMENT_DYNAMIC(CHtmlCtrl, CHtmlView)
BEGIN_MESSAGE_MAP(CHtmlCtrl, CHtmlView)
ON_WM_DESTROY()
ON_WM_MOUSEACTIVATE()
END_MESSAGE_MAP()
//////////////////
// Create control in same position as an existing static control with
// the same ID (could be any kind of control, really)
//
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
}
////////////////
// Override to avoid CView stuff that assumes a frame.
//
void CHtmlCtrl::OnDestroy()
{
// This is probably unecessary since ~CHtmlView does it, but
// safer to mimic CHtmlView::OnDestroy.
if (m_pBrowserApp) {
m_pBrowserApp->Release();
m_pBrowserApp = NULL;
}
CWnd::OnDestroy(); // bypass CView doc/frame stuff
}
////////////////
// Override to avoid CView stuff that assumes a frame.
//
int CHtmlCtrl::OnMouseActivate(CWnd* pDesktopWnd, UINT nHitTest, UINT msg)
{
// bypass CView doc/frame stuff
return CWnd::OnMouseActivate(pDesktopWnd, nHitTest, msg);
}
//////////////////
// Override navigation handler to pass to "app:" links to virtual handler.
// Cancels the navigation in the browser, since app: is a pseudo-protocol.
//
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);
if (_tcsnicmp(lpszURL, APP_PROTOCOL, len)==0) {
OnAppCmd(lpszURL + len);
*pbCancel = TRUE;
}
}
void CHtmlCtrl::OnAppCmd(LPCTSTR lpszWhere)
{
// default: do nothing
}
|
|
|