注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

jasonyang9的博客

随便写写

 
 
 

日志

 
 

(怀旧系列)VC程序设计(孙鑫老师)听课笔记:18 ActiveX控件  

2013-03-06 10:36:19|  分类: programming |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
(怀旧系列)VC程序设计(孙鑫老师)听课笔记:18 ActiveX控件 - jasonyang9 - jasonyang9的博客
 ===========
ActiveX控件
===========

容器和服务器程序
* 容器应用程序是可以嵌入或链接对象的应用程序(例如,Word可以充当容器应用程序)。
* 服务器应用程序是创建对象并当对象被双击时可被启动的应用程序(例如,Excel可以充当服务器应用程序)。
* Word和Excel既是服务器应用程序,同时也是容器应用程序。
* 可将ActiveX控件看作一种极小的服务器应用程序,只能在容器应用程序中运行。

利用VB测试控件(略)。

典型的ActiveX具有方法、属性和事件,共3种特性。可以将常用的功能封装到控件中,提供给多种开发环境使用。

新建工程MFC ActiveX ControlWizard,Clock。
How many controls would you like your project to have? 1(一个工程中提供多少个控件?)
Would you like the controls in this project to have a runtime license? No runtime license(是否需要运行时的许可?)
Would you like source file comments to be generated? Yes, please(生成代码中的注释?)
Would you like help files to be generated? No help files(生成帮助文件?)
MFC为工程增加了以下内容:
一个从COleControlModule派生的CClockApp类。COleControlModule从CWinApp派生,是一个应用程序类,代表应用程序(控件)本身。
一个从COleControl派生的CClockCtrl类。COleControl从CWnd派生,是一个窗口类,相当于SDI程序中的View类,有OnDraw函数。
一个从COlePropertyPage派生的CClockPropPage类。COlePropertyPage从CDialog派生,是一个对话框类,用来显示控件的属性对话框,有一个对话框资源(IDD_PROPPAGE_CLOCK)与其关联。
一个_DClock接口。(接口:外部程序和控件通信的协议,是函数的集合,外部程序通过这些方法来访问控件的属性。接口是抽象基类,其中的所有函数都是纯虚函数,这些函数的具体实现由CClockCtrl类完成。调用接口中的函数时,真正起作用的是CClockCtrl中的函数)
一个_DClockEvents接口。(接口是由COM技术实现的,具体细节被封装)

编译工程后,在Debug目录下生成Clock.ocx文件,这就是ActiveX控件文件。单击VC中的运行,弹出对话框,要求指定一个可执行文件,这是由于ActiveX控件不能独立运行,必须嵌入一个容器中才可运行。可以选择ActiveX Control Test Container这个测试容器。
在ActiveX Control Test Container中选择Edit|Insert New Control...,在列表中选择Clock Control控件,此控件即被加载。该控件在其窗口中画了一个椭圆。
至于为何ActiveX Control Test Container或VB能够找到刚刚编译生成的Clock控件,是因为在VC编译成功后又执行了Registering Active Control...、RegSvr32: DllRegisterServer in .\Debug\Clock.ocx successed.注册控件。注册后控件的名称和路径都会被保存到注册表。
要删除注册可以执行命令:regsvr32 /u 控件路径名称。弹出DllUnregisterServer ... successed。DllUnregisterServer是ActiveX控件提供的函数,在执行regsvr32 /u时,它加载ActiveX控件,调用其DllUnregisterServer函数,删除控件的注册信息(这些相关信息只有该控件自己知道)。
要手动注册ActiveX控件,可执行命令:regsvr32 控件路径名称。弹出DllRegisterServer ... successed。DllRegisterServer也是ActiveX控件提供的函数,在执行regsvr32时,它加载ActiveX控件,调用其DllRegisterServer函数,注册控件的信息。
此外,VC的IDE中提供了一个菜单命令,在Tools|Register Control,效果和regsvr32命令是一样的。

编写代码,让Clock控件显示当前系统时间。修改CClockCtrl::OnDraw函数。

void CClockCtrl::OnDraw(CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid)
{
CTime time = CTime::GetCurrentTime(); // 定义CTime对象,调用GetCurrentTime静态函数
CString str = time.Format("%H:%M:%s"); // 用CTime的Format函数格式化输出时间
pdc->TextOut(0, 0, str); // 显示
}

编译后在ActiveX Control Test Container中加载Clock.ocx就能看到系统时间显示在控件中。

但要让时间走动,需要增加定时器。对CClockCtrl增加消息处理函数WM_CREATE。

int CClockCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct) // 在控件窗口创建完成后设置定时器
{
if (COleControl::OnCreate(lpCreateStruct) == -1)
return -1;

SetTime(1, 1000, NULL); // 每秒一个消息
return 0;
}

增加WM_TIMER消息处理。

void CClockCtrl::OnTimer(UINT nIDEvent)
{
Invalidate(); // 让窗口无效,重绘(或调用InvalidateControl效果相同)
COleControl::OnTimer(nIDEvent);
}

编译后测试,时钟走动正常。

在VB的环境中,可以看到其控件属性中缺少很多其他控件都有的属性,比如:BackColor(背景色)和ForeColor(前景色)。
选择VC中的View|ClassWizard,在弹出的MFC ClassWizard对话框中选择Automation,单击Add Property...按钮,弹出Add Property对话框,在External name:中选择BackColor,Implementation选择Stock,再增加ForeColor,Implementation选择Stock。

关于ActiveX控件的四种属性
* Stock:为每个控件提供的标准属性,如字体或颜色。
* Ambient:围绕控件的环境属性——已被置入容器的属性。这些属性不能被更改,但控件可以使用它们调整自己的属性。
* Extended:这些是由容器处理的属性,一般包括大小和在屏幕上的位置。
* Custom:由控件卡发者添加的属性。

在增加了BackColor和ForeColor这两个属性后,VC将它们添加到_DClock接口下。同时,编译后到VB中就能看到这两个属性,但修改后看不到效果,还需要编写代码。

void CClockCtrl::OnDraw(CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid)
{
CBrush brush(TranslateColor(GetBackColor())); // 定义背景色画刷,GetBackColor获得的颜色值为OLE_COLOR类型,需要用TranslateColor转换为COLORREF类型
pdc->FillRect(rcBounds, &brush); // 用画刷填充整个区域
pdc->SetTextColor(TranslateColor(GetForeColor())); // 设置前景色,同样需要转换颜色值类型
pdc->SetBkMode(TRANSPARENT); // 设置文本背景为透明模式(这样就不会有白底)

CTime time = CTime::GetCurrentTime(); // 定义CTime对象,调用GetCurrentTime静态函数
CString str = time.Format("%H:%M:%s"); // 用CTime的Format函数格式化输出时间
pdc->TextOut(0, 0, str); // 显示
}

编译后测试正常。

通常一个控件有自己的属性表,通过属性对话框来进行设置(而不是通过容器的属性表,比如VB),Clock这个控件已有一个默认的属性页General,对应的资源是IDD_PROPPAGE_CLOCK和CClockPropPage类。
修改ClockCtrl.cpp,增加一个属性页。

BEGIN_PROPPAGEIDS(CClockCtrl, 2) // 从1增加为2
PROPPAGEID(CClockPropPage::guid) // 原工程自带属性页的GUID
PROPPAGEID(CLSID_CColorPropPage) // 颜色属性页的GUID
END_PROPPAGEIDS(CClockCtrl)

编译后在容器中就能看到新增加的“颜色”属性页。

新增一个自定义属性:更新时间显示的间隔。在ClassWizard的Automation页中单击Add Property...按钮,增加External name: Interval(外部看到的属性名字),Variable name: m_Interval(类的成员变量名,自动填入),Type: short,Notification function: OnIntervalChanged(通知变量改变函数,自动增加),Implementation可选(Member variable——生成变量和通知函数,Get/Set methods——生成两个函数,用于设置和获取属性的值,但没有变量,需要自定义),这里选择Member variable。
在增加了Interval属性后,VC将其添加到_DClock接口下,在CClockCtrl中也增加有OnIntervalChanged通知函数。

void CClockCtrl::OnIntervalChanged()
{
SetModifiedFlag(); // 设置修改标记
}

在CClockCtrl.h中增加有short m_Interval;成员变量。

...
// dispatch maps
//{{AFX_DISPATCH(CClockCtrl) // 调度映射,给外部提供访问控件的方法
short m_Interval;
afx_msg void OnIntervalChanged();
//}}AFX_DISPATCH
DECLARE_DISPATCH_MAP()
...

修改OnIntervalChanged函数。

void CClockCtrl::OnIntervalChanged()
{
if (m_Interval < 0 || m_Interval > 6000) // 如果用户输入的Interval超出范围
{
m_Interval = 1000; // 则置为默认的1000
}
else
{
m_Interval = m_Interval / 1000 * 1000; // 否则将Interval取整
KillTimer(1); // 销毁原计时器
SetTimer(1, m_Interval, NULL); // 设置新的计时器
}

SetModifiedFlag();
}

