源码下载:ActiveX-Clock-OCX

参照孙鑫的<<VC++深入详解>>中第18章自定义ActiveX中的Clock例子(到18.3节之前),完成了OCX控件的制作,而且也编译(Debug模式)、注册成功了!于是又创建了一个MFC基于对话框的测试程序,在对话框中放入了这个Clock控件,界面如下:

接下来右击Clock控件,选择“属性”,切换到“设置时间间隔面板”,更改时间后,切换到其他一个属性页,这时就出现assert宏异常了,看图:

真是奇怪,孙鑫的教程里也没提到有这个问题。后来试了下在Release模式下编译成功的OCX,发现在切换属性页时却没有这样的问题,一切都是正常的。

看来是优化的问题吧,详情未知,猜测而已!

看到第18章的总结(Page708)才发现,原来孙鑫老师还是提到了这个问题的。

他说出现这种错误的原因是:当将Clock控件放到VB的Form上时,该控件的窗口已经创建,也就是说,CClockCtrl类的OnCreate()方法被执行了,这样就设置了定时器。而在VC的对话框上插入Clock控件时,却没有调用CClockCtrl类的OnCreate()方法,当修改Interval属性时,会调用CClockCtrl类的OnIntervalChanged()方法,在这个方法中,调用了KillTimer(1),因为定时器根本没有创建,因此就出现了非法操作。解决办法是:用一个变量保存定时器的返回值,然后在OnIntervalChanged()方法中对返回值进行判断。

于是我将代码改成下面的样子:

void CClockCtrl::OnIntervalChanged()
{
if(m_nInterval < 0 || m_nInterval > 6000)
m_nInterval = 1000;
else
m_nInterval = m_nInterval / 1000 * 1000;
//if(timer_flag != 0)
{
MessageBox("OnIntervalChanged: going to do KillTimer()");
//KillTimer(1);
timer_flag = 0;
}
MessageBox("OnIntervalChanged: going to do SetTimer()");
//timer_flag = SetTimer(1, m_nInterval, NULL);
SetTimer(1, m_nInterval, NULL);
char message[100] = {0};
sprintf(message, "timer_flag = %d", timer_flag);
MessageBox(message);
SetModifiedFlag();
}

再经过测试,发现在切换属性页时,弹出窗口输出了“OnIntervalChanged: going to do SetTimer()”后就出现了ASSERT()宏异常,可见这个异常是出现在SetTimer()内部的。我们都知道,ASSERT()宏只有在Debug模式下才会起作用,在Release下是不会起作用的,这就是为什么使用Release时生成的ocx时不会弹出ASSERT()宏异常窗口的原因了。可是为什么SetTimer()会失败呢???

先来看下Plateform SDK中的SetTimer()原型吧:

UINT_PTR SetTimer(
HWND hWnd,              // handle to window
UINT_PTR nIDEvent,      // timer identifier
UINT uElapse,           // time-out value
TIMERPROC lpTimerFunc   // timer procedure
);

看到第一个参数hWnd了吗,这是与窗口的句柄相关联的,但是孙鑫老师也说了“将Clock控件放到VB的Form上时,该控件的窗口已经创建”,但是“在VC的对话框上插入Clock控件时,却没有调用CClockCtrl类的OnCreate()方法 ”,这里的关键不是指“OnCreate()中的SetTimer()”,而是指“窗口没有创建”,所以“窗口对应的句柄又将是多少”呢?正是因为控件窗口没有创建,所以“CClockCtrl::OnIntervalChanged() ”中的“SetTimer()”和“KillTimer()”都将会失败,而且失败的主要原因是在其函数内部对“窗口句柄”的ASSERT()判断。因此,我认为孙鑫老师说的“解决办法”是行不通的,除非不调用KillTimer()和SetTimer(),但是这样的话,就达不到控制多少秒触发一次OnDraw()的效果了!

经过调试,终于在“D:\Program Files\Microsoft Visual Studio\VC98\MFC\Include\AFXWIN2.INL文件中的第166-171行”找到了SetTimer()和KillTimer()的具体实现:

_AFXWIN_INLINE UINT CWnd::SetTimer(UINT nIDEvent, UINT nElapse,
void (CALLBACK* lpfnTimer)(HWND, UINT, UINT, DWORD))
{ ASSERT(::IsWindow(m_hWnd)); return ::SetTimer(m_hWnd, nIDEvent, nElapse,
(TIMERPROC)lpfnTimer); }
_AFXWIN_INLINE BOOL CWnd::KillTimer(int nIDEvent)
{ ASSERT(::IsWindow(m_hWnd)); return ::KillTimer(m_hWnd, nIDEvent); }

因此,我认为解决的办法有3种

1. 使用“Release方式生成的OCX”

2. 越过KillTimer()和SetTimer()中“ASSERT(::IsWindow(m_hWnd)); ”,即将CClockCtrl::OnIntervalChanged() 中的内容修改如下:

void CClockCtrl::OnIntervalChanged()
{
if(m_nInterval < 0 || m_nInterval > 6000)
m_nInterval = 1000;
else
m_nInterval = m_nInterval / 1000 * 1000;
//KillTimer(1);                 // 为了越过KillTimer()中的ASSERT(::IsWindow(m_hWnd));
::KillTimer(m_hWnd, 1);
::SetTimer(m_hWnd, 1, m_nInterval, NULL);
//SetTimer(1, m_nInterval, NULL);
SetModifiedFlag();
}

这时,在切换属性页时虽然也会执行::KillTimer和::SetTimer(),而且其中的m_hWnd可能为一个非法的值,但是起码不会弹出ASSERT()宏异常窗口,大不了就是这两个函数调用失败而已,所以也解决了这个问题。

