65.9K
CodeProject 正在变化。 阅读更多。
Home

C++ 和 ATL 中的 2D 图形 ActiveX 控件(无 MFC 依赖)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (25投票s)

2012 年 1 月 4 日

CPOL

7分钟阅读

viewsIcon

130404

downloadIcon

12021

绘制多个数据集、交互式工具提示信息、缩放/平移、编辑颜色/宽度/格式、注释、打印/保存

引言

开发人员经常需要绘制各种数据。他们希望使用一个依赖项最少、轻量级的控件。

背景

NTGraph 控件是一个功能强大的 ActiveX 控件,可以绘制多个数据集。但不幸的是,它依赖于 MFC 库。

这款名为 DMGraph 的新 2D 图表 ActiveX 控件基于 NTGraph 绘图引擎,但消除了 MFC 依赖。对于 DMGraph,ATL 3.0 被用作框架。唯一的依赖项是一些 Microsoft Windows DLL(C 运行时库 msvcrt.dll 从 Windows 2000 开始就已包含在操作系统中)。这意味着没有部署问题 - DMGraph 可在 Windows 2000 或更高版本上运行。

与旧的 NTGraphCtrl 相比,另一个重大变化是公开的 COM 接口架构。DMGraphCtrl 没有将所有内容打包到一个接口中,而是公开了一个表示绘图所用实体的接口层次结构。

Using the Code

主接口 IDMGraphCtrl 包含项的集合(由 IDMGraphCollection 接口管理)。此集合接口公开了常用方法(如 AddDeleteCountItem)。特有的概念是“选定项”。集合中的一个项可以是“选定”的。有时用户操作(如鼠标拖动)会应用于“选定”项(如果存在)。IDMGraphCollection::Selected 属性获取/设置选定项的索引。

当用户双击图表区域时,将显示一个带有属性页的模态对话框。此对话框也可以通过 ShowProperties 方法以编程方式调用。在这些属性页中修改数据会立即反映在显示的图表中。

CDMGraphCtrl 类实现了 IDMGraphCtrl 接口。在运行时,可以使用 DM Graph 属性页查看或更改某些属性。

CDMGraphCtrl 类维护 IDMGraphCtrl 接口公开的以下集合:

1. 元素集合

get_Elements 属性公开元素集合。

每个项都是 CGraphElement 类的实例,该类公开 IDMGraphElement 接口。图表元素是需要绘制的点集合。图表元素具有定义其绘图样式的各种属性。例如,Linetype 属性定义了用于连接点的线(包括“Null” - 完全不绘制线条)。可以为点设置颜色、宽度、形状;可以启用/禁用整个点集进行绘制等。每个图表元素都由一个“name”标识。所有这些都可通过 IDMGraphElement 接口公开的 COM 属性访问。设置此类属性时,整个图表都会重新绘制以反映更改。

要绘制的点集(数据)由客户端通过多种方法提供:

  • Plot - 两个大小相同的⼀维数组(一个用于 X,一个用于 Y)将为特定图表元素设置整个点集合。
  • PlotXY - 只向点集合添加一个点(同时指定 X 和 Y 坐标)。
  • PlotY - 只向点集合添加一个点(只指定 Y,X 是添加点在点集合中的索引)。

每次修改点集合时,图表都会更新以反映更改,但范围不会更新。如果新点超出范围,则需要调用 SetRangeAutoRange 方法。

可以向集合中添加新元素,删除现有元素,更改选定元素索引,并通过“元素”属性页查看/更改选定元素属性。

2. 注释集合

get_Annotations 属性公开注释集合。

一个注释是显示在图表特定位置的文本。此集合维护 CGraphAnnotation 类的实例,该类公开 IDMGraphAnnotation 接口。使用此接口可以访问各种属性,例如标题(显示的文本)、位置、颜色、文本方向、背景启用/禁用。设置此类属性时,整个图表都会重新绘制以反映更改。

可以向集合中添加新注释,删除现有注释,更改选定注释索引,并通过“注释”属性页查看/更改选定注释属性。

3. 光标集合

get_Cursors 属性公开光标集合。

光标由一到两条与 X 或 Y 轴平行的线组成。IDMGraphCursor 接口处理光标特定属性。如果 Style 属性(类型为 Crosshair enum)设置为“XY”,则光标将有两条线:一条平行于 X 轴,另一条平行于 Y 轴。如果光标 Mode 设置为 Snap,则选定的光标在鼠标拖动时会吸附到选定图表元素的最近点。

可以向集合中添加新光标,删除现有光标,更改选定光标索引,并通过“光标”属性页查看/更改选定光标属性。

4. 轴对象

get_Axis 属性公开两个对象:一个用于 X(水平)轴,另一个用于 Y(垂直)轴。这些对象是 CGraphAxis 类的实例,该类公开 IDMGraphAxis 接口。可以为每个轴获取/设置各种属性。如果 put_Time 属性设置为 VARIANT_TRUE,则该轴的双精度值将被视为日期/时间值。这些值按照 OLE 自动化 VARIANT 联合使用的 DATE 类型进行解释。值根据 Format 属性设置的格式字符串显示。对于日期/时间,可能的格式字符串在 MSDN 中的 strftime 函数中有说明。否则,对于非对数轴,则接受常规的 sprintf 格式字符串。一些轴属性可以在 DM Graph 属性表中(见上文)获得,而另一些属性可以在格式属性页(见下文)中获得。

