自定义ActiveX组件在设计阶段,切换属性页后出现异常
源碼下載: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;elsem_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 windowUINT_PTR nIDEvent, // timer identifierUINT uElapse, // time-out valueTIMERPROC 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;elsem_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;elsem_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组件在设计阶段,切换属性页后出现异常的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 那些年,登山徒步记录,立贴
- 下一篇: JAVA获取汉字拼音首字母