首先,这里有两个难点需要解决!
一:既然最后的产物是CHtmlCtrl,如何能象其他控件(比如Button)随意的丢到对话框里呢? 给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的文档视图结构一无所知。
void CView::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那些给我们惹麻烦的代码。
{
CframeWnd* pFrame = GetParentFrame();
If(pFrame!=NULL&&pFrame->GetActiveView==this)
// deactive during death
pFrame->SetActiveView(NULL);
CWnd::OnDestroy();
}
int CHtmlCtrl::onMouseActive(…)
{
// bypass CView doc/frame stuff
return CWnd::onMouseActive(…);
}
void CHtmlCtrl::OnDestroy()除此之外,CHtmlCtrl也重载了PostNcDestroy
{
// bypass CView doc/frame stuff
CWnd::OnDestroy();
}
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页面里,如果你这么做: 就需要把vckcom.gif放到当前目录下。对于访问一个存储在EXE文件里的资源,同样也要这样。这种情况下,你必须使用下面的代码帮助浏览器寻找你的HTML元素:
浏览器就会知道当前的"目录"是res://AboutHtml.exe,所以当它遇到 ,它会自动找到res://AboutHtml.exe/vckcom.gif,否则,它将在HTML文件所在的当前目录下寻找。
BOOL CaboutDialog::OnInitDialog()你可能对CHtmlCtrl::CreateFromStatic有点迷惑。 还记得我们刚开始谈到的CStatic吗? 我们打算用它来代表CHtmlCtrl控件,它将从CStatic建立一个CHtmlCtrl控件对象,这是一个子类化的过程,该对象将和CStatic有同样的ID,大小和位置。这么做很方便,很有效!:)
{
VERIFY(Cdialog::OnInitDialog());
VERIFY(m_page.CreateFromStatic(IDC_HTMLVIEW,this));
m_page.LoadFromResource(_T("about.htm"));
return TRUE;
}
BOOL CHtmlCtrl::CreateFromStatic(UINT nID, CWnd* pParent)然后是使用CHtmlCtrl::LoadFromResource打开页面,它从CHtmlView继承而来。当然偶也可以这样打开页面:res://AboutHtml.exe/about.html OK! 偶已经向你展示了CHtmlCtrl如何通过回避CView而顺利代替frame在dialog里显示!。偶也介绍了如何如何在资源文件里定位HTML文件和图象文件。并且告诉你如何打开一个Web页面。 但还有一个极为精彩的处理没有告诉你!:) ,能猜到是什么吗? 哇哈哈哈!!! 看到About对话框里的OK按钮了吧? 它并不是一个按钮!!仅仅是HTML文件里的一副图片! 偶使用了javascript使得它在被单击时有up和down两种状态,但是它是如何和我们的对话框程序通讯的呢??? 你说好玩不?
{
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 }
void CmyHtmlCtrl::OnBeforeNavigate2( LPCTSTR lpszURL,…,BOOL *pbCancel)[这是多么振奋人心的消息?? 想一想,我们几乎可以让对话框做几乎所有能做的事情! 而且我们可以将Web页面处理的更为美观!!:]] 所以,ok并不正真的是另一个文件,而CHtmlCtrl正是利用它来解释OK按钮!! 太完美了! 为了让这个想法更紧凑,偶引入了一个伪协议! 叫做:app:。用它来代替使用ok,在about.htm里正真的链接是app:ok。当CHtmlCtrl发现浏览器试图导航到app:somewhere时,它调用一个新的虚函数,CHtmlCtrl::OnAppCmd,它用somewhere作为参数,并cancels调航(navigation),所以CmyHtmlCtrl并没有重载OnNavigate2,而是重载了OnAppCmd:
{
if(_tcscmp(lpszURL,_T("ok"))==0)
{ //"ok" clicked
*pbCancle=TRUE;
// abort // will close dialog
GetParent()->SendMessage(WM_COMMAND,IDOK);
}
}
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。所以,尽管麻烦,我们还是很乐意尝试。 这个也不难!! 我们同样使用子类化的原理就能实现。