0
使用ATL,可以创建高效、灵活、轻量级的控件,本教程会指导你创建控件并验证一些ATL基本原理。
本教程通过七个步骤创建ATL控件,绘制一个圆和圆内的填充多边形。我们可以向工程中添加一个控件,设置Sides属性指明多边形的边数,然后添加绘制代码在属性变化时显示控件,此外我们还可以使控件响应click事件、向控件添加一个属性页、将控件置于一个网页上。

本教程的七个步骤如下:

Step 1: 创建工程
Step 2: 向工程中添加一个控件
Step 3. 向控件添加一个属性
Step 4: 改变控件的绘制代码
Step 5: 向控件添加一个事件
Step 6: 向控件添加一个属性页
Step 7: 将控件置于一个网页

Step 1:创建工程

首先,使用“ATL COM AppWizard”创建初始的ATL工程。

  1. 在VC++环境下,点击File/New/Project tab
  2. 选择 ATL COM AppWizard
  3. 在工程名处输入 Polygon

    此时代对话框应该像下面这样:


点击 OK,则ATL COM AppWizard 会给出一个对话框,提供几种选择以配置初始的ATL工程。


因为要创建一个控件,而控件必须是一个进程内服务器(in-process server),所以设置Server Type为DLL。所有缺省的选项都合适,所以点击“Finish”;然后会出现一个对话框,显示出将要创建的主要文件列表,大体如下:

文件 描述
Polygon.cpp 包含DllMain、DllCanUnloadNow、DllGetClassObject、DllRegisterServer和DllUnregisterServer的实现。还包含对象映射,是一个工程中的ATL对象的列表,该列表初始时是空的,因为还没有创建任何对象。
Polygon.def 用于DLL的标准Windows模块定义文件
Polygon.dsw 工程工作空间
Polygon.dsp 包含工程设置的文件
Polygon.idl 接口定义语言文件,描述对象的接口
Polygon.rc 资源文件,初始包含版本信息和一个字符串(包含工程名)
Resource.h 用于资源文件的头文件
Polygonps.mk 可用于构建一个proxy/stub DLL的Make文件,本教程未用到
Polygonps.def 用于proxy/stub DLL的模块定义文件
StdAfx.cpp #include ATL实现文件的文件
StdAfx.h #include ATL头文件的头文件

为了使Polygon DLL有用,需要使用ATL Object Wizard添加一个控件


Step 2: 添加一个控件

使用ATL Object Wizard向ATL工程中添加一个对象:点击Insert菜单,选择New ATL Object,然后出现ATL Object Wizard。


ATL Object Wizard对话框中,选择要添加到当前ATL工程中的对象类别,例如基本的COM对象、为在IE中运行而裁剪的控件、属性页等。在本教程中将创建一个标准控件,所以在左边窗口选择类别为Controls,然后在右边窗口选择Full Control,最后点击Next

此时,出现一组属性页允许对加入工程的控件进行配置,键入“PolyCtl”为“Short Name”,其它的字段域自动完成填充。


Class属性域指定了被创建的实现控件的C++类的名字,.H File.CPP File 属性域显示了包含C++ 类定义的文件,而CoClass是用于该控件的组件类的名字,Interface 是控件将要实现的自定义方法和属性的接口名字,Type 是用于控件的描述,而ProgID可用于查找控件的CLSID的易读性名字。

现在为控件启用对
丰富错误信息(rich error information)和连接点(connection points)的支持:
  1. 点击 Attributes 标签tab;
  2. 选择 Support ISupportErrorInfo 复选框;
  3. 选择 Support Connection Points 复选框,这样将在IDl文件中创建对outgoing接口的支持。
当绘制多边形时需要填充颜色,所以对控件添加一个Fill Color 内建(stock)属性:
  1. 点击 Stock Properties 标签;
    此处,可以看到一个列表框,其中列出所有可用的内建属性。
  2. 滚动列表框,然后双击 Fill Color 将其移到 Supported 列表框中。


完成选项配置后,点击OK按钮。此时,代码发生几个变化,以下文件被创建:

文件 描述
PolyCtl.h 包含C++类CPolyCtl的大部分实现
PolyCtl.cpp 包含CPolyCtl的剩余部分
PolyCtl.rgs 用于注册控件的文本文件,包含注册脚本
PolyCtl.htm HTML文件,包含一个网页的源文件,其中包含到新建控件的一个引用,因而可以立即在IE中测试


