• 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 "));

        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、修改 ATLIncludeSTAREG.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

  • 相关阅读:
    mysql 统计新增每天数据
    Oracle dg下掉一个从库
    rman全备脚本
    Linux Shell 统计一(行列)数值的总和及行、列转换
    pt工具加字段脚本
    MySQL慢日志切割邮件发送脚本
    MySQL主从复制邮件报警脚本
    读书清单
    数据库学习笔记
    JAVAEE学习笔记
  • 原文地址:https://www.cnblogs.com/hanford/p/6103127.html
Copyright © 2020-2023  润新知