深入分析基于VCL派生的ActiveX控件的实现原理及应用(二)

翻译|其它|编辑:郝浩|2006-04-29 16:29:00.000|阅读 2257 次

概述:

# 界面/图表报表/文档/IDE等千款热门软控件火热销售中 >>


简单的测试


由控件向导生成的TButtonX代码,不需要任何改动,直接编译就会产生一个TButtonX ActiveX控件,我们现在测试一下,点Register Active Server菜单,注册这个控件,然后在开启Visual Basic开发环境,加入刚才注册的控件,发现它确实是按照我们的设计工作的,注意:如果你在开始创建这个控件的时候,选择了生成About对话框的选项,那么还有一个About属性用于显示关于对话框。
那么这个关于对话框又是怎么回事?代码为我们展示这点:

void ShowButtonXAbout(void)
{
   TButtonXAbout* Form;
   Form = new TButtonXAbout(NULL);
   try
   {
     Form->ShowModal();
   }
     catch(...)
    {
      Form->Free();
      return;
    }
  Form->Free();
}



在TButtonImpl的AboutBox函数中调用了上面的函数来显示对话框,不过有一点作者也不太清楚,就是:


Form->Free();


本来按照Borland的说法,在BCB中不推荐使用Free来释放内存,而应该使用delete这个关键字,但这里为什么这样使用?不过作者做了测试,使用delete也是可以的,没有发现什么问题,所以作者猜测,这里可能是Borland没有更新那个代码向导以适应BCB的开发(可能本来是为Delphi设计的,而Borland只是简单的做了一下Delphi到BCB的转换)。
最后需要说明的是AboutBox函数的DISPID必须是-552,这样ActiveX会把这个函数作为About来对待,其实DISPID的设置还是有强制性,很多标准属性必须是特定的DISPID,这些DISPID都是负值,有兴趣的朋友可以看看MSDN或者COM原理的书籍。


重用 继承?


