ActiveX控件(ATL篇)


1 VC++6.0創建    2

1.1 目標    2

1.2 創建項目    2

1.3 增加COM    4

1.4 屬性    7

1.5 事件    8

1.6 實現連接點    9

1.7 編碼    11

1.7.1 增加成員變量    11

1.7.2 初始化成員變量    11

1.7.3 完成屬性賦值代碼    11

1.7.4 完成控件繪制代碼    11

1.7.5 響應鼠標左鍵按下消息    13

1.7.6 修改DllUnregisterServer    14

1.7.7 修改Fire_ClickInFire_ClickOut    14

1.7.8 保存、恢復屬性值    14

1.7.9 屬性頁    15

1.7.10 實現IObjectSafety接口    20

1.8 注冊    21

1.9 BUG    22

 

 

1 VC++6.0創建

本章內容根據MSDN98ATL Tutorial翻譯、整理而成。

1.1 目標

本章的目標是使用ATL創建一個下圖所示的ActiveX控件。

1.1

這個控件只有一個屬性 short Sides;用於指定多邊形的邊數。

這個控件有兩個事件:

void ClickIn([in]long x, [in] long y);

void ClickOut([in] long x, [in] long y);

當鼠標左鍵在多邊形內部按下時將觸發ClickIn事件;當鼠標左鍵在多邊形外部按下時將觸發ClickOut事件。

1.2 創建項目

運行VC++6.0,新建"ATL COM AppWizard"項目,如下圖所示。配置好項目名稱、項目目錄后,單擊"OK"按鈕。

1.2

采用默認設置,直接單擊"Finish"按鈕。

1.3

單擊"OK"按鈕,完成項目創建。

1.4

1.3 增加COM

單擊【Insert】【New ATL Object...

1.5

選中Controls里的Full Control,單擊"Next"按鈕

1.6

Names頁面,請輸入COM類的名稱。

1.7

Attributes頁面,請勾中Support ISupportErrorInfo(錯誤信息)和 Support Connection Points(連接點)這兩個復選框。其中,連接點非常重要:ActiveX控件通過連接點把事件傳遞給客戶程序。

1.8

Miscellaneous頁面的設置保持不變

1.9

Stock Properties頁面,增加庫存屬性——Fill Color

1.10

上圖中,單擊"確定"按鈕完成COM類的添加。

1.4 屬性

鼠標右鍵單擊接口IPolyCtl,彈出菜單中單擊【Add Property】。注意:不要混淆IPolyCtl_IPolyCtlEvents,后者只是用來產生事件。

1.11

多邊形邊數的Property Type(屬性類型)為shortProperty Name(屬性名稱)為Sides。單擊"OK"按鈕完成屬性Sides的添加。

1.12

1.5 事件

現在添加兩個事件:

void ClickIn([in]long x, [in] long y);

void ClickOut([in] long x, [in] long y);

當鼠標左鍵在多邊形內部按下時將觸發ClickIn事件;當鼠標左鍵在多邊形外部按下時將觸發ClickOut事件。

鼠標右鍵單擊_IPolyCtlEvents,彈出菜單中單擊【Add Method...

1.13

依下圖顯示進行配置,單擊"OK"按鈕完成ClickIn的添加。

1.14

用同樣的方法添加ClickOut事件。

1.6 實現連接點

鼠標右鍵單擊"Polygon.idl"文件,彈出菜單中單擊【Compile Polygon.idl】,編譯此文件。

1.15

鼠標右鍵單擊"CPolyCtl",彈出菜單中單擊【Implement Connection Point...

1.16

勾中"_IPolyCtlEvents",然后單擊"OK"按鈕。

1.17

1.7 編碼

1.7.1 增加成員變量

CPolyCtl增加成員變量m_nSidesm_arrPoint。可以增加到庫存屬性m_clrFillColor的下方:

OLE_COLOR    m_clrFillColor;         //填充色(庫存屬性)

short            m_nSides;             //多邊形邊數

POINT            m_arrPoint[100];     //多邊形頂點坐標

1.7.2 初始化成員變量

CPolyCtl的構造函數里對成員變量進行初始化:

CPolyCtl()

