前言
伴随着VC7的诞生,载入IE的对话框成为了新的热点,CDHtmlDialog给不熟悉COM编程的程序员注入了丝丝暖风。处理页面元素的响应事件,与其交换数据都被封装到几组宏内。类似DHTML_EVENT_ONCLICK,DDX_DHtml_Radio的宏被我们添加到实现类里,一切只需等待MFC的魔法开始。
但在视图类方面,吝啬的MFC并没有对其做进一步的封装,能用到的只是IE控件本身几个可怜的事件函数。当然你完全可以这么认为,视图只是观赏IE的成果,这样就不必关心HTML元素的事件了。可贪心和滥用使我既想看到HTML的秀色,又想疯狂的捕获HTML元素的事件。当然,去看MSDN的有关COM的神秘的捕获事件文章(How To Sink HTML Document Events for WebBrowser Host:http://support.microsoft.com/default.aspx?scid=kb;EN-US;q246247),或许你能轻巧的处理事件。对于不熟悉COM的我,能找到CDHtmlDialog和CHtmlView的共同之处,然后重用里面的代码是最最理想的。当然因此产生的两个类CDHtmlSpecEventSink和CDHtmlViewSpec是这一思想的精华。
刨析CDHtmlDialog类
有关COM的知识点可能被我弱化,如果你有兴趣,请翻阅相关的书籍,据说《COM本质论》写的很不错,值得翻阅。如果你不想知道CDHtmlDialog类的有关内容,请直接跳到下一节。
在MFC安装目录下找到下列文件:
afxDhtml.h,dlgdhtml.cpp :CDHtmlDialog 以及其相关类的源代码文件;
afxHtml.h ,viewhtml.cpp:CHtmlView 以及相关类的源代码文件;
如果你研究的更有心得,应该可以完成更为完善的类。这里我们看看CDHtmlDialog类凭什么就可以成为IE控件的宿主,并且与之关联?很快我们就注意到在对话框的OnInitDialog里发现了IE控件的创建。为了便于说明问题,简化代码如下:
BOOL CDHtmlDialog::OnInitDialog()
{
//1.激活对OLE控件容器的支持
AfxEnableControlContainer();
//2. 建立IE控件,当然如果已经有了就Attach之类的东东都被我去掉了
// create the control window ,替换调现有的窗口对象
m_wndBrowser.CreateControl(CLSID_WebBrowser, NULL,
WS_VISIBLE | WS_CHILD, rectClient, this, AFX_IDC_BROWSER);
}
经过这样的处理,我们明白对话框建立后已经附加了一个IE的控件。但我们更关心的是事件怎么被链接和宏化。继续跟踪代码,我们又发现CreateControlSite函数比较可疑,参看MSDN宝典后,我们清楚的知道这个函数就是在控件容器建立控件后需要回调的一个定制管理页面的函数。
BOOL CDHtmlDialog::CreateControlSite(COleControlContainer* pContainer,
COleControlSite** ppSite, UINT /* nID */, REFCLSID /* clsid */)
{
//1.增加一个定制的管理页面
CBrowserControlSite *pBrowserSite =
new CBrowserControlSite(pContainer, this);
if (!pBrowserSite)
return FALSE;
*ppSite = pBrowserSite;
return TRUE;
}
问题又转到CBrowserControlSite 类里,分析其接口,并不多,大都在建立与控件的事件链接。好了,到此为止,我们终于知道诸如ShowUI事件的来历。当然利用宏DECLARE_EVENTSINK_MAP来声明的事件应该与之息息相关。CDHtmlDialog的另一个继承类 CDHtmlEventSink,单单从命名上来看,就觉得其就是我们的目标了。我们去寻找其被调用的地方吧,搜索发现在使用ON_EVENT宏定义的事件里分别是各个EventHook的入口和出口。
代码如下:
void CDHtmlDialog::OnBeforeNavigate(LPDISPATCH pDisp, LPCTSTR szUrl)
{
//1。取消DHTML事件链接
DisconnectDHtmlEvents();
}
void CDHtmlDialog::OnNavigateComplete(LPDISPATCH pDisp, LPCTSTR szUrl)
{
//1。链接DHTML事件
ConnectDHtmlEvents(pdispDoc);}
void CDHtmlDialog::OnDocumentComplete(LPDISPATCH pDisp, LPCTSTR szUrl)
{
//链接DHTML元素事件
ConnectDHtmlElementEvents((((DWORD_PTR)static_cast< CDHtmlSinkHandler* >(this)) - (DWORD_PTR)this));
}
功夫不负有心人,经过一番呆头呆脑的搜索,我们发现了这个事件的来龙去脉。
如果你继续跟踪,会发现还有几个支撑类实现,但我不想再跟了。就在此利用 CDHtmlEventSink类来实现我们的设想吧。
构造类 CDHtmlViewSpec
直接象CDHtmlDialog那样从 CDHtmlEventSink类继承,编译时提示不能使用动态创建。那就让我们继承并实现一个CDHtmlSpecEventSink类吧。
class CDHtmlSpecEventSink: public CDHtmlEventSink
{
public:
//实现这几个抽象方法
virtual const DHtmlEventMapEntry* GetDHtmlEventMap();
virtual BOOL DHtmlEventHook(HRESULT *phr, DISPID dispIdMember, DISPPARAMS *pDispParams,
VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr);
virtual HRESULT GetDHtmlDocument(IHTMLDocument2 **pphtmlDoc);
//建立
CDHtmlSpecEventSink();
//给个接口,这个我没有找到在CDHtmlDialog中怎么实现的。
void InitEventSink(IHTMLDocument2* pDoc);
protected:
IHTMLDocument2* m_pDocHtml;
CHtmlView* m_pView;
};
类CDHtmlViewSpec的声明就像下面:
class CDHtmlViewSpec : public CHtmlView ,public CDHtmlSpecEventSink
{
//加入对DHTML事件的支持吧。相关辅助函数也要拷贝哦
};
需要什么就从CDHtmlDialog中拷贝吧,(我最恨你们拷贝程序了,一点技术含量都没有!)
完成了,测试,通过。
使用类CDHtmlViewSpec
使用本类很简单了,直接从CHtmlView继承一个类,然后把文件中的CHtmlView类都改为CDHtmlViewSpec。
添加事件和DDx参考CDHtmlDialog即可。
如果你懒的看帮助,就参考下面的代码吧:
增加事件支持
在你的HTMLView头文件里增加DECLARE_DHTML_EVENT_MAP(),
在cpp文件里增加下列格式的代码:
BEGIN_DHTML_EVENT_MAP(ChtmlExView)
DHTML_EVENT_ONCLICK(_T("btnEnd"), OnButtonEnd)
END_DHTML_EVENT_MAP()
然后添加OnButtonEnd函数的声明和执行体。
增加数据交换的支持
在cpp文件的DoDataExchange里添加下列代码:
void ChtmlExView::DoDataExchange(CDataExchange* pDX)
{
CDHtmlViewSpec::DoDataExchange(pDX);
//增加对Radio控件的数据交互
DDX_DHtml_Radio(pDX,_T("btnControl"),m_iControl);
}
关于作者和代码你可以在这里下载代码和demo程序: http://www.codeproject.com/useritems/luo31.asp
你可以使用和修改本类,但你不应该就此发表类似的文章。引用请标明出处,谢谢!
你可以在这里下载代码和demo程序: http://www.codeproject.com/useritems/luo31.asp
你可以使用和修改本类,但你不应该就此发表类似的文章。引用请标明出处,谢谢!