上面的TButtonX控件简单的通过继承VCL的TButton的实现了一个按钮的AcitiveX控件,如果是其他的VCL能不能同样这样简单的继承就可以方便的生成ActiveX控件呢?在问答前,我们先做一个试验,把刚才这个TButtonX放在Visual Basic开发环境中,然后使用Spy++这样工具(这里作者使用作者自己开发的MySpy,可以到http://siney.yeah.net下载)看看他的类名:



发现它的类名并不是ActiveX的控件的TButtonX,而是原来VCL的TButton,这能说明什么?这从一个侧面说明了,在BCB下我们开发ActiveX控件其实就是设计相应的VCL控件,而在最后把他再封装成为ActiveX控件,那么到底什么样的VCL控件都可以封装成为ActiveX控件呢?通常来说只要从TWinControl继承的VCL元件都可以封装成为ActiveX。而这样VCL元件具有如下特征:
? 可以获得焦点
? 可以包含其他控件(仅是具有这样能力,不代表一定具备)
? 拥有窗口句柄
还记得这段代码吗:

HRESULT OnDraw(ATL_DRAWINFO& di)
{
   try
    {
       if (m_VclCtl)
       m_VclCtl->PaintTo(di.hdcDraw, di.prcBounds->left, di.prcBounds->top);
    }
   catch (Exception& e)
   {
      return (static_cast<T*>(this))->Error(e.Message.c_str());
    }
   return S_OK;
}


前面说这是BCB实现ActiveX机制的关键类TVclControlImpl所具有的代码,用于把控件画在ActiveX宿主窗体上,而这个方法就是来源于TWinControl,所以ActiveX控件的必须继承自这个类(当然如果是自己实现了,就另当别论),这样,很容易想到的是像TLabel这样的VCL控件是无法实现为ActiveX控件的。
那么是不是从TWinControl继承的VCL元件都可以被封装为ActiveX呢?这也不一定,如果你已经把相应的的unit添加进入工程或者已经把它安装了,则这个VCL元件可能不会出现在那个DropDown 列表里,还有就是这个类没有用RegisterNonActiveX函数注册,这个函数专门用来设置那些类不能被封装为ActiveX,这个函数很复杂:


extern PACKAGE void __fastcall RegisterNonActiveX(System::TMetaClass*,
const * ComponentClasses,
const int ComponentClasses_Size,
TActiveXRegType AxRegType);


具体的使用方法可以参考帮助。在CSDN上和Borland新闻组作者也看到过网友询问“为什么我的控件不能出现在AcitveX的生成向导里”,希望下次看过这篇文章的朋友下次可以解决这个问题。


再完善一些


再回到刚才Visual Basic的开发环境,我们来看看到底那些属性和方法被表露了:
而同样的TButton在BCB中却具有非常多的属性,为什么呢?开始作者认为所有的VCL元件都是表露这些基本的属性和方法,但后来作者又做了一个试验,就是简单的封装了TEdit后发现他表露其他的更多属性和方法,参考了一下帮助文档,发现原来有如下规则:

  • 数据感知属性不表露。
  • 任何与自动化不兼容的类型定义不表露。
  • 可以表露在VCL中未发布的属性,但这样做不保证持久性(persist)。


如果VCL的属性或方法不符合上述规则,我们就需要自己实现相应ActiveX代码来表露他们;相反如果符合上述规定,而你又不想表露给最终用户,你可以在Type Library Editor中删除它们,刷新代码后再删除单元文件中相应的生成代码。
这里需要强调的是:在BCB中设计ActiveX控件在很大程度上是先设计对应的VCL后再封装为ActiveX,而不是像VC那样直接开发ActiveX(其实BCB也可以像VC那样开发ActiveX,毕竟都是使用ATL嘛),这样设计不管是从难易程度还是调试都是非常轻松的。
知道了原理,我们现在要做的是在这个半成品的TButtonX中加入新的事件和属性页,使它看起来更像一个专业的ActiveX控件,对于属性和方法,因为编写这些代码非常简单,为了节省篇幅,这里就不实际添加属性和方法了。
从TButton封装得到的AcitiveX缺少了一个重要的事件,OnMouseMove,下面我们就写代码来表露这个事件,根据我们上面讲述的原理,完成这部分很容易,首先就是在Type Library Editor里的事件支持接口添加相应方法,如图:



刷新后IDE自动产生相应代码,在Impl单元文件中,加入VCL的OnMouseMove消息处理的转移代码,如下黑体部分:

class ATL_NO_VTABLE TButtonXImpl:
VCLCONTROL_IMPL(TButtonXImpl, ButtonX, TButton, IButtonX, DIID_IButtonXEvents)
{
   void __fastcall ClickEvent(TObject *Sender);
   void __fastcall KeyPressEvent(TObject *Sender, char &Key);
   void __fastcall MouseMoveEvent(TObject *Sender, TShiftState Shift, int X,
int Y);
   public:

   void InitializeControl()
    {
      m_VclCtl->OnClick = ClickEvent;
      m_VclCtl->OnKeyPress = KeyPressEvent;
     m_VclCtl->OnMouseMove = MouseMoveEvent;
   }



这里的m_VclCtl其实就是TButton,他是通过模板参数替换的,所以通过上面的代码TButton的OnMouseMove消息的处理过程转向了MouseMoveEvent,现在我们最后的工作就是编写MouseMoveEvent这个函数处理OnMouseMove消息:

void __fastcall TButtonXImpl::MouseMoveEvent(TObject *Sender,
TShiftState Shift, int X, int Y)
{
   int ss=0;
   if(Shift.Contains(ssLeft))
      ss=1;
   else if(Shift.Contains(ssRight))
      ss=2;
   Fire_OnMouseMove(ss,X,Y);
}



忽略Sender参数后,再次转发消息流,Fire_OnMouseMove这个函数是由IDE自动产生的,我们直接调用就可以了,它的代码如下:

template <class T> void
TEvents_ButtonX<T>::Fire_OnMouseMove(int Button, int X, int Y)
{
    T * pT = (T*)this;
    pT->Lock();
    IUnknown ** pp = m_vec.begin();
    while (pp < m_vec.end())
      {
        if (*pp != NULL)
         {
           m_EventIntfObj.Attach(*pp);
           m_EventIntfObj.OnMouseMove(Button, X, Y);
           m_EventIntfObj.Attach(0);
        }
     pp++;
    }
  pT->Unlock();
}



可以看到这部分代码与缺省的Fire_OnClick是一致的,这样添加OnMouseMove事件支持的代码就完成了。
接下来是添加一个属性页,由于VCL的封装,使得开发设计属性页变得非常简单,首先生成一个新的Property Page,这样BCB会为我们产生一个Form,这个Form与普通的Win32开发中Form的最大区别在于它继承自TPropertyPage,所以它有一些独有的方法是我们在设计需要注意的,为了简单起见,属性页里只简单地更改、反馈Caption属性,在实际开发中复杂的属性页是类似的。
属性页与ActiveX交互就是通过2个函数来进行的,而这两个函数都是来自于TPropertyPage类,在缺省生成的Property Page Form里已经加入了这个两个函数的缺省代码,我们要做就是完成这2个函数:
UpdatePropertyPage(void),在打开属性页时,系统会调用这个函数,你可以在这个函数里添加代码用以反映ActiveX的属性在属性页里的显示。
UpdateObject(void),在应用属性时,系统会调用这个函数,你可以把属性页中改变的数据应用到实际的ActiveX控件中。
按照BCB的帮助这两个函数实现起来非常简单,比如UpdateObject(void),就只需要如下代码:


void __fastcall TPropertyPage1::UpdateObject(void)
{
    OleObject.OlePropertySet<WideString>("EditMask", WideString(InputMast->Text).Copy());
}


就可以把ActiveX控件的属性更改为属性页中设置的数据,但在作者的计算机上(BCB6+sp4),上面的代码怎么都无法通过编译,无奈之下,作者只好使用原始的方法,代码如下:

void __fastcall TpageNormal::UpdatePropertyPage(void)
{
   // Update your controls from OleObjects
   IDispatch* ctrl=OleObject;
   CComPtr<IButtonX> btnctrl;
   ctrl->QueryInterface<IButtonX>(&btnctrl);
   edtcaption->Text=String(btnctrl->get_Caption());
}
//---------------------------------------------------------------------------
void __fastcall TpageNormal::UpdateObject(void)
{
   // Update OleObjects from your controls
   IDispatch* ctrl=OleObject;
   CComPtr<IButtonX> btnctrl;
   ctrl->QueryInterface<IButtonX>(&btnctrl);
   btnctrl->set_Caption(WideString(edtcaption->Text));
}


对于没有ATL、COM知识的读者上面的代码可能比较难懂,建议找些相关书籍熟悉一下。
通过上面的代码,ActiveX控件和属性页之间就可以完美的交互了,最后在ButtonImpl.h文件中加入如下宏映:


BEGIN_PROPERTY_MAP(TButtonXImpl)
PROP_PAGE(CLSID_pageNormal)
END_PROPERTY_MAP()


关于这些宏的说明和原理因篇幅关系这里就不讨论了,BCB的帮助和源码注释里都写的很清楚,感兴趣的朋友可以自己研究一下。至于上面的CLSID_pageNormal是生成属性页时,IDE自动为改属性页生成的ClassID。
这样一个比较完善的ActiveX控件就写完了,是不是非常简单,详细代码可以到CSDN网站下载。


写在最后


由于篇幅关系,本来很多内容可以展开详细讨论,但作者都省略了,本文就当抛砖引玉,感兴趣的读者可以再深入研究。作者想再次强调的是,不管开发ActiveX控件还是Active Form,最好的方法都是封装(或者转换)为ActiveX,而不是一切从头来,比如你有一个工程,想以ActiveForm的形式用在Web上,那么最好的方法是拿出单独的Form然后转换为ActiveForm,这不需要多复杂的代码,这也是Borland新闻组上专家给的建议。
由于编写ActiveX控件的调试复杂性,所以保证代码质量非常重要,除此之外就是善于利用一些工具来帮助调试,比如Visual Basic、ActiveX Control Test Container等工具,如果有机会作者会写一些实际开发中可能遇到的调试问题和经验。
参考文献:

  • Borland C++ Builder5 Help Document
  • Borland VCL Source and Comment

标签:

本站文章除注明转载外,均为本站原创或翻译。欢迎任何形式的转载,但请务必注明出处、不得修改原文相关链接,如果存在内容上的异议请邮件反馈至chenjj@evget.com


为你推荐

  • 推荐视频
  • 推荐活动
  • 推荐产品
  • 推荐文章
  • 慧都慧问
扫码咨询


添加微信 立即咨询

电话咨询

客服热线
023-68661681

TOP