{

m_nSides = 3;                             //初始化為三角形

m_clrFillColor = RGB(0, 0xFF, 0);         //填充顏色默認為綠色

memset(m_arrPoint,0,sizeof(m_arrPoint));     //多邊形頂點初始化為零

}

1.7.3 完成屬性賦值代碼

STDMETHODIMP CPolyCtl::get_Sides(short *pVal)

{

*pVal = m_nSides;

return S_OK;

}

STDMETHODIMP CPolyCtl::put_Sides(short newVal)

{

if (newVal > 2 && newVal < 101)

{

m_nSides = newVal;

FireViewChange();

return S_OK;

}

return Error(_T("Shape must have between 3 and 100 sides"));

}

獲取Sides屬性時,將調用get_Sides函數;修改Sides屬性時,將調用put_Sides函數。FireViewChange()將通知控件重新繪制。CComCoClass::Error函數將產生一個錯誤信息。

1.7.4 完成控件繪制代碼

控件的繪制由CPolyCtl::OnDraw負責。

HRESULT OnDraw(ATL_DRAWINFO& di)

{

RECT& rc = *(RECT*)di.prcBounds;

HDC hdc = di.hdcDraw;

COLORREF colFore;

HBRUSH hOldBrush, hBrush;

HPEN hOldPen, hPen;

//Translate m_colFore into a COLORREF type

OleTranslateColor(m_clrFillColor, NULL, &colFore);

//Create and select the colors to draw the circle

hPen = (HPEN)GetStockObject(BLACK_PEN);

hOldPen = (HPEN)SelectObject(hdc, hPen);

hBrush = (HBRUSH)GetStockObject(WHITE_BRUSH);

hOldBrush = (HBRUSH)SelectObject(hdc, hBrush);

Ellipse(hdc, rc.left, rc.top, rc.right, rc.bottom);

// Create and select the brush that will be used to fill the polygon

hBrush = CreateSolidBrush(colFore);

SelectObject(hdc, hBrush);

CalcPoints(rc);

Polygon(hdc, &m_arrPoint[0], m_nSides);

// Select back the old pen and brush and delete the brush we created

SelectObject(hdc, hOldPen);

SelectObject(hdc, hOldBrush);

DeleteObject(hBrush);

return S_OK;

}

上面的OleTranslateColor用於將OLE_COLOR轉換為COLORREFCalcPoints函數用於計算多邊形頂點坐標至m_arrPoint,其代碼如下:

void CPolyCtl::CalcPoints(const RECT& rc)

{

const double pi = 3.14159265358979;

POINT ptCenter;

double dblRadiusx = (rc.right - rc.left) / 2;

double dblRadiusy = (rc.bottom - rc.top) / 2;

double dblAngle = 3 * pi / 2; // Start at the top

double dblDiff = 2 * pi / m_nSides; // Angle each side will make

ptCenter.x = (rc.left + rc.right) / 2;

ptCenter.y = (rc.top + rc.bottom) / 2;

// Calculate the points for each side

for (int i = 0; i < m_nSides; i++)

{

m_arrPoint[i].x = (long)(dblRadiusx*cos(dblAngle) + ptCenter.x + 0.5);

m_arrPoint[i].y = (long)(dblRadiusy*sin(dblAngle) + ptCenter.y + 0.5);

dblAngle += dblDiff;

}

}

1.7.5 響應鼠標左鍵按下消息

鼠標右鍵單擊"CPolyCtl",彈出菜單中單擊【Add Windows Message Handler...】。

1.18

選中"WM_LBUTTONDOWN"消息,然后依次單擊"Add Handler"和"OK"按鈕或直接單擊"Add and Edit"按鈕。

1.19

編輯CPolyCtl::OnLButtonDown函數如下:

LRESULT OnLButtonDown(UINT uMsg

,WPARAM wParam, LPARAM lParam, BOOL& bHandled)

{

HRGN hRgn;

WORD xPos = LOWORD(lParam); // horizontal position of cursor

WORD yPos = HIWORD(lParam); // vertical position of cursor

CalcPoints(m_rcPos); // Create a region from our list of points

hRgn = CreatePolygonRgn(&m_arrPoint[0], m_nSides, WINDING);

// If the clicked point is in our polygon then fire the ClickIn

// event otherwise we fire the ClickOut event

if (PtInRegion(hRgn, xPos, yPos))

Fire_ClickIn(xPos, yPos);

else

Fire_ClickOut(xPos, yPos); // Delete the region that we created

DeleteObject(hRgn);

return 0;

}

