ActiveX控件(ATL篇)

时间:2022-10-02 21:25:45

目录

第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_ClickIn、Fire_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创建

本章内容根据MSDN98的ATL Tutorial翻译、整理而成。

1.1 目标

本章的目标是使用ATL创建一个下图所示的ActiveX控件。

ActiveX控件(ATL篇)

图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"按钮。

ActiveX控件(ATL篇)

图1.2

ActiveX控件(ATL篇)ActiveX控件(ATL篇)采用默认设置,直接单击"Finish"按钮。

ActiveX控件(ATL篇)

图1.3

单击"OK"按钮,完成项目创建。

ActiveX控件(ATL篇)

图1.4

1.3 增加COM类

单击【Insert】【New ATL Object...】

ActiveX控件(ATL篇)

图1.5

选中Controls里的Full Control,单击"Next"按钮

ActiveX控件(ATL篇)

图1.6

在Names页面,请输入COM类的名称。

ActiveX控件(ATL篇)

图1.7

在Attributes页面,请勾中Support ISupportErrorInfo(错误信息)和 Support Connection Points(连接点)这两个复选框。其中,连接点非常重要:ActiveX控件通过连接点把事件传递给客户程序。

ActiveX控件(ATL篇)

图1.8

Miscellaneous页面的设置保持不变

ActiveX控件(ATL篇)

图1.9

在Stock Properties页面,增加库存属性——Fill Color

ActiveX控件(ATL篇)

图1.10

上图中,单击"确定"按钮完成COM类的添加。

1.4 属性

鼠标右键单击接口IPolyCtl,弹出菜单中单击【Add Property】。注意:不要混淆IPolyCtl和_IPolyCtlEvents,后者只是用来产生事件。

ActiveX控件(ATL篇)

图1.11

多边形边数的Property Type(属性类型)为short,Property Name(属性名称)为Sides。单击"OK"按钮完成属性Sides的添加。

ActiveX控件(ATL篇)

图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...】

ActiveX控件(ATL篇)

图1.13

依下图显示进行配置,单击"OK"按钮完成ClickIn的添加。

ActiveX控件(ATL篇)

图1.14

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

1.6 实现连接点

鼠标右键单击"Polygon.idl"文件,弹出菜单中单击【Compile Polygon.idl】,编译此文件。

ActiveX控件(ATL篇)

图1.15

鼠标右键单击"CPolyCtl",弹出菜单中单击【Implement Connection Point...】

ActiveX控件(ATL篇)

图1.16

勾中"_IPolyCtlEvents",然后单击"OK"按钮。

ActiveX控件(ATL篇)

图1.17

1.7 编码

1.7.1 增加成员变量

给CPolyCtl增加成员变量m_nSides和m_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转换为COLORREF。CalcPoints函数用于计算多边形顶点坐标至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...】。

ActiveX控件(ATL篇)

图1.18

选中"WM_LBUTTONDOWN"消息,然后依次单击"Add Handler"和"OK"按钮或直接单击"Add and Edit"按钮。

ActiveX控件(ATL篇)

图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_ClickIn、Fire_ClickOut

CProxy_IPolyCtlEvents的函数Fire_ClickIn和Fire_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里增加本控件,修改FillColor和Sides属性,下次再打开此项目时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...】

ActiveX控件(ATL篇)

图1.20

选中"Controls"里的"Property Page",单击"Next"按钮

ActiveX控件(ATL篇)

图1.21

页面Names里输入名称

ActiveX控件(ATL篇)

图1.22

页面Attributes采用默认设置

ActiveX控件(ATL篇)

图1.23

页面Strings中的Title是属性页面的名称。

ActiveX控件(ATL篇)

图1.24

上图中,单击"确定"按钮,完成属性页面类的添加。

1.7.9.2 编辑属性页面

鼠标右键单击"CPolyProp"(属性页面类),弹出菜单中单击【Go To Dialog Editor】。

ActiveX控件(ATL篇)

图1.25

显示页面设计界面如下。增加一个文本框(IDC_SIDES)

ActiveX控件(ATL篇)

图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...】。

ActiveX控件(ATL篇)

图1.27

先选中IDC_SIDES,然后再选中EN_CHANGE消息。最后单击"Add and Edit"按钮。

ActiveX控件(ATL篇)

图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_NULL为CLSID_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浏览器里使用此控件,会提示如下对话框:

ActiveX控件(ATL篇)

图1.29

为了消除上面的对话框,需要实现IObjectSafety接口。其步骤如下:

1、给CPolyCtl增加基类

class ATL_NO_VTABLE CPolyCtl

: public CComObjectRootEx<CComSingleThreadModel>

... ... ...

,public IObjectSafetyImpl<CPolyCtl,INTERFACESAFE_FOR_UNTRUSTED_CALLER>

{

public:

CPolyCtl()

2、BEGIN_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++2005、2008、2010的ATL创建而成的ActiveX控件无法被VB6.0使用。解决方法:使用VC++6.0创建项目,然后使用高版本的VC++编译。

注意:高版本的VC++需要定义_WIN32_WINNT为0x0501