• 用Chart控件绘制动态图表


    进行程序设计时,选用一个合适的ActiveX控件,有时可大大减少编程工作量。ActiveX 控件(又称OCX)基于COM技术,作为独立的软件模块,它可以在任何程序设计语言中插入使用。本文仅以VC++为例说明Chart控件的使用。

      Chart控件指Mschart.ocx(5.0版)或Mschrt20.ocx(6.0 版),是Visual Studio自带的ActiveX控件之一,其属性、事件很多,功能非常强大,可实现柱状直方图、曲线走势图、饼状比例图等,甚至可以是混合图表,可以是二维或三维图表,可以带或不带坐标系,可以自由配置各条目的颜色、字体等等。

    一 安装和使用Chart控件

      在用到Chart控件的项目中安装该控件:从Project->Add to Project->Components And Controls->Registered Active Xcontrols,选择Chart控件,则 ClassWizard会生成相应的C++类,其中类CMSChart是由CWnd派生来的,它是Chart 控件的主要类,其他的类全部是由COleDispatchDriver派生来,控制控件中的相应对象,完成各部分相关功能,如CvcAxis类是实现坐标轴相关功能的源代码。同时在项目的控件工具箱上会出现代表Chart控件的按钮,使用时把Chart控件按钮从工具箱拖到对话框中,调整大小即可。

      Chart控件至少有45个属性、9个方法、49个事件,在这里就不一一列举了。

      在设计中,我们可以在主要属性页里修改各属性的属性值:右击对话框窗口中的Chart控件,选择“Properties”菜单项,就会弹出主要属性页对话框,对其中各属性值进行设置。有些属性在主要属性页里没有列出,只能编程修改。另外要动态绘制图表,必须掌握对控件的编程控制。

      首先在对话框类中定义控件变量,以便编程时操纵控件。如对话框类定义如下:

    class CAbcDlg : public CDialog{
    public:
        CAbcDlg(CWnd* pParent = NULL);	
    //{{AFX_DATA(CAbcDlg)
        enum { IDD = IDD_ABC_DIALOG };
        CMSChart	m_Chart;
        //}}AFX_DATA
        ......
    };
    

      ActiveX控件的属性和方法在控件内部对应唯一一个整数索引值,编程时可以通过索引来设置或获取控件的属性值,也可以通过调用控件的C++类(在这里就是CMSChart)的成员函数设置或获取控件的属性值及调用控件的方法。例如:

      在CMSChart类实现中有如下代码:

    CString CMSChart::GetData(){
        CString result;
    InvokeHelper(0x9, DISPATCH_PROPERTYGET,
     VT_BSTR, (void*)&result, NULL);
        return result;
    }
    void CMSChart::SetData(LPCTSTR lpszNewValue){
        static BYTE parms[] =VTS_BSTR;
       InvokeHelper(0x9, DISPATCH_PROPERTYPUT,
         VT_EMPTY, NULL, parms,lpszNewValue);
    }
    void CMSChart::Refresh(){
    	InvokeHelper(DISPID_REFRESH, 
        DISPATCH_METHOD, VT_EMPTY, NULL, NULL);
    }
    

      这段代码表明:属性“Data”索引值为0x9,我们可以调用函数SetData对图表中某点的值进行设置。索引值为DISPID_REFRESH的方法 “Refresh”,调用它进行刷新。如:

    CString str=“34.5";
    m_Chart.SetData(str);
    m_Chart.Refresh();
    ......
    

      阅读CMSChart类的实现会发现,有些属性的值不是普通的BOOL、CString等数据类型,而是另一个控件驱动类的类变量,如:

    CVcPlot CMSChart::GetPlot(){
        LPDISPATCH pDispatch;
    InvokeHelper(0x28, DISPATCH_PROPERTYGET,
     VT_DISPATCH, (void*)&pDispatch, NULL);
        return CVcPlot(pDispatch);
    }
    

      在CVcPlot类的实现中有如下代码:

    CVcAxis CVcPlot::GetAxis
     (long axisID, const VARIANT& Index){
        LPDISPATCH pDispatch;
        static BYTE parms[] =VTS_I4 VTS_VARIANT;
    InvokeHelper(0x1f, DISPATCH_PROPERTYGET,
    VT_DISPATCH, (void*)&pDispatch, parms, axisID, &Index);
        return CVcAxis(pDispatch);
    }
    

      而CVcAxis类的实现中有如下代码:

    CVcValueScale CVcAxis::GetValueScale(){
        LPDISPATCH pDispatch;
    InvokeHelper(0x9, DISPATCH_PROPERTYGET,
     VT_DISPATCH, (void*)&pDispatch, NULL);
        return CVcValueScale(pDispatch);
    }
    

      而CVcValueScale类的实现中又有如下代码:

    void CVcValueScale::SetMaximum(double newValue){
        static BYTE parms[] =VTS_R8;
    InvokeHelper(0x3, DISPATCH_PROPERTYPUT,
     VT_EMPTY, NULL, parms,newValue);
    }
    

      这正是Chart控件的灵活性所在,根据上述代码,如下的调用:

        VARIANT var;
    m_Chart.GetPlot().GetAxis(1, var).GetValueScale()
    .SetMaximum(50.0);
    可实现把纵坐标的最大刻度设为50.0。
    

      控件触发的事件,如Click、MouseDown等,如果需要处理,可以通过ClassWizard在对话框类中定义相应的处理函数,实现相关的处理功能。

    二 动态绘制图表实例

      在一个温度采集系统中,希望把采集来的各项温度值实时显示,用Chart控件绘制曲线走势图:
    1. 各温度项以不同颜色的曲线表示;
    2. 横坐标为时间,纵坐标为温度值,均要求滚动显示;
    3. 在每次采样完成后,刷新屏幕。

      设计思路

    1. 随着时间的推移,采集来的数据不断增加,不一定在一屏中显示,所以系统打开一个实时数据库,存放采集来的实时数据。显示时,需要哪个时间段的数据,就从数据库中读取。
    2. 在对话框资源编辑时,增加水平滚动条和垂直滚动条,以便配合Chart控件进行滚动显示。
    3. 为对话框启动定时器,按采样间隔进行采样,并刷新屏幕显示。

      主要相关代码如下:

    BOOL CAbcDlg::OnInitDialog(){
        CDialog::OnInitDialog();
    pDataDB = new dbase;
    //实时数据记录库,类dbase的基类为CDaoRecordset
        pDataDB->Open(dbOpenDynaset, “select
    * from data");
        VARIANT var;
    m_Chart.GetPlot().GetAxis(1,var).GetValueScale().
      SetAuto(FALSE);//不自动标注y轴刻度
    m_Chart.GetPlot().GetAxis(1, var).GetValueScale().
      SetMaximum(37);//y轴最大刻度
    m_Chart.GetPlot().GetAxis(1, var).GetValueScale().
      SetMinimum(32);//y轴最小刻度
    m_Chart.GetPlot().GetAxis(1,var).GetValueScale().
      SetMajorDivision(5);//y轴刻度5等分
    m_Chart.GetPlot().GetAxis(1,var).GetValueScale().
      SetMinorDivision(1);//每刻度一个刻度线
    m_Chart.SetColumnCount(3); //3个温度项,3条曲线
       m_Chart.GetPlot().GetSeriesCollection().GetItem(1).
       GetPen().GetVtColor().Set(0, 0, 255);//线色
       m_Chart.GetPlot().GetSeriesCollection().GetItem(2).
        GetPen().GetVtColor().Set(255, 0, 0);
       m_Chart.GetPlot().GetSeriesCollection().GetItem(3).
        GetPen().GetVtColor().Set(0, 255, 0);
       m_Chart.GetPlot().GetSeriesCollection().
        GetItem(1).GetPen().SetWidth(2);//线宽
       m_Chart.GetPlot().GetSeriesCollection().
        GetItem(2).GetPen().SetWidth(2);
       m_Chart.GetPlot().GetSeriesCollection().
        GetItem(3).GetPen().SetWidth(2);
       m_Chart.SetRowCount(10); //一屏显示10个采样时刻
     m_Chart.GetPlot().GetAxis(0,var).GetCategoryScale().
       SetAuto(FALSE);//不自动标注x轴刻度
     m_Chart.GetPlot().GetAxis(0,var).GetCategoryScale().
       SetDivisionsPerLabel(1);//每时刻一个标注
       m_Chart.GetPlot().GetAxis(0,var).GetCategoryScale().
        SetDivisionsPerTick(1);//每时刻一个刻度线
          m_ScrLeft.SetScrollRange(0,45);
         //垂直滚动条可滚动范围(温度值范围0-50,
          每滚动1度,一屏显示5度)
        m_ScrLeft.SetScrollPos(45-32);//垂直滚动条的当前位置
        m_ScrBottom.SetScrollRange(0, 0);//水平滚动条的可滚动范围
        m_ScrBottom.SetScrollPos(0);//水平滚动条的当前位置
        SetTimer(23, 300000, NULL);//启动定时器,定时间隔5分钟
        Sample();//调用采样函数进行第一次采样,并把数据记录入库
         return TRUE;  
    }
    void CAbcDlg::OnTimer(UINT nIDEvent) {
        Sample();//采样,并把数据记录入库
        if (pDataDB->GetRecordCount()>10)
      theApp.nBottomRange = pDataDB->GetRecordCount()-10;
        else
    theApp.nBottomRange = 0; 
    //用全局变量保存水平滚动条的范围值
    m_ScrBottom.SetScrollRange(0,theApp.nBottomRange);
        theApp.nBottomPos = theApp.nBottomRange;
       m_ScrBottom.SetScrollPos(theApp.nBottomPos);
        //修正水平滚动条的显示
        DrawPic();//调用函数,刷新曲线显示
    	
        CDialog::OnTimer(nIDEvent);
    }
    void CAbcDlg::DrawPic() {
    char s[10];
        UINT row = 1;
        pDataDB->MoveFirst();
    pDataDB->Move(theApp.nBottomPos);
    //只从数据库中取某时间段的数据进行显示
        while ((!pDataDB->IsEOF()) && (row <= 10)){
        m_Chart.SetRow(row);
    m_Chart.SetRowLabel((LPCTSTR)pDataDB
    ->m_date_time.Format(“%H:%M"));
    //以采样时刻做x轴的标注
        m_Chart.SetColumn(1);
        sprintf(s, “%6.2f", pDataDB->m_No1);
        m_Chart.SetData((LPCSTR)s);
        m_Chart.SetColumn(2);
        sprintf(s, “%6.2f", pDataDB->m_No2);
        m_Chart.SetData((LPCSTR)s);
        m_Chart.SetColumn(3);
        sprintf(s, “%6.2f", pDataDB->m_No3);
        m_Chart.SetData((LPCSTR)s);
        pDataDB->MoveNext();
        row++;
        }
        while ((row <= 10)){
        m_Chart.SetRow(row);
        m_Chart.SetRowLabel((LPCTSTR)“");
    m_Chart.GetDataGrid().SetData(row, 1, 0, 1);
    //采样数据不足10个点, 对应的位置不显示
        m_Chart.GetDataGrid().SetData(row, 2, 0, 1);
        m_Chart.GetDataGrid().SetData(row, 3, 0, 1);
        row++;
        }
        m_Chart.Refresh();
    }
    void CAbcDlg::OnHScroll(UINT nSBCode, 
        UINT nPos, CScrollBar* pScrollBar) {
        if (pDataDB->GetRecordCount()>10)
      theApp.nBottomRange = pDataDB->GetRecordCount()-10;
        else
        theApp.nBottomRange = 0;
     m_ScrBottom.SetScrollRange(0, theApp.nBottomRange);
        switch (nSBCode){
        case SB_LINERIGHT:
       if (theApp.nBottomPos < theApp.nBottomRange){
       theApp.nBottomPos = theApp.nBottomPos + 1;
       m_ScrBottom.SetScrollPos(theApp.nBottomPos);
        DrawPic();
        }
        break;
        case SB_LINELEFT:
        if (theApp.nBottomPos > 0){
       theApp.nBottomPos = theApp.nBottomPos - 1;
        m_ScrBottom.SetScrollPos(theApp.nBottomPos);
        DrawPic();
        }
        break;
        }
        CDialog::OnHScroll(nSBCode, nPos, pScrollBar);
    }
    void CAbcDlg::OnVScroll(UINT nSBCode, 
        UINT nPos, CScrollBar* pScrollBar) {
        VARIANT var;
        double max1,min1,f;
        switch (nSBCode){
        case SB_LINEDOWN:
    f = m_Chart.GetPlot().GetAxis(1, var).
      GetValueScale().GetMinimum() - 1;
        if (f>=0) {//最小刻度大于等于0, 则可以滚动
    m_Chart.GetPlot().GetAxis(1, var).GetValueScale().
      SetMinimum(f);
    f = m_Chart.GetPlot().GetAxis
     (1, var).GetValueScale().GetMaximum() - 1;
    m_Chart.GetPlot().GetAxis(1, var).GetValueScale().
    SetMaximum(f);
      pScrollBar->SetScrollPos(pScrollBar->GetScrollPos() + 1);
        m_Chart.Refresh();
        }
        break;
        case SB_LINEUP:
    f = m_Chart.GetPlot().GetAxis(1, var).
    GetValueScale().GetMaximum() + 1;
        if (f <= 50) {//最大刻度小于等于50, 则可以滚动
    m_Chart.GetPlot().GetAxis
    (1, var).GetValueScale().SetMaximum(f);
    f = m_Chart.GetPlot().GetAxis(1, var).
    GetValueScale().GetMinimum() + 1;
    m_Chart.GetPlot().GetAxis(1, var).GetValueScale().
      SetMinimum(f);
      pScrollBar->SetScrollPos(pScrollBar->GetScrollPos() - 1);
        m_Chart.Refresh();
        }
        break;
        }
        CDialog::OnVScroll(nSBCode, nPos, pScrollBar);
    }
    

      特别注意,程序中用到的关于控件的类,如CVcAxis等,需要在AbcDlg.cpp文件的开始处说明:#include “VcAxis.h"。

      限于篇幅,文中仅仅是一个简单示例的部分代码。在实际应用中,一般会有更多的需求,比如:对坐标轴进行缩放显示;采样有可能得不到正确的采样值时曲线显示不连续等等,这时需要根据需求编写相应代码。

  • 相关阅读:
    再也不用为word 中表达式的上标和下标发愁了
    创建链接
    ps钢笔工具隐藏的知识。
    学Ps个人遇到的小细节
    新手琢磨ps,学问深着呢。。
    数据库2012终于知道数据库攻击注入参数
    想脱离鼠标,不想要鼠标就只想用键盘完成所有编程,你说可能吗?
    vs2013中的快捷键
    如何在C/C++中动态分配二维数组【转载】
    转载:C++的那些事:表达式与语句
  • 原文地址:https://www.cnblogs.com/lidabo/p/3470078.html
Copyright © 2020-2023  润新知