在上一步中我们实际是通过IDispatch接口的Invoke方法来访问控件的属性和方法的,虽然有COleDispatchDriver和其它一些辅助函数,但还是有些繁杂,能不能直接得到控件对象的指针,这样就可以直接访问控件了,甚至于控件的内部变量了?答案是肯定了。
我们的控件派生自COleControl,COleControl派生自CWnd,CWnd又派生自CCmdTarget,我们的IDispatch接口就是在CCmdTarget中实现的(其实应该是实现在COleDispatchImpl类中),实现的方法比较特殊,这里不细细分析了,有兴趣的朋友可以自己看看MFC源码或者网上搜搜资料,应该能知道的。
我们能从CCmdTarget中获得IDispatch接口指针(GetIDispatch函数),同样的,我们也能从IDispatch接口指针中反向导出CCmdTarget对象指针,用到的是CCmdTarget的静态成员函数
static CCmdTarget* FromIDispatch( LPDISPATCH lpDispatch );
前面我们已经知道可以通过属性页的GetObjectArray来获得控件的IDispatch接口指针,而有了上面的FromIDispatch这个函数,我们又可以从IDispatch接口指针中来获得控件的指针了,那么问题就解决了。
另外,这里我们还用到COlePropertyPage的一个虚拟函数 OnObjectsChanged,在控件变化(包括控件加载上和卸载掉时)时,都会调用到这个函数。(其实,并不是一定要用这个函数的,只是想介绍一下,就用上了)
还是用上一步中的例子topp
1.添加控件类指针为属性页类的成员变量(#include "ToppCtl.h",应该不用说的吧)
CToppCtrl* m_pctrl;
2.添加获得控件类指针的函数CToppCtrl* GetCtrlPtr();
CToppCtrl* CToppPropPage::GetCtrlPtr()
{
ULONG ulObjects;
LPDISPATCH* lpObjectArray = GetObjectArray( &ulObjects );
ASSERT( lpObjectArray != NULL );
LPDISPATCH lpdisp = NULL;
if(ulObjects > 0){//在这个函数中,并不一定能保证就有控件.
lpdisp = lpObjectArray[0];//这里省事了,就假设只有一个控件了
}
CToppCtrl* pctrl = NULL;
if(lpdisp){
pctrl = (CToppCtrl*)CCmdTarget::FromIDispatch(lpdisp);
}
return pctrl;
}
3.重载OnObjectsChanged,设置m_pctrl(在其它地方,如OnInitDialog中设置也无所谓,甚至即用即获得也无所谓)
void CToppPropPage::OnObjectsChanged()
{
ULONG ulObjects;
LPDISPATCH* lpObjectArray = GetObjectArray( &ulObjects );
ASSERT( lpObjectArray != NULL );
LPDISPATCH lpctl = NULL;
if(ulObjects > 0){//在这个函数中,并不一定能保证就有控件.
lpctl = lpObjectArray[0];//这里省事了,就假设只有一个控件了
}
if(lpctl){
m_pctrl = (CToppCtrl*)CCmdTarget::FromIDispatch(lpctl);
}
else{
m_pctrl = NULL;
}
}
3.修改上一步中的几个成员函数
BOOL CToppPropPage::AddItem(LPCTSTR lpszItem)
{
if(m_pctrl) m_pctrl->m_saItems.Add(lpszItem);
return m_pctrl != NULL;
}
CString CToppPropPage::GetItem(long lItem)
{
if(m_pctrl) {
if(lItem >= 0 && lItem < m_pctrl->m_saItems.GetSize()){
return m_pctrl->m_saItems[lItem];
}
else{
return _T("");
}
}
else{
return _T("");
}
}
//在获得m_color时,是直接将m_color从protected拉到了public下的,可恶的MFC,再添加新的属性或方法时可能会重新再添加一个,会给你带来一个小小的麻烦。
COLORREF CToppPropPage::GetColor()
{
if(m_pctrl) return m_pctrl->TranslateColor(m_pctrl->m_color);
return RGB(0, 0, 0);
}
void CToppPropPage::SetColor(COLORREF cr)
{
if(m_pctrl) m_pctrl->m_color = cr;
}
long CToppPropPage::GetCount()
{
if(m_pctrl) return m_pctrl->m_saItems.GetSize();
return 0;
}
4.编译,新建一VB工程,在VB下测试,可以发现没有问题
5.在ActiveX Control Container下测试,咦,出问题了,没有反应,为什么?
根据最后给出的资料上中的意思,大致是因为容器聚合了控件的原因(呵呵,懒得去想了,有兴趣的自己思考吧)
6.为控件添加一只读属性long CtrlPtr
long CToppCtrl::GetCtrlPtr()
{
// TODO: Add your property handler here
return reinterpret_cast<long>(this);
return 0;
}
7.编译,在ClassWizard的Automation下,点击Add Class,然后选择 From a type library ,找到编译时为控件生成的.tlb文件,这里应该是在/Debug目录下,选择这个topp.tlb,然后只选择其中的_DTopp,点确定,这样将生成一个_DTopp类,和我们上一步中的介绍比较一下,可以发现,其实就是派生自一个COleDispatchDriver类,也就是说,只是一个帮助我们调用控件属性和方法的类(也就是说,我们上一步中其实完全不用自己来编代码的,ClassWizard可以为我们生成一个方便的可操作的类:))。
8.修改属性页的GetCtrlPtr函数如下:
CToppCtrl* CToppPropPage::GetCtrlPtr()
{
ULONG ulObjects;
LPDISPATCH* lpObjectArray = GetObjectArray( &ulObjects );
ASSERT( lpObjectArray != NULL );
LPDISPATCH lpdisp = NULL;
if(ulObjects > 0){//在这个函数中,并不一定能保证就有控件.
lpdisp = lpObjectArray[0];//这里省事了,就假设只有一个控件了
}
CToppCtrl* pctrl = NULL;
if(lpdisp){
pctrl = (CToppCtrl*)CCmdTarget::FromIDispatch(lpdisp);
if(!pctrl){
long ControlPointer;
_DTopp control(lpdisp);
// GetObjectArray() docs state must not release pointer.
control.m_bAutoRelease = FALSE;
ControlPointer = control.GetCtrlPtr();
pctrl = reinterpret_cast<CToppCtrl*>(ControlPointer);
//这里其实可以简单用如下的方法调用,之所以用上面的方法,只是用来介绍而已
/*
long p;
GetPropText("CtrlPtr", &p);
pctrl = reinterpret_cast<CToppCtrl*>(p);
*/
}
}
return pctrl;
}
9.编译,在ActiveX Control Container中测试,OK!
注释:这里用ClassWizard导入了一个辅助类是用来帮助从IDispatch中获得控件的CtrlPtr属性值的,其实并不一定需要这个辅助类的,见 CToppCtrl* CToppPropPage::GetCtrlPtr()函数中 被注释掉的代码,之所以引入这个辅助类,也是介绍起见。
参考资料:
http://support.microsoft.com/kb/205670/
我们的控件派生自COleControl,COleControl派生自CWnd,CWnd又派生自CCmdTarget,我们的IDispatch接口就是在CCmdTarget中实现的(其实应该是实现在COleDispatchImpl类中),实现的方法比较特殊,这里不细细分析了,有兴趣的朋友可以自己看看MFC源码或者网上搜搜资料,应该能知道的。
我们能从CCmdTarget中获得IDispatch接口指针(GetIDispatch函数),同样的,我们也能从IDispatch接口指针中反向导出CCmdTarget对象指针,用到的是CCmdTarget的静态成员函数
static CCmdTarget* FromIDispatch( LPDISPATCH lpDispatch );
前面我们已经知道可以通过属性页的GetObjectArray来获得控件的IDispatch接口指针,而有了上面的FromIDispatch这个函数,我们又可以从IDispatch接口指针中来获得控件的指针了,那么问题就解决了。
另外,这里我们还用到COlePropertyPage的一个虚拟函数 OnObjectsChanged,在控件变化(包括控件加载上和卸载掉时)时,都会调用到这个函数。(其实,并不是一定要用这个函数的,只是想介绍一下,就用上了)
还是用上一步中的例子topp
1.添加控件类指针为属性页类的成员变量(#include "ToppCtl.h",应该不用说的吧)
CToppCtrl* m_pctrl;
2.添加获得控件类指针的函数CToppCtrl* GetCtrlPtr();
CToppCtrl* CToppPropPage::GetCtrlPtr()
{
ULONG ulObjects;
LPDISPATCH* lpObjectArray = GetObjectArray( &ulObjects );
ASSERT( lpObjectArray != NULL );
LPDISPATCH lpdisp = NULL;
if(ulObjects > 0){//在这个函数中,并不一定能保证就有控件.
lpdisp = lpObjectArray[0];//这里省事了,就假设只有一个控件了
}
CToppCtrl* pctrl = NULL;
if(lpdisp){
pctrl = (CToppCtrl*)CCmdTarget::FromIDispatch(lpdisp);
}
return pctrl;
}
3.重载OnObjectsChanged,设置m_pctrl(在其它地方,如OnInitDialog中设置也无所谓,甚至即用即获得也无所谓)
void CToppPropPage::OnObjectsChanged()
{
ULONG ulObjects;
LPDISPATCH* lpObjectArray = GetObjectArray( &ulObjects );
ASSERT( lpObjectArray != NULL );
LPDISPATCH lpctl = NULL;
if(ulObjects > 0){//在这个函数中,并不一定能保证就有控件.
lpctl = lpObjectArray[0];//这里省事了,就假设只有一个控件了
}
if(lpctl){
m_pctrl = (CToppCtrl*)CCmdTarget::FromIDispatch(lpctl);
}
else{
m_pctrl = NULL;
}
}
3.修改上一步中的几个成员函数
BOOL CToppPropPage::AddItem(LPCTSTR lpszItem)
{
if(m_pctrl) m_pctrl->m_saItems.Add(lpszItem);
return m_pctrl != NULL;
}
CString CToppPropPage::GetItem(long lItem)
{
if(m_pctrl) {
if(lItem >= 0 && lItem < m_pctrl->m_saItems.GetSize()){
return m_pctrl->m_saItems[lItem];
}
else{
return _T("");
}
}
else{
return _T("");
}
}
//在获得m_color时,是直接将m_color从protected拉到了public下的,可恶的MFC,再添加新的属性或方法时可能会重新再添加一个,会给你带来一个小小的麻烦。
COLORREF CToppPropPage::GetColor()
{
if(m_pctrl) return m_pctrl->TranslateColor(m_pctrl->m_color);
return RGB(0, 0, 0);
}
void CToppPropPage::SetColor(COLORREF cr)
{
if(m_pctrl) m_pctrl->m_color = cr;
}
long CToppPropPage::GetCount()
{
if(m_pctrl) return m_pctrl->m_saItems.GetSize();
return 0;
}
4.编译,新建一VB工程,在VB下测试,可以发现没有问题
5.在ActiveX Control Container下测试,咦,出问题了,没有反应,为什么?
根据最后给出的资料上中的意思,大致是因为容器聚合了控件的原因(呵呵,懒得去想了,有兴趣的自己思考吧)
6.为控件添加一只读属性long CtrlPtr
long CToppCtrl::GetCtrlPtr()
{
// TODO: Add your property handler here
return reinterpret_cast<long>(this);
return 0;
}
7.编译,在ClassWizard的Automation下,点击Add Class,然后选择 From a type library ,找到编译时为控件生成的.tlb文件,这里应该是在/Debug目录下,选择这个topp.tlb,然后只选择其中的_DTopp,点确定,这样将生成一个_DTopp类,和我们上一步中的介绍比较一下,可以发现,其实就是派生自一个COleDispatchDriver类,也就是说,只是一个帮助我们调用控件属性和方法的类(也就是说,我们上一步中其实完全不用自己来编代码的,ClassWizard可以为我们生成一个方便的可操作的类:))。
8.修改属性页的GetCtrlPtr函数如下:
CToppCtrl* CToppPropPage::GetCtrlPtr()
{
ULONG ulObjects;
LPDISPATCH* lpObjectArray = GetObjectArray( &ulObjects );
ASSERT( lpObjectArray != NULL );
LPDISPATCH lpdisp = NULL;
if(ulObjects > 0){//在这个函数中,并不一定能保证就有控件.
lpdisp = lpObjectArray[0];//这里省事了,就假设只有一个控件了
}
CToppCtrl* pctrl = NULL;
if(lpdisp){
pctrl = (CToppCtrl*)CCmdTarget::FromIDispatch(lpdisp);
if(!pctrl){
long ControlPointer;
_DTopp control(lpdisp);
// GetObjectArray() docs state must not release pointer.
control.m_bAutoRelease = FALSE;
ControlPointer = control.GetCtrlPtr();
pctrl = reinterpret_cast<CToppCtrl*>(ControlPointer);
//这里其实可以简单用如下的方法调用,之所以用上面的方法,只是用来介绍而已
/*
long p;
GetPropText("CtrlPtr", &p);
pctrl = reinterpret_cast<CToppCtrl*>(p);
*/
}
}
return pctrl;
}
9.编译,在ActiveX Control Container中测试,OK!
注释:这里用ClassWizard导入了一个辅助类是用来帮助从IDispatch中获得控件的CtrlPtr属性值的,其实并不一定需要这个辅助类的,见 CToppCtrl* CToppPropPage::GetCtrlPtr()函数中 被注释掉的代码,之所以引入这个辅助类,也是介绍起见。
参考资料:
http://support.microsoft.com/kb/205670/