Windows程序设计__孙鑫C++Lesson18《ActiveX 控件》
vc6.0 Win7下环境问题解决:
注意本节课程中的环境很重要,vc 6.0 在Win7系统下易发生错误,错误及解决方法如下,解决方法是:
(1)无法注册控件 尝试以管理员身份进行注册 比如以管理员身份运行cmd.exe 然后键入RegSvr *.ocx 或
者以管理员身份运行一些控件注册的辅助工具,本例中我使用的是RegCtrls.exe工具,下载地址为 http://wj.codefans.net/x/6/201011/RegCtrls.rar
(2)vc 6.0 中生成的控件无法添加到工程,因而无法生成相应的封装类,解决方法时先点击右键把控件加入到对话框上,
然后利用AppWizard 向导添加成员变量,系统就会为你生成相应的CClock类文件。
本节要点:
1.容器程序和服务器程序区别
2.Clock时钟控件的创建
3.Clock控件的使用
//**************************************************************
1.容器和服务器程序
容器应用程序是可以嵌入或者链接对象的应用程序,Word就是容器应用程序。
服务器程序应用程序是创建对象并且当对象被双击时,可以被启动的应用程序。Excel就是服务器应用程序。
在Word中插入一个Excel表格时,Excel就是该Excel表格的服务器程序,Word就是容纳Excel表格的容器应用程序。
当Excel中嵌入Word时,两者的角色变换了。
ActiveX控件可看做一个极小的服务器程序,不能独立运行,必须嵌入容器中和容器应用程序一起运行。
一个控件文件中可以包含多个控件。
2.时钟控件Clock的创建初步
(1)MFC框架自动生成的类: 利用MFC生成的Clock控件生成的类CClockCtrl派生自COleControl;而COleControl从CWnd类派生,支持控件的事件、属性和方法。
利用MFC生成的Clock控件生成的类COlePropertyPage类显示自定义的控件的属性,类似于一个对话框。
(2)接口的概念: 接口是外部程序和控件通信的协议,接口看做函数的集合,外部程序通过接口暴露的方法访问控件的属性和方法。
接口可看做抽象基类,接口定义的这些函数都是纯虚函数,MFC让CClockCtrl继承自DClock,所以接口调用的函数实际上是调用CClockCtrl内部真正实现的函数。
(3)控件的测试程序: VC6.0控件测试程序 ActiveX Control Test Container.也可以利用其它程序例如VB来测试。
注册控件:将控件的注册信息和路径信息写入注册表
注册与反注册控件可以使用Regsvr32命令,在运行里运行 Regsvr32命令。Regsvr32 *.ocx(写完整路径信息) 注册控件;Regsvr32 /u *.ocx(写完整路径信息) 卸载控件.
3.时钟控件创建后续步骤
(1)添加系统预定义的属性
在AppWizard的Automation属性页中选择Add Property添加属性.属性包括以下几种,如下图所示:
(2)添加前景色和背景色属性
这是MFC准备好的属性,设置完毕后控件并没有改变,因为该控件内部并没有编码实现.
首先,获取控件设置的颜色使用函数COleControl::GetBackColor和COleControl::GetForeColor函数,
然后使用COleControl::TranslateColor将上述函数的返回值类型OLE_COLOR 转换为COLORREF.
然后,添加颜色属性页,方法是在CClockCtrl类的实现文件中属性页代码段最如下更改,更改后为:
// Property pages
// TODO: Add more property pages as needed. Remember to increase the count!
BEGIN_PROPPAGEIDS(CClockCtrl, 2)
PROPPAGEID(CClockPropPage::guid)
PROPPAGEID(CLSID_CColorPropPage) //添加颜色属性页 注意更改数目
END_PROPPAGEIDS(CClockCtrl)
(3)增加自定义的属性 时间显示间隔属性
在AppWizard的Automation属性页中选择Add Property添加属性,关于以下类型的说明:
Stock:MFC准备好的标准属性
选择Member variable:添加一个成员变量和通知函数OnIntervalChanged.
选择Get/Set Methods:添加了两个函数SetInterval和GetInterVal方法.
生成的成员变量为:short m_interval;
生成的函数为:afx_msg void OnIntervalChanged();
对应该属性添加属性页方法为在IDD_PROPPAGE_CLOCK对话框上进行设置,为编辑框添加成员变量,变量名字叫m_updateInterval,类型为short;
可选属性名字选择为Interval.这是MFC的特点,编辑框关联成员变量时可以选择一个属性进行关联,可以是标准的属性也可以是自定义属性,关联属性名字为外部名字。
编辑框与属性和变量的关联代码由MFC自动生成代码如下:
void CClockPropPage::DoDataExchange(CDataExchange* pDX)
{
//{{AFX_DATA_MAP(CClockPropPage)
DDP_Text(pDX, IDC_EDIT_INTERVAL, m_updateInterval, _T("Interval") );
DDX_Text(pDX, IDC_EDIT_INTERVAL, m_updateInterval);
//}}AFX_DATA_MAP
DDP_PostProcessing(pDX);
}
(4)添加方法
在AppWizard的Automation属性页中选择Add Method添加属性。
内部名称程序内部实现,外部是在外部方法,可以不一样。
添加Hello方法:
void CClockCtrl::Hello()
{
// TODO: Add your dispatch handler code here
MessageBox("Hello World!");
}
(5)添加事件
在AppWizard的ActiveX Events属性页中选择Add Event添加事件。:
添加Click标准事件(Stokck) ,MFC自动在底层实现了事件的通知
coclass Clock
{
[default] dispinterface _DClock;//
[default, source] dispinterface _DClockEvents;//源接口 控件将用这个接口发送通知事件 不是控件本身实现的接口 控件使用
_DClockEvents接口由容器实现的。控件定义的容器实现,控件实现定义.接口定义无所谓,只要双方遵循统一接口进行通信就好了。
};
添加自定义事件(custom) NewMinute 需要显式调用内部FireNewMinute发出通知。
4.控件开发几个问题
(1控件数据的持久性:用户设置的属性值应当被保存,下次载入时可重现之前的值。
PX_开头的各个函数实现了数据的持久性,当读取或者存取控件的属性时被框架调用。关于这方面的内容详细的可以参考MSDN中ActiveX Controls: Serializing 主题。)
本节数据持久代码(由AppWizard添加 也可手动添加)如下:
void CClockCtrl::DoPropExchange(CPropExchange* pPX)
{
ExchangeVersion(pPX, MAKELONG(_wVerMinor, _wVerMajor));
COleControl::DoPropExchange(pPX);
// TODO: Call PX_ functions for each persistent custom property.
PX_Short(pPX,"Interval",m_interval,1000);//事件间隔值的持久性保存
}
(2)自定义属性和容器中属性面板(例如VB开发时添加控件时的面板)的同步问题
自定义的属性在改变时没有通知容器做相应的调整。在改变属性值时通知容器,
使用函数COleControl::BoundPropertyChanged。调用这个函数时参数DISPID dispid为调度ID,Typedef LONG DISPID;
可参见程序中第一属性的定义处设置的调度ID,本程序中如下:
dispidInterval = 1L,
dispidHello = 2L,
程序中使用代码:BoundPropertyChanged(0x1);//通知容器属性做了更改
(3)设计模式和运行模式才让控件显示出变化
本例中就是时钟控件的时间在走动。
容器知道自己在设计时还是运行时,通过环境属性来获取控件当前处于设计时还是运行时,
这个功能由函数COleControl::AmbientUserMode完成。更多的环境属性可通过COleControl类的Ambient 属性函数来操作。
5.控件的利用
(1)在控件设计完成后,可以再VB或者VC程序中应用。在VC中建立对话框程序,右键添加控件时vc系统不会添加控件封装的类,需要从菜单中增加组建和控件或者添加控件后添加成员变量时系统提示生成相应的类。
(2)Clock生成的类的测试,可通过新建ClockTest对话框工程来测试控件的属性、方法、事件。
(3)响应动态生成的Clock控件的事件
//注意这里事件映射函数原型为:
事件的映射是由ON_EVENT( theClass, id, dispid, pfnHandler, vtsParams )函数完成,注意dispid表示控件触发事件的分派ID,
,如果动态创建的控件的这个参数不正确是不会正确响应事件的。
ClockTest程序中的事件映射部分代码为:
BEGIN_EVENTSINK_MAP(CClockTestDlg, CDialog)
//{{AFX_EVENTSINK_MAP(CClockTestDlg)
ON_EVENT(CClockTestDlg, IDC_CLOCKCTRL, -600 /* Click */, OnClickClockctrl, VTS_NONE)
ON_EVENT(CClockTestDlg, IDC_CLOCKCTRL, 1 /* NewMinute */, OnNewMinuteClockctrl, VTS_NONE)
//}}AFX_EVENTSINK_MAP
ON_EVENT(CClockTestDlg, IDC_CLOCKDYNAMIC, 1 /* NewMinute */, OnNewMinuteDynamicCtr, VTS_NONE)
END_EVENTSINK_MAP()
//这里Click事件是系统标准事件,由系统预定义分派ID而NewMinute 事件则是由生成Clock控件时生成的ID,
可以查看Clock源文件,源文件中形式为[id(1)] void NewMinute(),说明NewMinute 事件的ID就是上面的1。
动态响应控件事件,首先注意先添加资源ID IDC_CLOCKDYNAMIC,然后再EVENTSINK_MAP()中添加事件映射,并编写事件响应函数代码.代码参见本文第6部分代码部分。
6.主要代码及演示效果
这里仅列出了主要代码,一些由框架生成的代码在这里未列出。如果需要查看完整代码,整个Clock控件及其测试程序ClockTest文件下载地址为:
(1)Clock控件编写主要代码及测试效果
//************************************************************************************
//ClockCtl.cpp void CClockCtrl::OnDraw( CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid) { // TODO: Replace the following code with your own drawing code. CBrush brush( TranslateColor( GetBackColor() )); pdc->FillRect(rcInvalid,&brush); pdc->SetTextColor(TranslateColor(GetForeColor())); pdc->SetBkMode(TRANSPARENT); CTime time=CTime::GetCurrentTime(); if(0==time.GetSecond()) FireNewMinute();//发起自定义的事件 显示调用 NewMinute()函数由容器实现 CString strTime=time.Format("%H:%M:%S"); pdc->TextOut(0,0,strTime); } ///////////////////////////////////////////////////////////////////////////// // CClockCtrl::DoPropExchange - Persistence support void CClockCtrl::DoPropExchange(CPropExchange* pPX) { ExchangeVersion(pPX, MAKELONG(_wVerMinor, _wVerMajor)); COleControl::DoPropExchange(pPX); // TODO: Call PX_ functions for each persistent custom property. PX_Short(pPX,"Interval",m_interval,1000);//事件间隔值的持久性保存 } ///////////////////////////////////////////////////////////////////////////// // CClockCtrl message handlers void CClockCtrl::OnTimer(UINT nIDEvent) { // TODO: Add your message handler code here and/or call default //判断当前控件所处环境 运行模式还是设计模式 if(AmbientUserMode()) Invalidate();//方法一 //InvalidateControl();//COleControl::InvalidateControl() 方法二 COleControl::OnTimer(nIDEvent); } int CClockCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (COleControl::OnCreate(lpCreateStruct) == -1) return -1; // TODO: Add your specialized creation code here SetTimer(1,m_interval,0);//换成时间间隔变量 return 0; } //设置时间显示的间隔 事实上就是隔多长时间显示一次时间变化 void CClockCtrl::OnIntervalChanged() { // TODO: Add notification handler code if(m_interval<0 || m_interval >6000) m_interval=1000; else m_interval=m_interval/1000*1000;//调整用户输入 取整数秒数 KillTimer(1); SetTimer(1,m_interval,0); BoundPropertyChanged(0x1);//通知容器属性做了更改 SetModifiedFlag();//设置修改标记 } void CClockCtrl::Hello() { // TODO: Add your dispatch handler code here MessageBox("Hello World!"); }
//************************************************************************************
实验过程中,TSTCON32.exe中测试效果如下图所示:
(2)Clock控件测试程ClockTest的主要代码如下,注意包含Clock控件的封装类文件。
//************************************************************************************
BEGIN_EVENTSINK_MAP(CClockTestDlg, CDialog) //{{AFX_EVENTSINK_MAP(CClockTestDlg) ON_EVENT(CClockTestDlg, IDC_CLOCKCTRL, -600 /* Click */, OnClickClockctrl, VTS_NONE) ON_EVENT(CClockTestDlg, IDC_CLOCKCTRL, 1 /* NewMinute */, OnNewMinuteClockctrl, VTS_NONE) //}}AFX_EVENTSINK_MAP ON_EVENT(CClockTestDlg, IDC_CLOCKDYNAMIC, 1 /* NewMinute */, OnNewMinuteDynamicCtr, VTS_NONE) END_EVENTSINK_MAP() //动态创建控件 void CClockTestDlg::OnBtnCreate() { // TODO: Add your control notification handler code here //控件的create函数可查看类的定义 m_ctlClock.Create("Clock",WS_CHILD|WS_VISIBLE,CRect(0,0,80,50),this,IDC_CLOCKDYNAMIC); m_ctlClock.SetBackColor(RGB(0,0,255)); m_ctlClock.SetForeColor(RGB(255,0,0));//设置背景色和前景色 m_ctlClock.Hello();//调用其方法 } //静态控件的事件响应 void CClockTestDlg::OnClickClockctrl() { // TODO: Add your control notification handler code here MessageBox("Control Clicked!"); } //动态创建控件的事件响应 //注意这里事件映射函数原型为: //ON_EVENT( theClass, id, dispid, pfnHandler, vtsParams ) //dispid 表示控件触发事件的分派ID 如果动态创建的控件的这个参数不正确是不会正确响应事件的 //动态响应控件事件 注意先添加资源ID IDC_CLOCKDYNAMIC //然后再EVENTSINK_MAP()中添加事件映射代码如下一行所示并添加事件响应函数OnNewMinuteDynamicCtr. //ON_EVENT(CClockTestDlg, IDC_CLOCKDYNAMIC, 10 /* NewMinute */, OnNewMinuteDynamicCtr, VTS_NONE) void CClockTestDlg::OnNewMinuteClockctrl() { // TODO: Add your control notification handler code here MessageBox("New minute On static control!"); } void CClockTestDlg::OnNewMinuteDynamicCtr() { MessageBox("New minute on dynamic control!"); }
//************************************************************************************
测试程序运行效果如下图所示:
//************************************************************************************
本节小结:
本节通过讲述时钟控件的编写,首先介绍了容器程序和服务器程序这个概念,这对概念是基础的,理解了这一点便于设计更完善的控件;其次介绍了控件中的一些基本概念如接口,并介绍了如何利用AppWizard为控件添加属性、方法、事件,这三点是一个控件的关键三要素。最后介绍了一些控件运行中出现的问题及其解决方法并测试了时钟控件。
通过本节学习,了解了在MFC下编写一个控件的完整过程,对进一步学习ActiveX控件编程很有帮助。