编译后用ActiveX Control Test Container测试,用Control|Invoke Methods...,弹出Invoke Methods对话框,选择Method Name:为Interval (ProgPut),Parameter设置为2000,按Set Value,再按Invoke生效,之后可以看到效果。
在VB中测试,直接在Properties面板上调整Interval的值即可。

将设置Interval属性的功能加入到属性页中。
删除IDD_PROPPAGE_CLOCK中的提示文字,增加静态控件(“Interval:”),编辑框(IDC_EDIT_INTERVAL),用ClassWizard为IDC_EDIT_INTERVAL增加成员变量short m_updateInterval,Optional property name:选Interval(这是开发ActiveX控件才有的功能)。
(VC通过DDP_Text将对话框控件,变量和属性进行关联)
编译后测试,通过Clock Control属性对话框即可设置Interval的值。

对控件增加方法。用ClassWizard,Automation,Class name:选CClockCtrl,单击Add Method...,External name:(外部访问时用的方法)Hello,Internal name:(内部访问时用的方法)Hello,Return type:(返回类型)void。
VC在_DClock接口下增加Hello方法,在CClockCtrl中增加Hello函数实现。

void CClockCtrl::Hello()
{
MessageBox("Hello world!");
}

编译后用ActiveX Control Test Container测试,Control|Invoke Methods...,Method Name:Hello (Method),单击Invoke即可弹出消息框。
在VB中测试,放入Clock控件后,再增加一个按钮控件,双击,编辑代码。

// VB代码
Private Sub Command1_Click()
Clock1.Hello
End Sub

运行Form1,单击按钮后弹出消息框。

为控件增加事件。调出ClassWizard,ActiveX Events,单击Add Event...,External name:选择标准事件(Stock事件)Click。
VC在_DClockEvents下增加了Click方法,代表Click事件。
_DClockEvents是一个源(Source)接口,控件用这个接口发送分支事件,这个接口不是控件实现的,这个接口是由容器实现的。

编译后用ActiveX Control Test Container测试,在控件上点击鼠标左键,可以看到Clock Control: Click。
在VB中测试,双击控件,可以看到VB自动生成的Clock1_Click()过程。

// VB代码
Private Sub Clock1_Click()
MsgBox "control is clicked"
End Sub

运行Form1,点击鼠标左键,控件接收到消息,利用_DClockEvents事件接口中的方法Click,向容器发出事件,因为源接口_DClockEvents是由容器实现的,即控件调用容器中的Click方法,容器(VB)调用窗体过程(即Clock1_Click()),也就会显示出“control is clicked”。

为控件增加自定义事件。调出ClassWizard,ActiveX Events,单击Add Event...,External name:输入NewMinute,Internal name:(自动填入)FireNewMinute。
VC在_DClockEvents下增加了NewMinute方法,在CClockCtrl类中,增加FireNewMinute方法,在控件内部可以通过调用FireNewMinute,让控件通过_DClockEvents接口中的NewMinute方法向容器发出事件通知(NewMinute由容器实现,即收到事件后做什么由容器决定)。

控件(FireNewMinute)→_DClockEvents(NewMinute)→容器

修改OnDraw函数,定时发出FireNewMinute。

void CClockCtrl::OnDraw(CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid)
{
CBrush brush(TranslateColor(GetBackColor())); // 定义背景色画刷,GetBackColor获得的颜色值为OLE_COLOR类型,需要用TranslateColor转换为COLORREF类型
pdc->FillRect(rcBounds, &brush); // 用画刷填充整个区域
pdc->SetTextColor(TranslateColor(GetForeColor())); // 设置前景色,同样需要转换颜色值类型
pdc->SetBkMode(TRANSPARENT); // 设置文本背景为透明模式(这样就不会有白底)

CTime time = CTime::GetCurrentTime(); // 定义CTime对象,调用GetCurrentTime静态函数

if (0 == time.GetSecond())
{
FireNewMinute(); // 当秒数为0时(即新的一分钟)发出FireNewMinute
}

CString str = time.Format("%H:%M:%s"); // 用CTime的Format函数格式化输出时间
pdc->TextOut(0, 0, str); // 显示
}

编译后在VB中调试,双击控件,在代码编辑器中选择NewMinute事件,编写代码。

// VB代码
Private Sub Clock1_NewMinute()
MsgBox "new minute"
End Sub

运行Form1,每当时间到达1分钟时,弹出对话框“new minute”。
用ActiveX Control Test Container测试,每当时间到达1分钟时,显示:Clock Control: NewMinute。