Wizard还会执行以下的代码修改:
  • 一个 #include 被加入 StdAfx.h 和 StdAfx.cpp 文件,以包含控件必须的ATL文件;
  • 注册脚本 PolyCtl.rgs 被加入工程资源;
  • Polygon.idl 被修改以包含新控件的细节
  • 新控件被加入Polygon.cpp中的对象映射。

文件 PolyCtl.h 是最令人感兴趣的,因为它包含了实现控件的主要代码。现在准备构建你的控件:

  1. Build 菜单上选择 Build Polygon.dll
  2. 一旦完成控件构建,在Tools菜单上选择ActiveX Control Test Container,则Test Container被启动;
  3. Test Container中,从Edit菜单中选择Insert New Control,出现Insert Control对话框;
  4. Insert Control对话框中的可获得控件列表中,选择PolyCtl class
    应该看到一个矩形,在矩形中内含文本"ATL 3.0 : PolyCtl"。
  5. 关闭Test Container

下一步,向控件中添加一个自定义的属性。


Step 3: 向控件添加一个属性

IPolyCtl是一个包含我们自定义方法和属性的接口。最简单的向接口中添加属性的方式是在ClassView中右击,选择Add Property

Add Property to Interface 对话框出现,允许我们输入待添加的属性细节:
  1. 在属性类型的下拉列表框中,选择 short
  2. 键入 "Sides" 作为属性名(Property Name)。当编辑属性名时,Implementation 框中显示将被加入IDL文件的行;
  3. 点击 OK 完成属性添加。

MIDL (.idl文件的编译程序) 定义一个 Get 方法用于检索属性值,一个 Put 方法设置属性值。当MIDL编译idl文件时,会自动地在接口中定义两个方法:put_属性名 和 get_属性名

此外,还会将 GetPut 函数原型加入PolyCtl.h类定义文件中,并添加空的实现到 PolyCtl.cpp文件中。

为了设置和检索属性,我们需要将其存储到某个地方。点击文件视图(FileView),打开PolyCtl.h并在类定义的最后加入如下代码行:

              short m_nSides; 

现在实现 GetPut 方法。get_Sidesput_Sides 函数定义已经被加入 PolyCtl.h 文件,我们需要在PolyCtl.cpp中加入如下代码:


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;
return S_OK;
}
else
return Error(_T("Shape must have between 3 and 100 sides"));
}
get_Sides 函数通过pVal指针简单地返回当前的 Sides 属性值,在the put_Sides方法中,我们需确定用户设置的边属性值是否在可接受范围[3, 100]之内,若不在则使用ATL的 Error 函数来设置IErrorInfo接口的细节,如果容器需要关于错误更多的信息(而非简单的HRESULT)这将非常有用。

最后:初始化 m_nSides。假设缺省为三角形,则在构造器中加入如下代码:

CPolyCtl()
{
m_nSides = 3;
}

现在,控件已经有了一个称作Sides的属性,如果不用它干点什么事情,则该属性没什么用处,所以接下来需要用它来改变绘制代码。



Step 4: 改变绘制代码

在绘制代码中,要使用 sincos 函数计算多边形的顶点,所以需在PolyCtl.h的顶部包含math.h

#include <math.h>
#include "resource.h"       // main symbols

注意(仅对Release版本) 在ATL COM AppWizard生成缺省工程时,会定义宏 _ATL_MIN_CRT,该宏使得工程不会导入C运行库。但polygon控件需要C运行库中的浮点函数,因此,在构建Release版本时,需要移除 _ATL_MIN_CRT 宏。具体移除方法:Project 菜单/Settings 菜单项,在Settings For: 下拉列表框中选择Multiple Configurations;在出现的Select project configuration(s) to modify对话框中,选择应用于所有四个Release版本的复选框,然后点击OK。在C/C++ tab标签中,选择General 类别,然后从编辑框Preprocessor definitions中移除_ATL_MIN_CRT宏。

一旦多边形顶点计算出来,需要在类定义文件PolyCtl.h最后增加一个POINT类型的数组:

   OLE_COLOR m_clrFillColor;
