VC中添加HTML網頁

微軟的MFC在Visual Studio 6.0中提供了一個新類CHtmlView,利用這個類,我們可以實現在基於文檔視圖結構的程序中顯示HTML文件 但是它是否可以用來在對話框中實現這一功能呢?我們不妨拿CHtmlView和CListView做一個比較,通過比較這兩個類,我們會發現一些有趣的差別,MFC中CListView有一個對應的CListCtrl類用來在對話框中使用,而CHtmlView卻沒有一個CHtmlCtrl類與之對應所以爲了實現在對話框的控制中顯示HTML文件,我們不得不爲CHtmlView創建一個對應的子類CHtmlCtrl爲了演示該類的使用方法,本實例在程序的About對話框中顯示一個名爲"about.htm"的HTML文件更有趣的是,程序所用到的HTML源文件是作爲資源存儲在EXE文件中的該程序編譯運行後的效果如圖一所示:

在VC對話框中用ChtmlView控件顯示HTML
圖一、顯示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對話框改進一番吧如果有興趣的話,您甚至可以利用這個技術來實現復活節彩蛋
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章