圖一、顯示HTML文件的對話框
一、實現方法
爲了在對話框中顯示HTML文件,我們必須將CHtmlCtrl類與對話框中的一個靜態控制(也可以是其它控制)關聯起來,這樣才能爲顯示HTML文件提供一個窗口,爲此我們在CHtmlCtrl類中定義了CreateFromStatic()函數,具體代碼如下:
BOOL CHtmlCtrl::CreateFromStatic(UINT nID, CWnd* pParent) { CStatic wndStatic; //靜態控件對象; if (!wndStatic.SubclassDlgItem(nID, pParent)) return FALSE; // 獲取靜態控制的矩形區域並轉換爲父窗口的客戶區座標 CRect rc; wndStatic.GetWindowRect(&rc); pParent->ScreenToClient(&rc); wndStatic.DestroyWindow(); // 創建 HTML 控制 (CHtmlView) return (Create(NULL, // 類名; NULL, // 標題; (WS_CHILD | WS_VISIBLE ), // 風格; rc, // 矩形區域; pParent, // 父窗口; nID, // 控制的ID號; NULL)); //取消文檔框架支持; } |
爲了避免主控程序將CHtmlView對象看作是文檔/視圖框架,需要重載CView::OnMouseActivate()和CView::OnDestroy()函數。此外,當用戶在控制中單擊時,OnMouseActivate要負責響應(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()中釋放空間,但對話框中的控制常常是作爲堆棧對象實現的,所以,在PostNcDestroy()中不必在做什麼。
爲了播放資源中的HTML文件,需要重載導航處理器OnBeforeNavigate2(), 實現"app:" 僞協議,。傳遞"app:"鏈接到一個虛擬協議處理器。因爲app:是假協議,所以需要設置pbCancel參數爲"TRUE",以停止掉這個導航。 不太懂,呵呵。HTML裏面的吧
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:命令,例如當瀏覽器準備導航到"app:foo"時,這個函數被調用,參數lpszWhere的值爲"foo"。
void CHtmlCtrl::OnAppCmd(LPCTSTR lpszWhere){ // default: do nothing} |
對於作爲資源的HTML文件和其中的嵌入的圖片和音樂文件,用文件的實際名字作爲資源名很重要,以便瀏覽器能夠找到他們。在一個普通的Web頁面中,我們使用圖像是用下列語法:
<IMG src="pd.jpg"> |
此代碼假設圖像文件"pd.jpg"存在當前目錄(頁面文件所在目錄)中。如果圖像文件是作爲資源存在EXE文件中,我們如何引用呢?方法一樣,此時,我們必須告訴瀏覽器Web頁面文件的位置。爲此要在Web頁面文件的開頭加上如下代碼:
<BASE url="res://ShowHtml.exe/about.htm"> |
這一行代碼告訴瀏覽器當前目錄是"res://ShowHtml.exe",當瀏覽器遇到代碼<IMG src="pd.jpg">時,它會按照路徑res://ShowHtml.exe/pd.jpg查找。否則,它會在程序文件的路徑查找。通常用res://modulename可以訪問動態庫或可執行文件中的資源。這裏res:的意思與http:,ftp:,file:,及mailto的意思相同。即:"在這個路徑中的第一個名字是一個文件名,第二個名字是文件中的資源名"。其餘的工作由瀏覽器完成。
爲了在對話框中加載web頁面,調用CHtmlCtrl::LoadFromResource函數,它是由CHtmlView繼承而來的。也可以用全路徑res://ShowHtml.exe/about.htm作爲參數。除此之外,還有一個問題就是:CAboutDialog對話框中"OK"按鈕的處理,其實,它根本就不是一個按鈕,而是一個在HTML文件中嵌入的圖像,用JScript來控制圖像被按下時和彈起時的狀態。處理"OK"按鈕的技巧主要是解決對話框與主控程序之間的通訊。利用動態HTML文檔層(COM)技術可以處理用戶單擊圖像或鏈接,方法是獲得圖像元素,然後偵聽OnClick事件。但這是一種非常非常麻煩的方法。還有一種更簡單的方法。假設HTML有如下的圖像鏈接:
<A href="ok"><IMG ...></A> |
當用戶單擊它時,瀏覽器顯示這個"OK"文件,但是在顯示之前,控制先執行CHtmlView::OnBeforeNavigate2()函數,爲此可以定義CHtmlCtrl類的子類CMyHtmlCtrl,重載這個函數,在這裏面實現想做的任何事情。下面的代碼實現了當用戶點擊HTML文件上的"OK"圖片時,關閉對話框。
void CHtmlCtrl::OnBeforeNavigate2( LPCTSTR lpszURL, ..., BOOL* pbCancel) { if (_tcscmp(lpszURL,_T("ok"))==0) { // "ok" clicked: *pbCancel=TRUE; // abort GetParent()->SendMessage(WM_COMMAND,IDOK); // will close dialog } } |
其實"OK"並不是什麼文件;它只是一個很特殊的名字,可以定義一個CHtmlCtrl類的子類CMyHtmlCtrl,該類將"OK"圖片看作是"OK"按鈕。爲了實現這個想法,程序中創建了一個叫app:的冒充協議來代替"OK",在about.htm中定義實際的鏈接是app:ok。每當瀏覽器導航到app:somewhere的時候,CMyHtmlCtrl都以"somewhere"爲參數調用一個虛函數:CMyHtmlCtrl::OnAppCmd。
void CMyHtmlCtrl::OnAppCmd( LPCTSTR lpszWhere ) { if (_tcsicmp(lpszWhere, _T("ok"))==0) { GetParent()->SendMessage(WM_COMMAND,IDOK); } } |
二、編程步驟
1、啓動Visual C++6.0,生成一個單文檔的應用程序,命名爲"ShowHtml";
2、修改程序中的"About"對話框資源,在其中放置一個Static控件,設置它的ID爲IDC_HTMLVIEW;
3、向程序中添加HTML文件資源,其ID設置爲"About.htm";
4、向程序中添加CHtmlCtrl、CMyHtmlCtrl類文件;
5、在CAbout類中增加一個CMyHtmlCtrl類的對象m_page,並使用CLASSWIZARD重載CAbout類的OnInitDialog()函數;
6、編譯運行程序。
三、程序代碼
////////////////////////////////////////////////////CHtmlCtrl類的頭文件; #include "afxhtml.h" class CHtmlCtrl : public CHtmlView { public: CHtmlCtrl() { } ~CHtmlCtrl() { } //使CHtmlCtrl控件與靜態控件建立關聯; BOOL CreateFromStatic(UINT nID, CWnd* pParent); virtual void PostNcDestroy() { } //重載下面兩個函數,旁路ChtmlView類的文檔視圖結構; afx_msg void OnDestroy(); afx_msg int OnMouseActivate(CWnd* pDesktopWnd, UINT nHitTest, UINT msg); // 實現"app"僞協議; virtual void OnBeforeNavigate2( LPCTSTR lpszURL, DWORD nFlags, LPCTSTR lpszTargetFrameName, CByteArray& baPostedData, LPCTSTR lpszHeaders, BOOL* pbCancel ); virtual void OnAppCmd(LPCTSTR lpszWhere); DECLARE_MESSAGE_MAP(); DECLARE_DYNAMIC(CHtmlCtrl) }; //////////////////////////////////////////////CHtmlCtrl類的實現文件; #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() BOOL CHtmlCtrl::CreateFromStatic(UINT nID, CWnd* pParent) { CStatic wndStatic; if (!wndStatic.SubclassDlgItem(nID, pParent)) return FALSE; // 獲取靜態控件的尺寸,並銷燬該控件的窗口; CRect rc; wndStatic.GetWindowRect(&rc); pParent->ScreenToClient(&rc); wndStatic.DestroyWindow(); // 創建一個HtmlView控件; 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 } void CHtmlCtrl::OnDestroy() { if (m_pBrowserApp) {釋放瀏纜器的m_pBrowserApp成員變量; m_pBrowserApp->Release(); m_pBrowserApp = NULL; } CWnd::OnDestroy(); // bypass CView doc/frame stuff } int CHtmlCtrl::OnMouseActivate(CWnd* pDesktopWnd, UINT nHitTest, UINT msg) { //旁路文檔視圖結構; return CWnd::OnMouseActivate(pDesktopWnd, nHitTest, msg); } //////////////////實現"app"僞協議; 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 } ///////////////////////////////////////////////////// class CMyHtmlCtrl : public CHtmlCtrl { virtual void OnAppCmd(LPCTSTR lpszWhere); }; /////////////////// 處理HTML文件上的 "app:ok"鏈接,關閉對話框; void CMyHtmlCtrl::OnAppCmd(LPCTSTR lpszWhere) { if (_tcsicmp(lpszWhere,_T("ok"))==0) { GetParent()->SendMessage(WM_COMMAND,IDOK); } } //////////////////////////////////////// class CAboutDlg : public CDialog { public: CAboutDlg(); CMyHtmlCtrl m_page; // Dialog Data //{{AFX_DATA(CAboutDlg) enum { IDD = IDD_ABOUTBOX }; //}}AFX_DATA // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CAboutDlg) protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support //}}AFX_VIRTUAL // Implementation protected: //{{AFX_MSG(CAboutDlg) virtual BOOL OnInitDialog(); //}}AFX_MSG DECLARE_MESSAGE_MAP() }; CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD) { //{{AFX_DATA_INIT(CAboutDlg) //}}AFX_DATA_INIT } void CAboutDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CAboutDlg) //}}AFX_DATA_MAP } BEGIN_MESSAGE_MAP(CAboutDlg, CDialog) //{{AFX_MSG_MAP(CAboutDlg) //}}AFX_MSG_MAP END_MESSAGE_MAP() // App command to run the dialog void CShowHtmlApp::OnAppAbout() { CAboutDlg aboutDlg; aboutDlg.DoModal(); } BOOL CAboutDlg::OnInitDialog() { CDialog::OnInitDialog(); VERIFY(CDialog::OnInitDialog()); VERIFY(m_page.CreateFromStatic(IDC_HTMLVIEW, this)); m_page.LoadFromResource(_T("ABOUT.HTM")); return TRUE; } |
四、小結
在上述實例中,讀者朋友還可以在HTML文件中作其他的鏈接,諸如:app:cancel, app:refresh, 或 app:whatever等等,並且在OnAppCmd中編寫自己的代碼來處理 "cancel"、"refresh"、和"whatever"等字符串。參照例子程序,將自己的About對話框改進一番吧。如果有興趣的話,您甚至可以利用這個技術來實現復活節彩蛋。