short m_nSides;
POINT m_arrPoint[100];

现在,修改PolyCtl.h中的 OnDraw 函数。注意,移除对Rectangle和DrawText函数的调用,另外还要显式地取得并选择一个黑色的画笔和白色的画刷。

完整的 OnDraw 函数如下:
HRESULT CPolyCtl::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;
}

很明显,需要增加一个函数: CalcPoints,用于计算交点坐标,计算基于传递给函数的RECT参数。以下在PolyCtl.h中的public控制区加入CalcPoints的定义:

void           CalcPoints(const RECT& rc);

IPolyCtl类的public部分现在看起来应该如下:

// IPolyCtl
public:
STDMETHOD(get_Sides)(/*[out, retval]*/ short *newVal);
STDMETHOD(put_Sides)(/*[in]*/ short newVal);
void    CalcPoints(const RECT& rc);

接下来,在PolyCtl.cpp文件中增加CalcPoints函数的实现

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;
}
}

现在,初始化 m_clrFillColor 成员变量,选择绿色作为缺省颜色。在PolyCtl.h的CPolyCtl构造器中加入代码:

   m_clrFillColor = RGB(0, 0xFF, 0);

构造器现在应该如下:

CPolyCtl()
{
m_nSides = 3;
m_clrFillColor = RGB(0, 0xFF, 0);
}

重新编译构建控件。打开ActiveX Control Test Container ,插入控件,我们应该能够看到在一个圆内有一个绿色的三角形。然后,按照如下步骤尝试改变边的数量:

  1. Test Container 中, 在Control 菜单上点击 Invoke Methods
    出现 Invoke Method 对话框
  2. Method Name点击Sides属性的PropPut;
  3. Parameter Value 编辑框中输入5,点击 Set Value ,然后点击 Invoke


注意:控件没有发生变化,为什么呢?虽然我们通过设置m_nSides变量改变了边数,但是没有引起控件重绘,如果此时切换到另一个应用程序,然后切换回来,就会发现控件重绘了且图形的边数正确了。

为了修正这个问题,需要在设置了边数之后对FireViewChange 函数进行调用,该函数定义在IViewObjectExImpl接口中。如果控件运行在自有窗口中,FireViewChange将会直接调用InvalidateRect API函数;如果控件在无窗口模式下运行,InvalidateRect 方法会被容器的接口调用,从而迫使控件重新绘制。

新的 put_Sides 方法变化如下:

STDMETHODIMP CPolyCtl::put_Sides(short newVal)
{
if (newVal > 2 && newVal < 101)
{
m_nSides = newVal;
FireViewChange();
return S_OK;
}
else
return Error(_T("Shape must have between 3 and 100 sides"));
}

重建并测试控件,应该可以看到期望的改变。

下一步,向控件添加事件


Step 5: 添加一个事件

本步骤向ATL控件中添加ClickIn和ClickOut事件。如果用户在多边形内部单击,则触发ClickIn;否则,触发ClickOut事件。

在第二步中,我们创建Full Control时选择了Support Connection Points复选框,这样就会在.idl文件中创建_IPolyCtlEvents接口。注意:该接口名以下划线开始,这是一个约定,暗示该接口是一个内部接口,该接口不会暴露给用户;另外,注意Support Connection Points 会在.idl文件中添加一行,表示_IPolyCtlEvents接口是缺省的源接口(source interface),这里源属性表示控件是通知的源,所以会在容器中调用该接口。

现在,应该向_IPolyCtlEvents接口中添加 ClickIn 和 ClickOut 方法:

  1. ClassView中右击 _IPolyCtlEvents接口,从弹出的菜单中选择 Add Method…
  2. 选择返回类型为 void
  3. Method Name 框中键入 ClickIn
  4. Parameters 框中输入:[in] long x, [in] long y
  5. 点击 OK

检查 .idl 文件,看看被添加为IPolyCtlEvents disp接口的代码,然后使用同样的过程定义 ClickOut 方法。最终,应该能够在.idl文件中看到如下的IPolyCtlEvents disp接口:

dispinterface _IPolyCtlEvents
{
properties:
methods:
[id(1), helpstring("method ClickIn")] void ClickIn([in]long x, [in] long y);
[id(2), helpstring("method ClickOut")] void ClickOut([in] long x, [in] long y);
};

