WIN32 application invoke html files

来源:互联网 发布:剑三脸型数据怎么导出 编辑:程序博客网 时间:2024/04/30 12:29
新类CHtmlView,利用这个类,我们可以实现在对话框的控制中显示HTML文件。 要想使用CHtmlView类,对它的定义和实现就必须有全面深入的理解。我们不妨拿CHtmlView和CListView做一个比较,通过比较这两个类,我们会发现一些有趣的差别。首先,MFC中CListView有一个对应的CListCtrl类,而CHtmlView却没有一个CHtmlCtrl类与之对应;其次,CListView的使用依赖于MFC的文档/视结构,而CHtmlView的实现是基于COM的。通过IWebBrowser2接口来实现,而且IWebBrowser2与MFC文档/视图结构之间没有任何关系。

为了实现在对话框的控制中显示HTML文件,我们也可以为CHtmlView创建一个对应的类CHtmlCtrl。

下面介绍一下CHtmlctrl的几个主要在demo中用到的函数

序。框架的处理

首先,这里有两个难点需要解决! 一是:既然最后的产物是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 deathpFrame->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那些给我们惹麻烦的代码。

一。 框架搭建处理

// 还记得我们刚开始谈到的CStatic吗? 我们打算用它来代表CHtmlCtrl控件,它将从CStatic建立一个CHtmlCtrl控件对象,这是一个子类化的过程,该对象将和CStatic有同样的ID,大小和位置。这么做很方便,很有效!:)

1.  创建一个静态控制(也可以是其他控制),这个控制的ID及大小位置与界面上的控制相同。

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);          ///框架/文档}
CHtmlCtrl::CreateFromStatic是个很简单的函数,它用于简化对话框的设计。因为用插入COM对象的方法太麻烦,所以我在对话框中插入了一个静态控件,改变它的缺省ID号。然后调用CreateFromStatic,以完全相同的ID号、大小、位置创建一个静态CStatic对象。然后在调用DestroyWindow,这个方法很有效。
2.为了实现“app:” 伪协议,重载导航处理器OnBeforeNavigate2()。传递“app:”链接到一个虚拟协议处理器。因为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;        }}       
    重载OnAppCmd(),处理app:命令,当浏览器准备导航到“app:foo”时,这个函数被调用,参数lpszWhere的值为“foo”。
void CHtmlCtrl::OnAppCmd(LPCTSTR lpszWhere){ // default: do nothing}
3.为了加载web页面,调用CHtmlCtrl::LoadFromResource函数,它是由CHtmlView继承而来的。也可以用全路径res://AboutHtml.exe/about.htm作为参数。
现在您已经知道了CHtmlCtrl是如何在对话框中绕过CView来替代框架;知道了如何创建HTML文件,其中包含文字、图像和声音,并把它作为资源在程序中使用。
二。网页上的处理
1. 在一般的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文件所在的当前目录下寻找。 
/ 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">     
    此代码假设图像文件"pd.jpg"存在当前目录(页面文件所在目录)中。
如果图像文件是作为资源存在EXE文件中,我们如何引用呢?方法一样,此时,我们必须告诉浏览器Web页面文件的位置。为此要在Web页面文件的开头加上如下代码:
<BASE url="res://AboutHtml.exe/about.htm">
    这一行代码告诉浏览器当前目录是“res://AboutHtml.exe”,当浏览器遇到代码<IMG src="pd.jpg">时,它会按照路径res://AboutHtml.exe/pd.jpg查找。否则,它会在程序文件的路径查找。
    通常用res://modulename可以访问动态库或可执行文件中的资源。这里res:的意思与http:,ftp:,file:,及mailto的意思相同。即:“在这个路径中的第一个名字是一个文件名,第二个名字是文件中的资源名”。其余的工作由浏览器完成。
2. 一般来说,你可以使用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;}

3. 其实“OK”并不是什么文件;它只是一个很特殊的名字,CHtmlCtrl将它看作是“OK”按钮。为了实现这个想法,程序中创建了一个叫app:的冒充协议来代替“OK”,在about.htm中实际的链接是app:ok。每当浏览器导航到app:somewhere的时候,CHtmlCtrl都以“somewhere”为参数调用一个虚函数: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中编程。
三、交互处理
然后是使用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 dialogGetParent()->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。所以,尽管麻烦,我们还是很乐意尝试。 这个也不难!! 我们同样使用子类化的原理就能实现。但不是现在,而是将来的某个时候!! :) 

原创粉丝点击