Fire_ClickIn將觸發ClickIn事件給客戶程序;Fire_ClickOut將觸發ClickOut事件給客戶程序。

1.7.6 修改DllUnregisterServer

STDAPI DllUnregisterServer(void)

{

    HRESULT hr = _Module.UnregisterServer();

#if _WIN32_WINNT >= 0x0400

    if (FAILED(hr))

        return hr;

    // Following assumes that the type library version is 1.0

    hr = UnRegisterTypeLib(LIBID_POLYGONLib, 1, 0

, LOCALE_NEUTRAL, SYS_WIN32);

#endif

    return hr;

}

1.7.7 修改Fire_ClickInFire_ClickOut

CProxy_IPolyCtlEvents的函數Fire_ClickInFire_ClickOut中的如下代碼有問題:

pvars[1] = x;

pvars[0] = y;

請更改為:

pvars[1].vt = VT_I4;

pvars[1].lVal= x;

pvars[0].vt = VT_I4;

pvars[0].lVal= y;

1.7.8 保存、恢復屬性值

完成上述步驟,即可編譯本項目,生成的Polygon.dll將自動注冊。VB6.0里也可以使用這個控件了。但是,兩個屬性里FillColor可以保存,Sides卻不能保存。也就是說:VB6.0里增加本控件,修改FillColorSides屬性,下次再打開此項目時FillColor是上次修改的值,而Sides將恢復成構造函數里的數值3

為此,需要增加下代碼PROP_ENTRY("Sides",1,CLSID_NULL),如下所示:

BEGIN_PROP_MAP(CPolyCtl)

PROP_DATA_ENTRY("_cx", m_sizeExtent.cx, VT_UI4)

PROP_DATA_ENTRY("_cy", m_sizeExtent.cy, VT_UI4)

PROP_ENTRY("FillColor", DISPID_FILLCOLOR, CLSID_StockColorPage)

PROP_ENTRY("Sides", 1, CLSID_NULL)

END_PROP_MAP()

PROP_ENTRY("Sides",1,CLSID_NULL)中的"Sides"是屬性名稱。1是該屬性在odl文件里的順序號。CLSID_NULL表示該屬性不在任何屬性頁面內。

1.7.9 屬性頁

1.7.9.1 增加屬性頁面類

單擊【Insert】【New ATL Object...

1.20

選中"Controls"里的"Property Page",單擊"Next"按鈕

1.21

頁面Names里輸入名稱

1.22

頁面Attributes采用默認設置

1.23

頁面Strings中的Title是屬性頁面的名稱。

1.24

上圖中,單擊"確定"按鈕,完成屬性頁面類的添加。

1.7.9.2 編輯屬性頁面

鼠標右鍵單擊"CPolyProp"(屬性頁面類),彈出菜單中單擊【Go To Dialog Editor】。

1.25

