Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1852075
  • 博文数量: 909
  • 博客积分: 4000
  • 博客等级: 上校
  • 技术积分: 12260
  • 用 户 组: 普通用户
  • 注册时间: 2008-05-06 20:50
文章分类

全部博文(909)

文章存档

2008年(909)

我的朋友

分类:

2008-05-06 21:32:38

一起学习
Convert CHtmlView to CHtmlCtrl
View与Frame的分离
Reference from MSJ
MicroSpace


下载源代码


Wow!! 几篇让人拍案的文章,啃完之后大呼过瘾!想不到微软也有如此精通windows编程的家伙?! 此时此刻,俺想到的是分享给KBASE里的兄弟们啊! 没的说,掌声伺候!!!!

[NOTE]:

罗头说了,最好不要把Frame/Doc/View拆的妻离子散。是啊,本来好好的一家人,谁会那么残忍呢!? 嘿嘿,偶只是给他们弄了个远房的亲戚。:)

Now, Stop 费话ing!! Let''s go on the stuff…
首先,这里有两个难点需要解决! 一是:既然最后的产物是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 death

	pFrame->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那些给我们惹麻烦的代码。
int CHtmlCtrl::OnMouseActive(…)

{

	// bypass CView doc/frame stuff

	return CWnd::OnMouseActive(…);

}

void CHtmlCtrl::OnDestroy()

{

	// bypass CView doc/frame stuff

	CWnd::OnDestroy();

}      
除此之外,CHtmlCtrl也重载了PostNcDestroy
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文件所在的当前目录下寻找。


图一

一般来说,你可以使用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;

}

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

BOOL CHtmlCtrl::CreateFromStatic(UINT nID, CWnd* pParent)

{

	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

}      
然后是使用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可以做任何合法的事情:
           
void CmyHtmlCtrl::OnBeforeNavigate2(

		LPCTSTR lpszURL,…,BOOL *pbCancel)

{

	if(_tcscmp(lpszURL,_T("ok"))==0)

	{

		//"ok" clicked

		*pbCancle=TRUE; // abort

		// will close dialog

		GetParent()->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。所以,尽管麻烦,我们还是很乐意尝试。 这个也不难!! 我们同样使用子类化的原理就能实现。但不是现在,而是将来的某个时候!! :)

下载本文示例代码


Convert CHtmlView to CHtmlCtrlConvert CHtmlView to CHtmlCtrlConvert CHtmlView to CHtmlCtrlConvert CHtmlView to CHtmlCtrlConvert CHtmlView to CHtmlCtrlConvert CHtmlView to CHtmlCtrlConvert CHtmlView to CHtmlCtrlConvert CHtmlView to CHtmlCtrlConvert CHtmlView to CHtmlCtrlConvert CHtmlView to CHtmlCtrlConvert CHtmlView to CHtmlCtrlConvert CHtmlView to CHtmlCtrl
阅读(414) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~