3. 判断控件当前状态是否为运行状态,如果是才调用SetTimer()和KillTimer(),即修改CClockCtrl::OnIntervalChanged()的内容如下:

void CClockCtrl::OnIntervalChanged()
{
if(m_nInterval < 0 || m_nInterval > 6000)
m_nInterval = 1000;
else
m_nInterval = m_nInterval / 1000 * 1000;
if(AmbientUserMode())
{
KillTimer(1);                   // 为了越过KillTimer()中的ASSERT(::IsWindow(m_hWnd));
//::KillTimer(m_hWnd, 1);
//::SetTimer(m_hWnd, 1, m_nInterval, NULL);
SetTimer(1, m_nInterval, NULL);
}
SetModifiedFlag();
}

如果各位有什么不同的看法,欢迎提出来探讨!

自定义ActiveX组件在设计阶段,切换属性页后出现异常相关推荐

  1. vue2自定义分页组件,可设置每页显示数量,指定跳转具体页面

    https://blog.csdn.net/yangwei282367751/article/details/82722840 分页组件 <template>   <div> ...

  2. MFC编程入门之十三(对话框:属性页对话框及相关类的介绍)

    前面讲了模态对话框和非模态对话框,本节来将一种特殊的对话框--属性页对话框. 属性页对话框的分类 属性页对话框想必大家并不陌生,XP系统中桌面右键点属性,弹出的就是属性页对话框,它通过标签切换各个页面 ...

  3. 鸡啄米vc++2010系列12(属性页对话框)

    前面讲了模态对话框和非模态对话框,本节开始鸡啄米讲一种特殊的对话框--属性页对话框.另外,本套教程所讲大部分对VC++各个版本均可适用或者稍作修改即可,但考虑到终究还是基于VS2010版本的,所以将& ...

  4. 对话框属性页(VC_MFC)

    目录 属性页编写总体思路 CPropertySheet 编程 Tab Control 控件 (本章节中例子都是用 VS2005 编译调试的) 属性页编写总体思路 大体思想: 设置对话框属性页属性: 在 ...

  5. WX小程序组件不支持hidden属性(uni-app v-show直接作用在自定义的在组件上不起作用)

    文章目录 问题描述 过程分析 问题突破口 结论 代码验证 问题描述 今天在工作中,同事使用uni-app开发,想利用v-show指令实现页面内容切换,结果在微信小程序中,页面的内容全部显示出来了. 看 ...

  6. CPropertyPage::OnSetActive()和OnKillActive()函数:属性页切换时的处理函数

    CPropertyPage::OnKillActive virtual BOOL OnKillActive(); 返回值: 如果数据被成功更新则返回非零值:否则返回0. 说明: 当页不再是活动页时,框 ...

  7. dplayer js控制 自动全屏_vue-video-player 通过自定义按钮组件实现全屏切换效果【推荐】...

    最近公司的产品上线,一些高级功能在基础版本中不对用户开发,通过视频的形式展示. 产品开发用的是 vue, 经同事介绍使用了vue-video-player视频播放插件,通过 demo案例很快实现了视频 ...

  8. Angular 自定义分页组件,自定义每页显示个数

    后端返回列表是全部数据,分页前端做的,自定义分页组件实现前端分页,下图为分页效果: 1.创建page.component.html <nav class="navigation&quo ...

  9. Unity自定义UI组件(十一) 雷达图、属性图

    前言 借用梦想世界宠物属性图 想必大家都在游戏中见过属性图用于展示多种属性的数值,可以较为直观的对比某种属性的缺陷或者是哪种属性有优势.在三维可视化领域也会遇到类似的属性对比,用属性图来展示最为合适. ...

最新文章

  1. Visual Studio 2019没有Setup安装项目(Microsoft Visual Studio Installer Projects)的官方解决方案
  2. linux文件分别打包命令,Linux文件打包命令
  3. Linux 修改密码root(账号)
  4. 数组的几种定义方式及初始化
  5. 【转载】关于.NET下开源及商业图像处理(PSD)组件
  6. 生成树切分matlab_机器学习——手把手教你用Python实现回归树模型
  7. PCM(Pulse-code modulation)脉冲编码调制
  8. Python3的迭代器
  9. 《基于LSTM长短时神经网络的电机旋转振动单步预测》
  10. vue3.0 前进刷新后退缓存
  11. 千锋教育python老师_千锋老师分享Python经典面试题
  12. 彻底解决2440触摸屏跳点以及抖动问题
  13. 模拟电路仿真LTspice(3):三极管共发射极放大电路
  14. Python笔记day04(基础)|列表、元组
  15. Macbook Pro 外接显卡实现Tensorflow GPU运行之内屏输出
  16. 不经意传输协议-密码学
  17. Ureal:用ue4做出游戏中的爆炸特效真实感和力量感
  18. android onCreate与onCreateView的区别
  19. 【Electron】桌面应用开发
  20. 【zephyr】apds9660 接近(Proximity)传感器 驱动模型实现方式(一)

热门文章

  1. Oracle Row cache lock图解
  2. 够学习一辈子的生活经典
  3. 在应用程序级别以外使用注册为 allowDefinition='MachineToApplication' 的节是错误
  4. 吹气球问题的C语言编程,C语言怎样给一个数组中的数从大到小排序
  5. leetcode945. 使数组唯一的最小增量(排序)
  6. css注释_CSS注释示例–如何注释CSS
  7. linux gcc 示例_最好的Linux示例
  8. css 计算属性的应用_如何使用一点CSS Grid魔术设计计算器应用
  9. draft.js_如何使用快捷方式在Draft.js中创建有序列表和无序列表
  10. 如何使用JavaScript中的工厂函数构建可靠的对象