自定义事件需要在某个条件满足时自己调用Fire___函数来发出事件通知,而标准事件由MFC在条件满足时自动发出通知。

目前Clock控件在VB中设置了背景色、前景色后保存工程,再次打开工程后这些属性能够恢复到之前的状态。但Interval这个属性的值无法被保存(持久化)。

void CClockCtrl::DoPropExchange(CPropExchange* pPX) // DoPropExchange负责属性的持久化
{
...
PX_Short(pPX, // 调用PX_Short来持久化m_Interval这个short类型的属性变量
"Interval", // 属性的名称
m_Interval, // 属性变量
1000); // 缺省值(即在没有保存之前)
}

编译测试,发现虽然可以保存Interval这个属性的值,但控件显示却没有相应的效果。原因是在OnCreate中设置定时器时,是写的1000这个参数。

int CClockCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct) // 在控件窗口创建完成后设置定时器
{
if (COleControl::OnCreate(lpCreateStruct) == -1)
return -1;

SetTime(1, m_Interval, NULL); // 根据m_Interval设置定时器
return 0;
}

此外,在VB中,通过控件的属性对话框调整前景色或背景色,VB自身的属性面板中也会相应的变化,说明控件通知了容器,其属性的值改变了。
而调整Interval值,其属性面板不会变化。为此,应该在Interval值改变时也通知容器。

void CClockCtrl::OnIntervalChanged()
{
if (m_Interval < 0 || m_Interval > 6000) // 如果用户输入的Interval超出范围
{
m_Interval = 1000; // 则置为默认的1000
}
else
{
m_Interval = m_Interval / 1000 * 1000; // 否则将Interval取整
KillTimer(1); // 销毁原计时器
SetTimer(1, m_Interval, NULL); // 设置新的计时器
}

// 注意:这个函数不应该方在上面那个大括号前,否则if条件判断满足的情况下也是会将Interval设置为1000的,而那个时候就没有通知到容器
BoundPropertyChanged(0x1); // 通知容器,调度ID为1的属性发生了改变。属性的调度ID可通过ClassView中双击Interval属性节点来查看

SetModifiedFlag();
}

如果向让Clock控件在设计时停止时钟的更新,为此,需要让控件能够得知自己是在“设计时”还是“运行时”(即控件处在什么环境下)。

void CClockCtrl::OnTimer(UINT nIDEvent)
{
if (AmbientUserMode()) // 判断是否在运行时(User mode)
Invalidate(); // 让窗口无效,重绘(或调用InvalidateControl效果相同)
COleControl::OnTimer(nIDEvent);
}

更多的环境函数可以通过MSDN中的COleControl的class members查阅。另外,可以参考MSDN中的ActiveX Controls文档。
对于ActiveX Control Test Container,切换Options下的Design mode来改变“设计时”或“运行时”。

当控件调试完毕后,可以选择生成Win32 Release(发行)版的ActiveX控件。

新建工程MFC AppWizard,ClockTest,Dialog based。在对话框上右击,选择Insert ActiveX Control...,插入Clock Control,可以修改其属性,按下测试开关即可切换到“运行时”。
或通过Project|Add to Project|Components and Controls...,Registered ActiveX Controls,找到Clock Control,这种方式VC会自动生成一个类,封装了对ActiveX控件访问的一些操作。
另外,还可以动态产生ActiveX控件。在CClockTestDlg中增加成员变量CClock m_clock;(同时在CClockTestDlg.cpp中包含clock.h),在对话框上增加一个按钮,双击。

void CClockTestDlg::OnButton1()
{
m_clock.Create("Clock", WS_CHILD | WS_VISIBLE, CRect(0, 0, 100, 50), this, 123); // ActiveX控件的动态创建和按钮控件非常类似
m_clock.Hello(); // 调用控件的方法Hello
m_clock.SetBackColor(RGB(0, 0, 255)); // 设置背景色(这些是封装类提供的函数)
m_clock.SetForeColor(RGB(255, 0, 0)); // 设置前景色
}

编译运行,单击按钮时,控件被创建,并按程序弹出对话框,设置背景色和前景色。

要访问ActiveX控件的事件,可在控件上右击,选择Events...,增加事件的处理。

void CClockTestDlg::OnClickClockctrl2()
{
MessageBox("control is clicked!");
}

void CClockTestDlg::OnNewMinuteClockctrl2()
{
MessageBox("new minute!");
}

要对动态创建的ActiveX控件的事件进行响应,可以参考VC为静态控件所增加的代码(如ON_EVENT宏)。
  评论这张
 
阅读(589)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017