顯示頁面設計界面如下。增加一個文本框(IDC_SIDES

1.26

1.7.9.3 響應Apply

單擊屬性頁上的Apply按鈕,會調用CPolyProp::Apply函數。在這里,把屬性頁面上的輸入值賦給屬性值,代碼如下:

STDMETHOD(Apply)(void)

{

    USES_CONVERSION;

    ATLTRACE(_T("CPolyProp::Apply\n"));

    for (UINT i = 0; i < m_nObjects; i++)

    {

        CComQIPtr<IPolyCtl, &IID_IPolyCtl> pPoly(m_ppUnk[i]);

        short nSides = (short)GetDlgItemInt(IDC_SIDES);

        if FAILED(pPoly->put_Sides(nSides))

        {

            CComPtr<IErrorInfo> pError;

            CComBSTR strError;

            GetErrorInfo(0, &pError);

            pError->GetDescription(&strError);

            MessageBox(OLE2T(strError),_T("Error")

,MB_ICONEXCLAMATION);

            return E_FAIL;

}

    }

    m_bDirty = FALSE;

    return S_OK;

}

1.7.9.4 使Apply按鈕可用

默認情況下,Apply按鈕是不可用的。在圖1.26中,修改Sides屬性值后,應該讓Apply按鈕可用,為此需要響應文本框的消息。

鼠標右鍵單擊"CPolyProp"(屬性頁面類),彈出菜單中單擊【Add Windows Message Handler...】。

1.27

先選中IDC_SIDES,然后再選中EN_CHANGE消息。最后單擊"Add and Edit"按鈕。

1.28

修改CPolyProp::OnChangeSides如下:

LRESULT OnChangeSides(WORD wNotifyCode,WORD wID

,HWND hWndCtl, BOOL& bHandled)

{

SetDirty(TRUE);

return 0;

}

SetDirty(TRUE);說明屬性值改變了,按鈕Apply就可使用了。

1.7.9.5 增加屬性頁面

修改PROP_ENTRY("Sides", 1, CLSID_NULL)中的CLSID_NULLCLSID_PolyProp

BEGIN_PROP_MAP(CPolyCtl)

PROP_DATA_ENTRY("_cx", m_sizeExtent.cx, VT_UI4)

PROP_DATA_ENTRY("_cy", m_sizeExtent.cy, VT_UI4)

PROP_ENTRY("FillColor", DISPID_FILLCOLOR, CLSID_StockColorPage)

PROP_ENTRY("Sides", 1, CLSID_PolyProp)

END_PROP_MAP()

1.7.10 實現IObjectSafety接口

IE瀏覽器里使用此控件,會提示如下對話框:

1.29

為了消除上面的對話框,需要實現IObjectSafety接口。其步驟如下:

1、給CPolyCtl增加基類

class ATL_NO_VTABLE CPolyCtl

: public CComObjectRootEx<CComSingleThreadModel>

... ... ...

,public IObjectSafetyImpl<CPolyCtl,INTERFACESAFE_FOR_UNTRUSTED_CALLER>

{

public:

CPolyCtl()

2BEGIN_COM_MAP(CPolyCtl)END_COM_MAP()之間增加代碼:

BEGIN_COM_MAP(CPolyCtl)

... ... ...

COM_INTERFACE_ENTRY(IObjectSafety)

END_COM_MAP()

1.8 注冊

ATL3.0 編寫的組件在注冊時,如果組件所在目錄包含中文,則注冊后注冊表中的路徑會有亂碼,導致無法正常使用組件。

解決方法一:使用 UNICODE,即定義宏 _UNICODE

解決方法二:

1、編譯時預定義宏 _ATL_STATIC_REGISTRY

_ATL_DLL 表示動態鏈接 ATL.DLL

_ATL_STATIC_REGISTRY 表示注冊組件時,不再使用 ATL.DLL

不能定義 _ATL_DLL,必須定義 _ATL_STATIC_REGISTRY。這樣就不會使用 ATL.DLL,也就不會產生路徑亂碼。

2、修改 ATL\Include\STAREG.H 文件里的 AddChar AddString函數:

BOOL AddChar(const TCHAR* pch)

{

//if (nPos == nSize) // realloc

//fix register bug with chinese path

if (nPos == nSize - 1 )

{

nSize *= 2;

p = (LPTSTR) CoTaskMemRealloc(p, nSize*sizeof(TCHAR));

}

p[nPos++] = *pch;

#ifndef _UNICODE

if(IsDBCSLeadByte(*pch))

{

p[nPos++] = *(pch + 1);

}

#endif

return TRUE;

}

 

BOOL AddString(LPCOLESTR lpsz)

{

USES_CONVERSION;

LPCTSTR lpszT = OLE2CT(lpsz);

while (*lpszT)

{

AddChar(lpszT);

#ifndef _UNICODE

//fix bug with chinese path

if (IsDBCSLeadByte(*lpszT))

{

lpszT++;

}

#endif

lpszT++;

}

return TRUE;

}

1.9 BUG

使用VC++200520082010ATL創建而成的ActiveX控件無法被VB6.0使用。解決方法:使用VC++6.0創建項目,然后使用高版本的VC++編譯。

注意:高版本的VC++需要定義_WIN32_WINNT0x0501


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM