首页 > 代码库 > ActiveX控件(ATL篇)

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

ActiveX控件(ATL篇)