ClickInClickOut 方法以鼠标点击位置的x、y坐标作为参数。

现在,生成我们的类型库。具体方法有二:(1)重新编译构建工程;(2)或者在FileView 中右击.idl文件,选择Compile Polygon.idl,就会生成类型库文件Polygon.tlb

接下来,为控件实现一个连接点接口(connection point interface)和一个连接点容器接口。

在COM中,事件通过连接点机制实现:要从一个COM对象接收事件,容器要建立一个咨询连接(advisory connection),连到该COM对象实现的连接点。由于一个COM对象可以有多个连接点,所以COM对象也要实现一个连接点容器接口,通过该接口,容器可以确定哪个连接点被支持。

实现连接点的接口:IConnectionPoint
实现连接点容器的接口:
IConnectionPointContainer

为了便于实现IConnectionPoint,使用ClassView 来访问连接点Wizard。该Wizard通过读取类型库并为每个可触发事件实现一个函数来生成IConnectionPoint 接口

要运行这个Wizard,执行如下步骤:

  1. ClassView 视图下;
  2. 选择控件的实现类并右击,这里控件的实现类是 CPolyCtl
  3. 在快捷菜单中,选择 Implement Connection Point…
  4. Interfaces 列表中选择 _PolyEvents ,然后选择 OK ,则会产生一个连接点的代理类,这里,该代理类应该是:CProxy_IPolyCtlEvents。


这时如果从FileView, 视图打开产生的PolygonCP.h文件,可以看到有一个从IConnectionPointImpl派生的类CProxy_PolyCtlEvents。此外,还可以看到定义的两个方法:Fire_ClickIn 和 Fire_ClickOut,都具有两个坐标参数。这两个方法就是需要从控件触发事件时要调用的方法。

此外,Wizard还会把CProxy_PolyEventsIConnectionPointContainerImpl 加入加到控件的继承列表中,且通过向COM映射中增加合适的入口项为我们暴露出IConnectionPointContainer 接口:

COM_INTERFACE_ENTRY_IMPL(IConnectionPointContainer)

至此,完成支持事件的代码。接下来,需要在合适的时间触发事件:当用户在控件中左键单击时触发一个ClickInClickOut 事件。那么什么时候用户鼠标单击呢?首先WM_LBUTTONDOWN加入一个消息处理器:在CPolyCtl类上右击,从快捷菜单中选择Add Windows Message Handler... ,然后从左边的列表中选择WM_LBUTTONDOWN,并点击Add Handler按钮,最后点击OK

接着,需要修改PolyCtl.h中的OnLButtonDown 函数:

LRESULT CPolyCtl::OnLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
HRGN hRgn;
WORD xPos = LOWORD(lParam); // 鼠标指针水平位置
WORD yPos = HIWORD(lParam); //
鼠标指针垂直位置
CalcPoints(m_rcPos);

// 根据点序列生成一个多边形区域
hRgn = CreatePolygonRgn(&m_arrPoint[0], m_nSides, WINDING);

// 如果点击位置在多边形区域内,触发 ClickIn
// 否则,触发 ClickOut事件
if (PtInRegion(hRgn, xPos, yPos))
Fire_ClickIn(xPos, yPos);
else
Fire_ClickOut(xPos, yPos);

// 删除创建的多边形区域
DeleteObject(hRgn);
return 0;
}

由于已经在OnDraw函数中计算过多边形的点序列,用该点序列创建一个多边形区域,使用PtInRegion API函数确定点击位置在多边形内部还是外部。

uMsg 参数是被处理Windows消息的ID标识,通过uMsg我们可以处理一个范围内的消息;
wParam 和 lParam 参数是被处理消息的标准值;
bHandled 参数允许指定是否函数处理该消息。缺省为TRUE表示处理消息,如果设置为FALSE,则ATL会继续寻找另一个消息处理函数,并将消息发送给它。

现在,测试事件。构建控件,启动ActiveX Control Test Container,可以看到事件日志窗口,插入控件进行测试。


转自:http://hi.baidu.com/mizzletown/blog/item/fdc4a39b1075bbb1c8eaf420.html

关闭 返回顶部
联系我们
Copyright © 2011. 聚财吧. All rights reserved.