COM組件開發實踐(八)---多線程ActiveX控件和自動調整ActiveX控件大小(下)
源代碼下載:MyActiveX20081229.rar
聲明:本文代碼基于CodeProject的文章《A Complete ActiveX Web Control Tutorial》修改而來,因此同樣遵循Code Project Open License (CPOL)。
在上一篇文章《COM組件開發實踐(七)---多線程ActiveX控件和自動調整ActiveX控件大小(上)》中介紹了ActiveX控件中使用多線程的基本需求,并提出了一個簡單的線程模型,但卻出現了意想不到的問題,本文將嘗試給出問題的一個可行的解法,并同時解決上文中提出的第二個問題。
其實解決的思路也很簡單,一開始我也早就想到了的,就是使用讓子線程PostMessage來發出自定義的消息來通知主線程,特定的事件已經發生了,需求主線程去響應。這不是什么了不起的想法,但我對子線程PostMessage非常恐懼,因為以前的一個項目中就是這個問題導致了內存泄露,所以這個方案一開始就被我否定了。
遍尋解決之道不可得時,只得在csdn的論壇上發貼求教高手了,具體的討論請參考這個帖子:
http://topic.csdn.net/u/20081226/17/9bf0ae08-c54d-4934-b1b2-91baa27ff76e.html
看到jameshooo(胡柏華)的回帖后,還是決定回到起點,嘗試用PostMessage這個方案。
首先自定義兩個事件,分別表示操作成功和操作失敗

#define WM_OPTFAILED WM_APP+102 //操作失敗

然后回調函數中就變得非常簡單,只需要post對應的事件即可。

//回調函數
/////////////////////////
void CMyActiveXCtrl::OnSuccesful()
{//操作成功
this->PostMessage(WM_OPTSUCCESS,(WPARAM)NULL,(LPARAM)NULL);
}
void CMyActiveXCtrl::OnFailed()
{//操作失敗
this->PostMessage(WM_OPTFAILED,(WPARAM)NULL,(LPARAM)NULL);
}

再重載消息處理函數WindowProc,在其中調用外部的JavaScript函數或者Fire出外部頁面可以響應的事件。

{
switch (msg)
{
case WM_OPTSUCCESS:
{//操作成功,通知外部頁面
CString strOnLoaded("OnLoaded");
this->CallJScript(strOnLoaded);
return 0;
}
case WM_OPTFAILED:
{//操作失敗,通知外部頁面
//這里不寫了,同上面
}
}
return COleControl::WindowProc(msg,wParam,lParam);
}

在OnCreate函數中加入啟動工作線程代碼:

int CMyActiveXCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (COleControl::OnCreate(lpCreateStruct) == -1)
return -1;
m_MainDialog.Create(IDD_MAINDIALOG, this);
pThread.SetICallBack(this);//設置主線程回調函數
pThread.Start();//啟動工作線程
return 0;
}

重載掉OnClose函數,在其中加入關閉工作線程的代碼:

{
pThread.Stop(true);//強行關閉工作線程
COMRELEASE(pWebBrowser);
COMRELEASE(pHTMLDocument);
COleControl::OnClose(dwSaveOption);
}

到此為止,一個多線程的ActiveX控件就誕生了。這里是不會發生以前我遇到的內存泄露的,因為情況不同了,只是在回調函數中簡單的post一個message,并沒有new一個內存區域并將這塊內存作為參數post給主線程,后面這種情況是可能會內存泄露的。
Ok,下面來考慮第二個問題,先簡單介紹下具體需求:就是一個AcitveX控件會用到不同的頁面中,每個頁面對這個控件的需求也不同,也就要求在兩個不同的頁面中,控件顯示的大小也不同。
jameshooo(胡柏華) 回帖說:“改變控件大小要通知容器,由容器再反過來通知控件改變大小,不然沒有任何效果。調用IOleInPlaceSite::OnPosRectChange即可”。因此就根據這個來嘗試提出一個解決方案來。
假設有兩種模式的控件,一種是“普通”模式, 如下圖所示:
一種是“特殊”模式,
為了區別開兩者,就考慮在web頁面中通過設置參數的方式來通知ActiveX控件,對于不同的模式填充不同的對話框就可以了。我們在web頁面中控件部分加入如下參數:


相應的在CMyActiveXCtrl類中加入一個變量,這里為簡單起見,選擇了類型為CString型,主要是為了傳參數方便。


Web頁面傳入的參數值在下面這個函數中讀取:

void CMyActiveXCtrl::DoPropExchange(CPropExchange* pPX)
{
ExchangeVersion(pPX, MAKELONG(_wVerMinor, _wVerMajor));
COleControl::DoPropExchange(pPX);
PX_String(pPX, _T("IsSpecial"), m_bIsSpecial); //讀取外部設置的參數
}

為了供控件選擇,這里提供了兩種模式的對話框:

CMyDlgTwo m_dlgSpecial;//特殊模式
CMyDlgThree m_dlgCommon;//普通模式

然后在創建和繪制對話框時,通過檢測參數是否為空就知道待創建的對話框類型到底是“普通“還是”特殊“了。

{
if (COleControl::OnCreate(lpCreateStruct) == -1)
return -1;
CRect newRc;
if(m_bIsSpecial.Compare(_T(""))==0)
{//沒設置參數,"普通“模式
this->m_dlgCommon.Create(IDD_DIALOG3,this);
//設置控件的大小
newRc.left = 0;
newRc.top = 0;
newRc.right = 200;
newRc.bottom = 200;
}
else
{//設置了參數,”特殊“模式
this->m_dlgSpecial.Create(IDD_DIALOG2,this);
//設置控件的大小
newRc.left = 0;
newRc.top = 0;
newRc.right = 200;
newRc.bottom = 200;
}
this->m_pInPlaceSite->OnPosRectChange(&newRc);
return 0;
}
void CMyActiveXCtrl::OnDraw(
CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid)
{
if (!pdc)
return;
if(m_bIsSpecial.Compare(_T(""))==0)
{//沒設置參數,"普通“模式
this->m_dlgCommon.MoveWindow(rcBounds,TRUE);
}
else
{//設置了參數,”特殊“模式
this->m_dlgSpecial.MoveWindow(rcBounds,TRUE);
}
}

這種方法對于我目前的需求剛好是滿足的,但也許還有其他更好的方法,也希望有知道的能貢獻出來,一起學習下。
作者:phinecos(洞庭散人)
出處:http://phinecos.cnblogs.com/
本文版權歸作者和博客園共有,歡迎轉載,但請保留此段聲明,并在文章頁面明顯位置給出原文連接。
作者:洞庭散人
出處:http://phinecos.cnblogs.com/
本博客遵從Creative Commons Attribution 3.0 License,若用于非商業目的,您可以自由轉載,但請保留原作者信息和文章鏈接URL。RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成