从“轴”组合框中,可以选择 X(底部)或 Y(左侧)轴。然后可以为选定的轴设置数据类型。对于每种类型,“模板”列表框会填充可用的格式模板。当从左侧选择一个模板项时,“格式”字符串右侧会更新。

基本用法

1. 从使用 ATL 编写的 C++ Windows 客户端

class CMainWnd : public CWindowImpl<CMainWnd>
{ 
     CAxWindow*                 m_pGraphCtrl; 
     CComPtr<IDMGraphCtrl>      m_spDMGraph; 
}; 

LRESULT CMainWnd::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)  
{ 
     m_pGraphCtrl = new CAxWindow; 
     if(m_pGraphCtrl == NULL) 
           return -1; 
     if(!AtlAxWinInit()) 
           return -1; 
     HRESULT hr; 
     CComPtr<IAxWinHostWindow> spHost; 
     hr = m_pGraphCtrl->QueryHost(IID_IAxWinHostWindow, (void**)&spHost); 
     if(FAILED(hr)) 
     { 
         Message(hr, NULL, L"Cannot query Ax host"); 
         return -1; 
     } 
     hr = spHost->CreateControl(L"DMGraph.DMGraphCtrl", m_pGraphCtrl->m_hWnd, NULL); 
     if(FAILED(hr)) 
     { 
         Message(hr, NULL, L"Cannot start DM Graph control"); 
         return -1; 
     } 

     CComVariant vData; 
     hr = m_pGraphCtrl->QueryControl(IID_IDMGraphCtrl, (void**)&m_spDMGraph); 
     if(FAILED(hr) || m_spDMGraph == NULL) 
     { 
         Message(hr, NULL, L"Cannot query DM Graph control"); 
         return -1; 
     } 
     return 0;
}  
 
void CMainWnd::SetGraphData(VARIANT* pvarrX, VARIANT* pvarrY, LPCTSTR szName)
{ 
  ATLASSERT(pvarrX); 
  ATLASSERT(pvarrY); 
  ATLASSERT(szName); 
  CComBSTR bsName(szName); 
  CComPtr<IDMGraphCollection> spElements; 
  CComPtr<IDMGraphElement> spGraphElement; 
  HRESULT hr = m_spDMGraph->get_Elements(&spElements); 
  long i, nElementCount = 0; 
  BOOL bReplace = FALSE; 
  hr = spElements->get_Count(&nElementCount); 
  for(i=0; i<nElementCount; i++) 
  { 
     CComBSTR bsElemName; 
     CComPtr<IDispatch> spDispatch; 
     
     hr = spElements->get_Item(i, &spDispatch); 

     hr = spDispatch.QueryInterface(&spGraphElement); 

     spGraphElement->get_Name(&bsElemName); 
     if(_wcsicmp(bsElemName, bsName) == 0) 
    { 
         OLECHAR szMsgText[256]; 
        _snwprintf(szMsgText, 256,
              L"There is ALREADY an element named '%s'.\n"
              L"Do you want to replace it ?", bsElemName); 
         if(::MessageBoxW(m_hWnd, szMsgText, NULL,
            MB_YESNO|MB_ICONQUESTION) != IDYES)
         { 
              return;
         } 
         bReplace = TRUE; 
         break; 
    } 
    else 
         spGraphElement = NULL; 
  } 
  if(bReplace == FALSE || spGraphElement == NULL) 
  { 
     CComPtr<IDispatch>     spDispatch; 
     hr = spElements->Add(&spDispatch); 
     spGraphElement = NULL; 
     hr = spDispatch.QueryInterface(&spGraphElement); 

  } 
  hr = spGraphElement->put_Name(bsName); 
  hr = spGraphElement->put_PointSymbol( Dots ); 
  hr = spGraphElement->put_PointSize(3); 
  hr = spGraphElement->Plot(*pvarrX, *pvarrY); 
  if(FAILED(hr)) 
  { 
     Message(hr, spGraphElement, L"Failed to plot items");
     return; 
  } 
  hr = m_spDMGraph->AutoRange(); 
}

2. 使用 VBScript 从 HTML 页面

在 HTML body 中,使用 object 标签创建 ActiveX。单击按钮将执行脚本来设置要绘制的数据。

<object ID="DMGraphCtrl" 
      CLASSID="CLSID:AAF89A51-7FC0-43B0-9F81-FFEFF6A8DB43" 
      width=600 height=400 VIEWASTEXT></object>
<input id=BtnSin value=sin type="button">
<script id=clientEventHandlersVBS language="vbscript">
<!--
Sub BtnSin_onclick
  On Error Resume Next
  Dim dmGraphCtrl
  Set dmGraphCtrl = document.getElementById("DMGraphCtrl")
  Dim idx : idx = dmGraphCtrl.Elements.Selected
  If idx < 0 Then
    MsgBox("Error: please create and select an element first." &_
             vbCrLf & "(Double click to see property pages)")
  Else
    Dim selElement
    Set selElement = dmGraphCtrl.Elements.Item(idx)
    Dim i
    Dim x()
    Dim y()
    ReDim x(100)
    ReDim y(100)
    For i=0 To 100
      x(i) = i/5
      y(i) = Sin( x(i) )
    Next
    selElement.Plot x, y
    dmGraphCtrl.AutoRange()
  End If
  If Err.number <> 0 Then
    MsgBox Err.Description
  End If
End Sub
-->
</script>

3. 从使用 MFC 编写的 C++ Windows 客户端

#import "..\DMGraph\DMGraph.tlb" no_namespace raw_interfaces_only

class CDmGraphMfcClientDlg : public CDialog
{
    ... ... ...

    IDMGraphCtrlPtr m_spGraph;
};

BOOL CDmGraphMfcClientDlg::OnInitDialog()
{
    ... ... ...

    CWnd* pwndCtrl = GetDlgItem(IDC_DMGRAPHCTRL1);
    ASSERT_VALID(pwndCtrl);
    IUnknown* pUnkCtrl = pwndCtrl->GetControlUnknown(); //weak reference

    HRESULT hr;
    m_spGraph = pUnkCtrl;
    IDMGraphCollectionPtr colElements;
    
    hr = m_spGraph->get_Elements(&colElements);

    IDispatchPtr spDisp;
    IDMGraphElementPtr spElem;
    hr = colElements->Add(&spDisp);
    spElem = spDisp;
    hr = spElem->put_Name(_bstr_t("sin"));
    hr = spElem->put_PointSymbol( Dots );
    hr = spElem->put_PointSize(1);
    hr = spElem->put_PointColor( RGB(255, 0, 0) );
    
    COleSafeArray arrx, arry;
    arrx.CreateOneDim(VT_R8, 100);
    arry.CreateOneDim(VT_R8, 100);
    long i;
    for(i=0; i<100; i++)
    {
        double x, y;
        x = i/10.;
        y = sin(x);

        arrx.PutElement(&i, &x);
        arry.PutElement(&i, &y);
    }

    hr = spElem->Plot(COleVariant(arrx), COleVariant(arry));
    hr = m_spGraph->AutoRange();

    return TRUE;  // return TRUE  unless you set the focus to a control
}

关注点

  • 需要保护格式化输入数据的“sprintf”代码免受异常影响。
  • 需要处理 WM_ERASEBKGND 以避免窗口在调整大小时闪烁。
  • 来自 VBScript 客户端的 VARIANT 通常需要对包含的 safe array 进行间接引用,并对数组元素进行转换。

历史

  • 版本 5.0.0.4
    • 更新了“关于”对话框,包含开发者网站的新 URL。
  • 版本 5.0.0.3
    • 修正了注册错误(在 CLSID 键中使用错误的 LIBID)。
    • 为类型库添加了“control”标志(在 IDL 中作为属性)。这样,DMGraph 就会出现在 Visual Basic 组件控件列表 / Excel 控件工具箱中,成为一个 ActiveX。
    • 更改了 DrawGraphOffScreen 函数,使其即使在提供的 HDC 是图元文件 DC 的情况下也能正常工作。这在 COM 客户端调用 IDataObject::GetData 方法时很有用。该方法的 ATL 实现使用了图元文件 DC。
  • 版本 5.0.0.2
    • 在 DM Graph 属性页中添加了 UI,允许更改网格步长。
  • 版本 5.0.0.1
    • 修复了一个小缺陷,该缺陷在 ActiveX 未处于活动状态时(例如,当它被 C++ 资源编辑器插入到对话框中时)会在“Debug build”中引发断言。
    • 用 MFC 客户端示例代码更新了帮助文件。
  • 版本 5.0
    • 移除了 MFC 依赖;旧的 .OCX 库现在是一个使用 ATL 的 .DLL。该 DLL 只依赖于标准的 Windows 库和标准 C++ 库(msvcrt.dll),后者从 Windows 2000 开始就已包含在操作系统中。这意味着没有部署问题。
    • 公开的 COM 接口进行了重大重新设计。添加了更多接口。它们将应用于同类对象的​​方法和属性组合在一起。因此,与旧的 NTGraphCtrl.ocx 所公开的 COM 不兼容。因此,所有 GUID 和 ProgID 都是新的。
    • 新的 DLL 公开双重接口(与旧的 dispinterface 不同)。
    • 修复了打印时的 GDI 和内存泄漏。
    • 添加了新的 API,可以使用一个方法调用传递一个点数组。
    • 添加了选择性缩放(仅 X 或仅 Y 缩放)。
    • 添加了一种新的跟踪模式,该模式在工具提示中显示鼠标附近最近点的值。
    • 打印功能使用新的操作系统打印向导对话框。
    • 文档(帮助)以 CHM 格式提供。
© . All rights reserved.