以文本方式查看主题

-  中文XML论坛 - 专业的XML技术讨论区  (http://bbs.xml.org.cn/index.asp)
--  『 C/C++编程思想 』  (http://bbs.xml.org.cn/list.asp?boardid=61)
----  工控软件图形界面-控件实现(圆形仪表控件)  (http://bbs.xml.org.cn/dispbbs.asp?boardid=61&rootid=&id=61475)


--  作者:卷积内核
--  发布时间:4/17/2008 1:57:00 PM

--  工控软件图形界面-控件实现(圆形仪表控件)
关键字 仪表 仿真 工控 组态

介绍
在工业控制系统开发过程中,图形显示方面占有着很重要的作用。比起很多专用的组态软件,他们有着强大的在图形系统,能够组态出来非常漂亮的系统。现在的很多的工业图形开发包都需要支付费用,很多漂亮的控件比如仪表等只能看图兴叹了。前些天一个朋友做一个泵站的监控系统,由于缺少相关的控件,在研究了该类控件的编程方法上,借鉴网络上的一些编程资料,完成了一些可用于工业控制系统开发使用的控件。

正文
国内现在的工业组态软件很多都是采用组态软件,比如组态王,IFIX,图王等。而在提供控件开发包方面也有很多厂商提供,比如世纪飞扬的控件开发包,图王等。由于很多组态系统属于项目授权形式,针对每个项目进行单独授权。而在中小型公司系统实施过程中这个是一个不小的压力,所以很多公司都是自己编写适合自己公司产品的组态软件。而在编写工业控制软件系统中,相关的控件是必不可少的,比如使用仪表仿真比直接数字显示更能够逼真的反应现在情况的模拟。
    现在网络上面常用的编写此类控件的方法是采用双缓冲的技术。双缓冲的原理可以这样形象的理解:把电脑屏幕看作一块黑板。首先我们在内存环境中建立一个“虚拟“的黑板,然后在这块黑板上绘制复杂的图形,等图形全部绘制完毕的时候,再一次性的把内存中绘制好的图形“拷贝”到另一块黑板(屏幕)上。采取这种方法可以提高绘图速度,极大的改善绘图效果。
    我们这里建立一个新类CDiscMeter集成CStatic,然后映射WM_PAINT消息,然后在OnPaint中间实现图形的绘制工作。

void CDiscMeter::OnPaint()
{
    CPaintDC dc(this); // device context for painting
    
    // 获得控件区域
    GetClientRect (&m_rectCtrl);

    CDiscMemDC memDC(&dc, &m_rectCtrl);

    //绘制仪表盘
    if (m_dcMeterPlate.GetSafeHdc() == NULL || (m_bitmapMeterPlate.m_hObject == NULL))
    {
        m_dcMeterPlate.CreateCompatibleDC(&dc);
        m_bitmapMeterPlate.CreateCompatibleBitmap(&dc, m_rectCtrl.Width(),                                                                                     m_rectCtrl.Height()) ;
        m_pbitmapOldMeterPlate = m_dcMeterPlate.SelectObject(&m_bitmapMeterPlate) ;
        DrawMeterBackground(&m_dcMeterPlate, m_rectCtrl);

    }
    memDC.BitBlt(0, 0, m_rectCtrl.Width(), m_rectCtrl.Height(),
                       &m_dcMeterPlate, 0, 0, SRCCOPY);

    DrawNeedle(&memDC);
    DrawValue(&memDC);
}

由于我们的背景不是实时变化的,我们在第一次运行的时候将背景的绘制完成,在以后每次调用Invalidate的时候就直接调用CDC的BitBtn方法完成背景的重绘工作。
    在绘制背景程序中,我们根据弧度等计算方法,动态计算仪表的外各个背景的部位的位置,然后调用CDC类对象的一系列方法完成图形的绘制
//绘制仪表背景
void CDiscMeter::DrawMeterBackground(CDC *pDC, CRect &rect)
{
    CPen   m_penMeter, *pOldPen;
    CBrush m_brushBack, *pOldBrush;

    pDC->SetBkColor(m_BackColor);
    m_brushBack.CreateSolidBrush(m_BackColor);
    pOldBrush = (CBrush *)pDC->SelectObject(&m_brushBack);    
    pDC->FillRect(rect, &m_brushBack);   //绘制背景
    pDC->Rectangle(rect);                //绘制一个边框
    pDC->SelectObject(pOldBrush);
    m_brushBack.DeleteObject();

    m_penMeter.CreatePen(PS_SOLID, 2, RGB( 0, 0, 0));
    pOldPen = (CPen *)pDC->SelectObject(&m_penMeter);
    pDC->SetTextColor(RGB( 0, 0, 0));
    pDC->SetBkMode(TRANSPARENT);

    int nTmpLong = __min(rect.Width(), rect.Height());
    m_ptMeterCenter.x = nTmpLong / 2; //点中心x坐标
    m_ptMeterCenter.y = nTmpLong / 2; //点中心y坐标
    m_nRadiusFrame = nTmpLong / 2 - 3;

    //绘制仪表圆盘
    CRect rectRound(m_ptMeterCenter.x - m_nRadiusFrame,
                    m_ptMeterCenter.y + m_nRadiusFrame,
                    m_ptMeterCenter.x + m_nRadiusFrame,
                    m_ptMeterCenter.y - m_nRadiusFrame);
    pDC->Ellipse(rectRound);                         

    //画书写当前值的内框
    CPen pInnerFramePen, *pOldInnerFramePen;
    pInnerFramePen.CreatePen(PS_SOLID, 1, RGB( 0, 0, 0));
    pOldInnerFramePen = (CPen *)pDC->SelectObject(&pInnerFramePen);
    CRect rectInnerFrame(m_ptMeterCenter.x - m_nRadiusFrame * 9 / 20,
                         m_ptMeterCenter.y + m_nRadiusFrame * 6 / 10,
                         m_ptMeterCenter.x - m_nRadiusFrame * 9 / 20 + m_nRadiusFrame * 9 / 10,
                         m_ptMeterCenter.y + m_nRadiusFrame * 6 / 10 + m_nRadiusFrame * 5 / 20);
    m_rectInnerFrame = rectInnerFrame;
    pDC->FillSolidRect(rectInnerFrame, RGB( 230, 232, 232));  
    DrawRectangle(pDC, rectInnerFrame, RGB( 0, 0, 0));
    rectInnerFrame.left ++;
    rectInnerFrame.top ++;
    rectInnerFrame.bottom --;
    rectInnerFrame.right --;
    DrawRectangle(pDC, rectInnerFrame, RGB( 255, 255, 255));
    pDC->SelectObject(pOldInnerFramePen);
    pInnerFramePen.DeleteObject();

    //画外圈圆弧
    int nTmpRadius = m_nRadiusFrame - 4;
    CPoint ptBoundary, ptStart, ptEnd;
    ptBoundary.x = int(sin(PI / 3) * nTmpRadius);
    ptBoundary.y = int(cos(PI / 3) * nTmpRadius);
    ptStart.x = m_ptMeterCenter.x + ptBoundary.x;
    ptStart.y = m_ptMeterCenter.y + ptBoundary.y;
    ptEnd.x = m_ptMeterCenter.x - ptBoundary.x;
    ptEnd.y = m_ptMeterCenter.y + ptBoundary.y;
    CRect arcAngle(m_ptMeterCenter.x - nTmpRadius,
                   m_ptMeterCenter.y - nTmpRadius,
                   m_ptMeterCenter.x + nTmpRadius,
                   m_ptMeterCenter.y + nTmpRadius);
    pDC->Arc(&arcAngle, ptStart, ptEnd);

    //画刻度
    int nTicks = m_nTicks;
    int nSubTicks = m_nSubTicks;
    char strFigure[MAXNAMELENGTH + 1];
    const int nSidePos = 40;
    memset(strFigure, 0, sizeof(strFigure));
    double dMaxAngle = double(240.00f / nTicks);      //每个大格的角度
    double dMinAnble = dMaxAngle / nSubTicks;         //每个小格的角度
    
    for (int i=0; i    {
        CPoint ptStartTick, ptEndTick;
        ptStartTick.x = int(m_ptMeterCenter.x + (m_nRadiusFrame *
                            cos((dMaxAngle * (nTicks - i) - 30) * PI / 180.00f)));
        ptStartTick.y = int(m_ptMeterCenter.y - (m_nRadiusFrame *
                            sin((dMaxAngle * (nTicks - i) - 30) * PI / 180.00f)));
        ptEndTick.x = int(m_ptMeterCenter.x + ((m_nRadiusFrame - 10) *
                          cos((dMaxAngle * (nTicks - i) - 30) * PI / 180.00f)));
        ptEndTick.y = int(m_ptMeterCenter.y - ((m_nRadiusFrame - 10) *
                          sin((dMaxAngle * (nTicks - i) - 30) * PI / 180.00f)));
        pDC->MoveTo(ptStartTick);
        pDC->LineTo(ptEndTick);

        sprintf(strFigure, "%.1f", (m_dMaxValue - m_dMinValue) * i / nTicks);
        if (dMaxAngle * (nTicks - i) - 30 < 60)
        {
            pDC->TextOut(ptEndTick.x - nSidePos, ptEndTick.y, strFigure);
        }
        else if (dMaxAngle * (nTicks - i) - 30 <= 90)
        {
            pDC->TextOut(ptEndTick.x - nSidePos / 2, ptEndTick.y + 3, strFigure);
        }
        else if (dMaxAngle * (nTicks - i) - 30 < 140)
        {
            pDC->TextOut(ptEndTick.x - nSidePos / 3, ptEndTick.y, strFigure);
        }
        else
        {
            pDC->TextOut(ptEndTick.x - nSidePos / 10, ptEndTick.y, strFigure);
        }
    }

    for (i=0; i    {
        CPoint ptSubStartTick, ptSubEndTick;
        ptSubStartTick.x = int(m_ptMeterCenter.x + (m_nRadiusFrame *
                            cos((dMinAnble * (nTicks * nSubTicks - i) - 30) * PI / 180.00f)));
        ptSubStartTick.y = int(m_ptMeterCenter.y - (m_nRadiusFrame *
                            sin((dMinAnble * (nTicks * nSubTicks - i) - 30) * PI / 180.00f)));
        ptSubEndTick.x = int(m_ptMeterCenter.x + ((m_nRadiusFrame - 6) *
                            cos((dMinAnble * (nTicks * nSubTicks - i) - 30) * PI / 180.00f)));
        ptSubEndTick.y = int(m_ptMeterCenter.y - ((m_nRadiusFrame - 6) *
                            sin((dMinAnble * (nTicks * nSubTicks - i) - 30) * PI / 180.00f)));
        pDC->MoveTo(ptSubStartTick);
        pDC->LineTo(ptSubEndTick);
    }

    //文本格式的初始化
    CFont pUnitFont, *pOldFont;
    LOGFONT lf;
    lf.lfEscapement = 0;
    lf.lfItalic = NULL;
    lf.lfUnderline = NULL;
    lf.lfStrikeOut = NULL;
    lf.lfCharSet = DEFAULT_CHARSET;
    lf.lfHeight = m_nRadiusFrame / 5;
    strcpy(lf.lfFaceName, "Impact");
    pUnitFont.CreateFontIndirect(&lf);
    pOldFont = (CFont *)pDC->SelectObject(&pUnitFont);
    CRect rectUnits(int(m_ptMeterCenter.x - m_nRadiusFrame * 0.18f),
                    int(m_ptMeterCenter.y + m_nRadiusFrame * 0.25f),
                    int(m_ptMeterCenter.x - m_nRadiusFrame * 0.18f + m_nRadiusFrame *0.4f),
                    int(m_ptMeterCenter.y + m_nRadiusFrame * 0.25f + m_nRadiusFrame * 6 / 20));
    pDC->DrawText(m_strUnits, &rectUnits, DT_CENTER | DT_SINGLELINE | DT_VCENTER);
    pDC->SelectObject(pOldFont);
    pUnitFont.DeleteObject();

    pDC->SelectObject(pOldPen);
    m_penMeter.DeleteObject();
}

    由于控件进行数据显示的时候调用了CStatic类的Invalidate方法触发WM_PAINT消息,在完成背景的绘制后,我们需要完成控件的指针和实时值的显示,这样,我们的控件就完成了。
//绘制仪表指针
void CDiscMeter::DrawNeedle(CDC *pDC)
{
    CBrush pCenterBrush, *pOldBrush;

    if (m_dCurrentValue < m_dMinValue)
    {
        m_dCurrentValue = m_dMinValue;
    }
    else if (m_dCurrentValue > m_dMaxValue)
    {
        m_dCurrentValue = m_dMaxValue;
    }

    double dAngle = double(240.00f * ((m_dCurrentValue - m_dMinValue) /
        (m_dMaxValue - m_dMinValue)));
    int nTmpRadius1 = int(m_nRadiusFrame / 1.4f);
    int nTmpRadius2 = m_nRadiusFrame / 6;

    CRgn pRgn;
    POINT ptAngle[3];
    ptAngle[0].x = long(m_ptMeterCenter.x + nTmpRadius2 *
                         cos((210 - dAngle - 90) * PI / 180.00f));
    ptAngle[0].y = long(m_ptMeterCenter.y - nTmpRadius2 *
                         sin((210 - dAngle - 90) * PI / 180.00f));
    ptAngle[1].x = long(m_ptMeterCenter.x + nTmpRadius2 *
                         cos((210 - dAngle + 90) * PI / 180.00f));
    ptAngle[1].y = long(m_ptMeterCenter.y - nTmpRadius2 *
                         sin((210 - dAngle + 90) * PI / 180.00f));
    ptAngle[2].x = long(m_ptMeterCenter.x + nTmpRadius1 *
                         cos((210 - dAngle) * PI / 180.00f));
    ptAngle[2].y = long(m_ptMeterCenter.y - nTmpRadius1 *
                         sin((210 - dAngle) * PI / 180.00f));
    pRgn.CreatePolygonRgn(ptAngle, 3, ALTERNATE);
    pDC->FillRgn(&pRgn, &CBrush(RGB( 0, 0, 0)));

    pCenterBrush.CreateSolidBrush(RGB( 255, 0, 0));
    pOldBrush = (CBrush *)pDC->SelectObject(&pCenterBrush);    
    CRect rectCenter(m_ptMeterCenter.x - nTmpRadius2,
                     m_ptMeterCenter.y - nTmpRadius2,
                     m_ptMeterCenter.x + nTmpRadius2,
                     m_ptMeterCenter.y + nTmpRadius2);
    pDC->Ellipse(rectCenter);
    pDC->SelectObject(pOldBrush);
    pCenterBrush.DeleteObject();
}

//绘制仪表实时值
void CDiscMeter::DrawValue(CDC *pDC)
{
    char strCurrentValue[10];
    memset(strCurrentValue, 0, sizeof(strCurrentValue));

    sprintf(strCurrentValue, "%.2f", m_dCurrentValue);

    CFont pUnitFont, *pOldFont;
    LOGFONT lf;
    lf.lfEscapement = 0;
    lf.lfItalic = NULL;
    lf.lfUnderline = NULL;
    lf.lfStrikeOut = NULL;
    lf.lfCharSet = DEFAULT_CHARSET;
    lf.lfHeight = m_nRadiusFrame / 6;
    strcpy(lf.lfFaceName, "Arial");
    pUnitFont.CreateFontIndirect(&lf);
    pOldFont = (CFont *)pDC->SelectObject(&pUnitFont);    
    pDC->SetBkMode(TRANSPARENT);
    pDC->DrawText(strCurrentValue, m_rectInnerFrame, DT_CENTER | DT_SINGLELINE | DT_VCENTER);
    
    pDC->SelectObject(pOldFont);
    pUnitFont.DeleteObject();
}


然后我们定义一些控件的接口来完成控件的参数的设置。
至此,这个仪表控件就绘制完成了,我们通过WIZARD定义一个静态控件的变量,运行就能够看到图形的效果了。效果见下:
按此在新窗口浏览图片

利用双缓冲完成这类控件的绘制其实原理很简单,关键是如何在绘制过程中进行各种不同控件的绘制方法等。这里粗略的绘制了一个圆形仪表的实现,希望能给各位抛砖引玉的作用。


--  作者:卷积内核
--  发布时间:4/17/2008 1:59:00 PM

--  
工控软件图形界面-控件实现(圆形仪表控件二)

关键字 仪表 仿真 工控 组态
原作者姓名 戚高
文章原始出处 原创

介绍
在工业控制系统开发过程中,图形显示方面占有着很重要的作用。比起很多专用的组态软件,他们有着强大的在图形系统,能够组态出来非常漂亮的系统。现在的很多的工业图形开发包都需要支付费用,很多漂亮的控件比如仪表等只能看图兴叹了。前些天一个朋友做一个泵站的监控系统,由于缺少相关的控件,在研究了该类控件的编程方法上,借鉴网络上的一些编程资料,完成了一些可用于工业控制系统开发使用的控件。

正文
在上一篇文章 中我们采用双缓冲绘图原理的方法,并结合网络上面的一些圆盘仪表的绘制技术完成了一个圆盘仪表的绘制。圆盘仪表绘制原理综合起来基本差不多,可以分为:绘制背景,绘制指针,绘制实时值等。这里,我们改变背景的显示式样,改变指针的显示式样和文字的显示式样,能够做出来更多类型的圆盘仪表。如果在我们的监控程序中使用这样的多类型的仪表,也会使得在软件的人机接口方面更丰富很多。中我们采用双缓冲绘图原理的方法,并结合网络上面的一些圆盘仪表的绘制技术完成了一个圆盘仪表的绘制。圆盘仪表绘制原理综合起来基本差不多,可以分为:绘制背景,绘制指针,绘制实时值等。这里,我们改变背景的显示式样,改变指针的显示式样和文字的显示式样,能够做出来更多类型的圆盘仪表。如果在我们的监控程序中使用这样的多类型的仪表,也会使得在软件的人机接口方面更丰富很多。
    实现方法还是按照上一篇文章的方法,建立一个继承CStatic的基类,并映射WM_PAINT消息完成控件的双缓冲绘制过程。在绘制背景的过程中,我们可以采用一些技术使得背景更加的好看。比如在显示字体的时候我们可以利用在两个叠加的区域用两种不同的颜色显示同一个文字就能够达到叠加的效果。
    比如我们在一个测试工程中间输入下面代码:
    CFont pUnitFont, *pOldFont;
    LOGFONT lf;
    lf.lfEscapement = 0;
    lf.lfItalic = NULL;
    lf.lfUnderline = NULL;
    lf.lfStrikeOut = NULL;
    lf.lfCharSet = DEFAULT_CHARSET;
    lf.lfHeight = 45;
    strcpy(lf.lfFaceName, "隶书");
    pUnitFont.CreateFontIndirect(&lf);
    pOldFont = (CFont *)dc.SelectObject(&pUnitFont);    
    dc.SetBkMode(TRANSPARENT);
    dc.SetTextColor(RGB(0, 0, 0));
    CRect rectText(10 + 3, 10 + 3, 450 + 3, 200 + 3);
    dc.DrawText("这个是一个测试实例", rectText, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
    dc.SetTextColor(RGB(255, 0, 0));
    rectText = CRect(10, 10, 450, 200);
    dc.DrawText("这个是一个测试实例", rectText, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
    dc.SelectObject(pOldFont);
    pUnitFont.DeleteObject();
例子的演示效果可以见下:
按此在新窗口浏览图片

我们可以看到下面的例子,这样比直接显示一个简单的文字看起来效果好多了,在这里我们在输出仪表圆盘刻度,仪表显示数据名称和显示实时值的时候采用这种方法增加控件的美观性。
    首先在调用刷新的时候由于背景的绘制是保持不变的,不同于指针和实时值的绘制,要根据值的变化动态绘制,所以在调用背景的绘制的时候我们可以进行一次绘制,每次调用刷新背景的时候将内存中的绘制直接贴到界面上。
//绘制仪表背景
void CRoundMeter::DrawMeterBackground(CDC *pDC, CRect &rect)
{
    CPen   pPenDeep, pPenThin, *pOldPen;
    CBrush m_brushBack, pBackBrush, *pOldBrush;

    pDC->SetBkColor(m_BackColor);
    m_brushBack.CreateSolidBrush(m_BackColor);
    pOldBrush = (CBrush *)pDC->SelectObject(&m_brushBack);    
    pDC->FillRect(rect, &m_brushBack);   //绘制背景
    pDC->Rectangle(rect);                //绘制一个边框
    pDC->SelectObject(pOldBrush);
    m_brushBack.DeleteObject();

    pPenDeep.CreatePen(PS_SOLID, 2, RGB( 0, 0, 0));
    pOldPen = (CPen *)pDC->SelectObject(&pPenDeep);
    pDC->SetTextColor(RGB( 0, 0, 0));
    pDC->SetBkMode(TRANSPARENT);

    m_ptMeterCenter = rect.CenterPoint(); //点中心坐标
    int nRadius = 0;

    if (rect.Width() <= rect.Height())
    {
        nRadius = rect.Width() - 5;
    }
    else
    {
        nRadius = rect.Height() - 5;
    }
    m_nRadiusFrame = nRadius;

    //绘制仪表圆盘
    m_brushBack.CreateSolidBrush(RGB(182, 182, 182));
    pOldBrush = (CBrush *)pDC->SelectObject(&m_brushBack);    

    CRect rectRound(m_ptMeterCenter.x - nRadius / 2,
                    m_ptMeterCenter.y - nRadius / 2,
                    m_ptMeterCenter.x + nRadius / 2,
                    m_ptMeterCenter.y + nRadius / 2);
    pDC->Ellipse(rectRound);  
    pDC->SelectObject(pOldPen);

    pPenThin.CreatePen(PS_SOLID, 1, RGB( 0, 0, 0));
    pOldPen = (CPen *)pDC->SelectObject(&pPenThin);
    rectRound.SetRect(m_ptMeterCenter.x - nRadius / 2 + nRadius / 50,
                      m_ptMeterCenter.y - nRadius / 2 + nRadius / 50,
                      m_ptMeterCenter.x + nRadius / 2 - nRadius / 50,
                      m_ptMeterCenter.y + nRadius / 2 - nRadius / 50);
    pDC->Ellipse(rectRound);
    pDC->SelectObject(pOldPen);
    pPenThin.DeleteObject();

    pOldPen = (CPen *)pDC->SelectObject(&pPenDeep);
    rectRound.SetRect(m_ptMeterCenter.x - nRadius / 2 + nRadius / 16,
                      m_ptMeterCenter.y - nRadius / 2 + nRadius / 16,
                      m_ptMeterCenter.x + nRadius / 2 - nRadius / 16,
                      m_ptMeterCenter.y + nRadius / 2 - nRadius / 16);
    pDC->Ellipse(rectRound);
    pDC->SelectObject(pOldPen);

    pDC->SelectObject(pOldBrush);
    m_brushBack.DeleteObject();
    pBackBrush.CreateSolidBrush(RGB( 0, 0, 0));
    pOldBrush = pDC->SelectObject(&pBackBrush );
    pOldPen = (CPen *)pDC->SelectObject(&pPenThin);
    rectRound.SetRect(m_ptMeterCenter.x - nRadius / 15,
                      m_ptMeterCenter.y - nRadius / 15,
                      m_ptMeterCenter.x + nRadius / 15,
                      m_ptMeterCenter.y + nRadius / 15);
    pDC->Ellipse(rectRound);
    pDC->SelectObject(pOldPen);
    pDC->SelectObject(pOldBrush);

    m_brushBack.DeleteObject();
    m_brushBack.CreateSolidBrush(m_BackColor);
    pOldBrush = pDC->SelectObject(&m_brushBack);
    rectRound.SetRect(m_ptMeterCenter.x - nRadius / 15 + nRadius / 50,
                      m_ptMeterCenter.y - nRadius / 15 + nRadius / 50,
                      m_ptMeterCenter.x + nRadius / 15 - nRadius / 50,
                      m_ptMeterCenter.y + nRadius / 15 - nRadius / 50);
    pDC->Ellipse(rectRound);
    pDC->SelectObject(pOldPen);
    pDC->SelectObject(pOldBrush);    

    //画刻度
    int nTicks = m_nTicks;
    int nSubTicks = m_nSubTicks;
    char strFigure[MAXNAMELENGTH + 1];
    const int nSidePos = 40;
    memset(strFigure, 0, sizeof(strFigure));
    double dRadius = fabs(nRadius / 2 - nRadius / 16);
    double dWidth = fabs(nRadius / 15);
    double dMaxAngle = double(300.00f / nTicks);      //每个大格的角度
    double dMinAnble = dMaxAngle / nSubTicks;         //每个小格的角度

    pPenDeep.DeleteObject();
    pPenDeep.CreatePen(PS_SOLID, 2, RGB( 0, 0, 0));
    pOldPen = (CPen *)pDC->SelectObject(&pPenDeep);
    pDC->SetTextColor(RGB( 0, 0, 255));
    pDC->SetBkMode(TRANSPARENT);

    CFont fDrawFont;
    fDrawFont.CreateFont(15, 0, 0, 0, 20, FALSE, FALSE, ANSI_CHARSET,
                      CLIP_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, PROOF_QUALITY,
                      DEFAULT_PITCH, FF_DONTCARE, "Arail");
    CFont *pOldFont = (CFont *)pDC->SelectObject(&fDrawFont);

    for (int i=0; i    {
        CPoint ptStartTick, ptEndTick;
        double dDrawAngle = (i * dMaxAngle + 30) * PI / 180;
        ptStartTick.x = int(m_ptMeterCenter.x - dRadius * sin(dDrawAngle));
        ptStartTick.y = int(m_ptMeterCenter.y + dRadius * cos(dDrawAngle));
        ptEndTick.x = int(m_ptMeterCenter.x - dRadius * sin(dDrawAngle) + dWidth * sin(dDrawAngle));
        ptEndTick.y = int(m_ptMeterCenter.y + dRadius * cos(dDrawAngle) - dWidth * cos(dDrawAngle));
        pDC->MoveTo(ptStartTick);
        pDC->LineTo(ptEndTick);

        sprintf(strFigure, "%.2f", (m_dMaxValue - m_dMinValue) * i / nTicks);
        if (dMaxAngle * (nTicks - i) - 30 < 60)
        {
            pDC->SetTextColor(RGB(255, 255, 255));
            pDC->TextOut(ptEndTick.x - nSidePos + 1, ptEndTick.y + 1, strFigure);
            pDC->SetTextColor(RGB(0, 0, 255));
            pDC->TextOut(ptEndTick.x - nSidePos, ptEndTick.y, strFigure);
        }
        else if (dMaxAngle * (nTicks - i) - 30 <= 90)
        {
            pDC->SetTextColor(RGB(255, 255, 255));
            pDC->TextOut(ptEndTick.x - nSidePos / 2 + 1, ptEndTick.y + 3 + 1, strFigure);
            pDC->SetTextColor(RGB(0, 0, 255));
            pDC->TextOut(ptEndTick.x - nSidePos / 2, ptEndTick.y + 3, strFigure);
        }
        else if (dMaxAngle * (nTicks - i) - 30 < 140)
        {
            pDC->SetTextColor(RGB(255, 255, 255));
            pDC->TextOut(ptEndTick.x - nSidePos / 3 + 1, ptEndTick.y + 1, strFigure);
            pDC->SetTextColor(RGB(0, 0, 255));
            pDC->TextOut(ptEndTick.x - nSidePos / 3, ptEndTick.y, strFigure);
        }
        else
        {
            pDC->SetTextColor(RGB(255, 255, 255));
            pDC->TextOut(ptEndTick.x - nSidePos / 10 + 1, ptEndTick.y + 1, strFigure);
            pDC->SetTextColor(RGB(0, 0, 255));
            pDC->TextOut(ptEndTick.x - nSidePos / 10, ptEndTick.y, strFigure);
        }
    }

    pDC->SelectObject(pOldFont);
    fDrawFont.DeleteObject();
    pDC->SelectObject(pOldPen);
    pPenThin.DeleteObject();
    pPenThin.CreatePen(PS_SOLID, 1, RGB( 0, 0, 0));
    pOldPen = (CPen *)pDC->SelectObject(&pPenThin);
    dWidth = fabs(nRadius / 20);

    for (i=0; i    {
        for (int j=0; j        {
            CPoint ptSubStartTick, ptSubEndTick;
            double dDrawAngle = ((i * dMaxAngle + 30) + (j * dMinAnble)) * PI / 180;
            ptSubStartTick.x = int(m_ptMeterCenter.x - dRadius * sin(dDrawAngle));
            ptSubStartTick.y = int(m_ptMeterCenter.y + dRadius * cos(dDrawAngle));
            ptSubEndTick.x = int(m_ptMeterCenter.x - dRadius * sin(dDrawAngle) + dWidth * sin(dDrawAngle));
            ptSubEndTick.y = int(m_ptMeterCenter.y + dRadius * cos(dDrawAngle) - dWidth * cos(dDrawAngle));
            pDC->MoveTo(ptSubStartTick);
            pDC->LineTo(ptSubEndTick);
        }
    }

    pDC->SelectObject(pOldPen);

    //文本格式的初始化
    int nRadiusFrame = m_nRadiusFrame / 2;
    CFont pUnitFont;
    LOGFONT lf;
    lf.lfEscapement = 0;
    lf.lfItalic = NULL;
    lf.lfUnderline = NULL;
    lf.lfStrikeOut = NULL;
    lf.lfCharSet = DEFAULT_CHARSET;
    lf.lfHeight = nRadiusFrame / 4;
    strcpy(lf.lfFaceName, "隶书");
    pUnitFont.CreateFontIndirect(&lf);
    pOldFont = (CFont *)pDC->SelectObject(&pUnitFont);
    pDC->SetTextColor(RGB(255, 255, 255));
    CRect rectUnits(int(m_ptMeterCenter.x - nRadiusFrame * 0.30f + 2),
                    int(m_ptMeterCenter.y + nRadiusFrame * 0.10f + 2),
                    int(m_ptMeterCenter.x + nRadiusFrame * 0.30f + 2),
                    int(m_ptMeterCenter.y + nRadiusFrame * 0.50f + 2));
    pDC->DrawText(m_strUnits, &rectUnits, DT_CENTER | DT_SINGLELINE | DT_VCENTER);
    pDC->SetTextColor(RGB(0, 0, 255));
    rectUnits.SetRect(int(m_ptMeterCenter.x - nRadiusFrame * 0.30f),
                      int(m_ptMeterCenter.y + nRadiusFrame * 0.10f),
                      int(m_ptMeterCenter.x + nRadiusFrame * 0.30f),
                      int(m_ptMeterCenter.y + nRadiusFrame * 0.50f));
    pDC->DrawText(m_strUnits, &rectUnits, DT_CENTER | DT_SINGLELINE | DT_VCENTER);
    pDC->SelectObject(pOldFont);
    pUnitFont.DeleteObject();

    pPenDeep.DeleteObject();
    pPenThin.DeleteObject();
    pBackBrush.DeleteObject();
    m_brushBack.DeleteObject();
}

绘制仪表指针:
void CRoundMeter::DrawNeedle(CDC *pDC, CRect &rect)
{
    CRect rectRound;
    CPen pPenThin, *pOldPen;
    CBrush pNeedleBrush, *pOldBrush;
    pNeedleBrush.CreateSolidBrush(m_NeedleColor);
    pOldBrush = (CBrush *)pDC->SelectObject(&pNeedleBrush);

    double dRadius = fabs(m_nRadiusFrame / 2 - m_nRadiusFrame / 16) - fabs(m_nRadiusFrame / 15);
    if (m_dCurrentValue < m_dMinValue)
    {
        m_dCurrentValue = m_dMinValue;
    }
    else if (m_dCurrentValue > m_dMaxValue)
    {
        m_dCurrentValue = m_dMaxValue;
    }

    double dAngle = 300 * (m_dCurrentValue - m_dMinValue) / (m_dMaxValue - m_dMinValue);
    
    CPoint ptRgn[5];
    ptRgn[0].x = int(m_ptMeterCenter.x - (m_nRadiusFrame / 25) * sin((dAngle + 30) * PI / 180));
    ptRgn[0].y = int(m_ptMeterCenter.y + (m_nRadiusFrame / 25) * cos((dAngle + 30) * PI / 180));
    ptRgn[1].x = int(m_ptMeterCenter.x - (m_nRadiusFrame / 25) * sin((dAngle + 30) * PI / 180)
                 - (m_nRadiusFrame / 20) * sin(dAngle * PI / 180));
    ptRgn[1].y = int(m_ptMeterCenter.y + (m_nRadiusFrame / 25) * cos((dAngle + 30) * PI / 180)
                 + (m_nRadiusFrame / 25) * cos(dAngle * PI / 180));
    ptRgn[2].x = int(m_ptMeterCenter.x - (dRadius - 10) * sin((dAngle + 30) * PI / 180));
    ptRgn[2].y = int(m_ptMeterCenter.y + (dRadius - 10) * cos((dAngle + 30) * PI / 180));
    ptRgn[3].x = int(m_ptMeterCenter.x - (m_nRadiusFrame / 25) * sin((dAngle + 30) * PI / 180)
                 - (m_nRadiusFrame / 20) * sin((dAngle + 60) * PI / 180));
    ptRgn[3].y = int(m_ptMeterCenter.y + (m_nRadiusFrame / 25) * cos((dAngle + 30) * PI / 180)
                 + (m_nRadiusFrame / 25) * cos((dAngle + 60) * PI / 180));
    ptRgn[4].x = int(m_ptMeterCenter.x - (m_nRadiusFrame / 25) * sin((dAngle + 30) * PI / 180));
    ptRgn[4].y = int(m_ptMeterCenter.y + (m_nRadiusFrame / 25) * cos((dAngle + 30) * PI / 180));
    
    pDC->SetPolyFillMode(WINDING);
    pDC->Polygon(ptRgn, 5);
    pDC->SelectObject(pOldBrush);
    pNeedleBrush.DeleteObject();

    pNeedleBrush.CreateSolidBrush(RGB( 0, 0, 0));
    pOldBrush = pDC->SelectObject(&pNeedleBrush );
    pPenThin.CreatePen(PS_SOLID, 1, RGB( 0, 0, 0));
    pOldPen = (CPen *)pDC->SelectObject(&pPenThin);
    rectRound.SetRect(m_ptMeterCenter.x - m_nRadiusFrame / 16,
                      m_ptMeterCenter.y - m_nRadiusFrame / 16,
                      m_ptMeterCenter.x + m_nRadiusFrame / 16,
                      m_ptMeterCenter.y + m_nRadiusFrame / 16);
    pDC->Ellipse(rectRound);
    pDC->SelectObject(pOldPen);
    pDC->SelectObject(pOldBrush);

    pNeedleBrush.DeleteObject();
    pNeedleBrush.CreateSolidBrush(RGB( 255, 128, 64));
    pOldBrush = pDC->SelectObject(&pNeedleBrush);
    rectRound.SetRect(m_ptMeterCenter.x - m_nRadiusFrame / 15 + m_nRadiusFrame / 50,
                      m_ptMeterCenter.y - m_nRadiusFrame / 15 + m_nRadiusFrame / 50,
                      m_ptMeterCenter.x + m_nRadiusFrame / 15 - m_nRadiusFrame / 50,
                      m_ptMeterCenter.y + m_nRadiusFrame / 15 - m_nRadiusFrame / 50);
    pDC->Ellipse(rectRound);

    pDC->SelectObject(pOldBrush);
    pNeedleBrush.DeleteObject();
    pPenThin.DeleteObject();
}

绘制仪表实时值,同样采用字体叠加的方法增加控件的美观性:
//绘制仪表实时值
void CRoundMeter::DrawValue(CDC *pDC, CRect &rect)
{
    char strCurrentValue[10];
    memset(strCurrentValue, 0, sizeof(strCurrentValue));

    sprintf(strCurrentValue, "%.2f", m_dCurrentValue);

    int nRadiusFrame = m_nRadiusFrame / 2;
    CFont pUnitFont, *pOldFont;
    LOGFONT lf;
    lf.lfEscapement = 0;
    lf.lfItalic = NULL;
    lf.lfUnderline = NULL;
    lf.lfStrikeOut = NULL;
    lf.lfCharSet = DEFAULT_CHARSET;
    lf.lfHeight = nRadiusFrame / 6;
    strcpy(lf.lfFaceName, "Arial");
    pUnitFont.CreateFontIndirect(&lf);
    pOldFont = (CFont *)pDC->SelectObject(&pUnitFont);    
    pDC->SetBkMode(TRANSPARENT);
    pDC->SetTextColor(RGB(255, 255, 255));
    CRect rectUnits(int(m_ptMeterCenter.x - nRadiusFrame * 0.30f),
                    int(m_ptMeterCenter.y + nRadiusFrame * 0.40f),
                    int(m_ptMeterCenter.x + nRadiusFrame * 0.30f + 2),
                    int(m_ptMeterCenter.y + nRadiusFrame * 0.60f + 2));
    pDC->DrawText(strCurrentValue, &rectUnits, DT_CENTER | DT_SINGLELINE | DT_VCENTER);
    pDC->SetTextColor(RGB( 0, 0, 255));
    rectUnits.SetRect(int(m_ptMeterCenter.x - nRadiusFrame * 0.30f),
                      int(m_ptMeterCenter.y + nRadiusFrame * 0.40f),
                      int(m_ptMeterCenter.x + nRadiusFrame * 0.30f),
                      int(m_ptMeterCenter.y + nRadiusFrame * 0.60f));
    pDC->DrawText(strCurrentValue, rectUnits, DT_CENTER | DT_SINGLELINE | DT_VCENTER);
    
    pDC->SelectObject(pOldFont);
    pUnitFont.DeleteObject();
}

至此控件的绘制工作已经基本完成。在这里我们考虑一个新的功能,就是在很多工业控制联合外围设备的时候有很多需要对外围设备制定一个值下发到外围设备从而达到精确控制的作用。我们指定某个值的方法有很多,比如采用输入编辑控件的方法去实现,加入我们的这个指定的值正是我们这个仪表显示的,我们在这里增加一个鼠标拖动仪表指针角度从而得到实时值的方法来代替编辑框的方法,能更形象的标示功能的操作要求。所以我们要在控件中增加WM_LBUTTONDOWN, WM_MOUSEMOVE, WM_LBUTTONUP消息来处理鼠标与圆盘角度实时值之间的关系,同时进行仪表的重绘工作。
首先封装鼠标当前的位置获得圆盘仪表的指针的角度
double CRoundMeter::GetAngle(CPoint &ptCur)
{
    double dAngle = 0;
    double xDisc = ptCur.x - m_ptMeterCenter.x;
    double yDisc = ptCur.y - m_ptMeterCenter.y;
    double tmpVal = yDisc / xDisc;

    if (xDisc < 0 && yDisc > 0)
    {        
        if ((-atan(tmpVal) * 180 / PI) > 60)
        {
            dAngle = 0;
        }
        else
        {
            dAngle = 60 + atan(tmpVal) * 180 / PI;
        }
    }
    else if (xDisc < 0 && yDisc <= 0)
    {
        dAngle = 60 + atan(tmpVal) * 180 / PI;
    }
    else if (xDisc > 0 && yDisc <= 0)
    {
        dAngle = 240 + atan(tmpVal) * 180 / PI;
    }
    else if (xDisc > 0 && yDisc > 0)
    {
        if ((-atan(tmpVal) * 180 / PI) > 60)
        {
            dAngle = 300;
        }
        else
        {
            dAngle = 240 + atan(tmpVal) * 180 / PI;
        }
    }
    else
    {
        dAngle = 150;
    }

    return dAngle;
}

然后通过这个角度获得圆盘的实时值:
double CRoundMeter::GetValue(double &dAngle)
{
    double dCurrentValue;
    dCurrentValue = m_dMinValue + dAngle * (m_dMaxValue - m_dMinValue) / 300;

    return dCurrentValue;
}

然后我们就可以在鼠标触发的消息中进行随意控制了。
void CRoundMeter::OnLButtonDown(UINT nFlags, CPoint point)
{
    if (!m_bMouseDrag)
    {
        return;
    }

    SetCapture();    

    double dAngle = GetAngle(point);
    m_dCurrentValue = GetValue(dAngle);
    Invalidate();

    CStatic::OnLButtonDown(nFlags, point);
}

void CRoundMeter::OnMouseMove(UINT nFlags, CPoint point)
{
    if(nFlags & MK_LBUTTON & m_bMouseDrag)
    {
        double dAngle = GetAngle(point);
        m_dCurrentValue = GetValue(dAngle);
        Invalidate();
    }

    CStatic::OnMouseMove(nFlags, point);
}

void CRoundMeter::OnLButtonUp(UINT nFlags, CPoint point)
{
    ReleaseCapture();

    CStatic::OnLButtonUp(nFlags, point);
}

编译运行,然后利用测试工程测试我们的控件,就可以看出效果了。
运行效果可以见下:
按此在新窗口浏览图片



--  作者:卷积内核
--  发布时间:4/17/2008 2:04:00 PM

--  
工控软件图形界面-控件实现(圆形仪表控件三)

关键字 仪表 仿真 工控 组态
原作者姓名 戚高

介绍
在工业控制系统开发过程中,图形显示方面占有着很重要的作用。比起很多专用的组态软件,他们有着强大的在图形系统,能够组态出来非常漂亮的系统。现在的很多的工业图形开发包都需要支付费用,很多漂亮的控件比如仪表等只能看图兴叹了。前些天一个朋友做一个泵站的监控系统,由于缺少相关的控件,在研究了该类控件的编程方法上,借鉴网络上的一些编程资料,完成了一些可用于工业控制系统开发使用的控件。

正文
前面的两篇文章中我已经用实例介绍了如何利用双缓冲技术编写圆盘仪表的方法。具体见
    实际仪表的编写原理可以大致差不多,区别就是编写背景或者指针或者显示实时值的时候采用的不同方法而已。而在工业控制程序中,如果采用了多种不同的仿真仪表来实现现场的模拟,将会在人机接口方面更加的友好。这里继续在前面的基础上编写一种新型的仪表,并应用到工控现场的仪表。
    我们还是按照编写的步骤,建立一个集成CStatic的仪表类。然后出发WM_PAINT等消息。然后在OnPaint中间完成仪表背景,仪表指针,仪表实时值的显示工作。在背景编写,指针和文字显示的时候,我们采用叠加的方法。图形叠加或者文字叠加方法其实很简单,在上篇文章已经有介绍,即是我们利用在相同的区域或者偏移量区域进行相同内容的绘制,绘制时候采用不同颜色,由于绘图的先后顺序,我们可以产生视觉上的立体叠加效果,具体实现见 工控软件图形界面-控件实现

相关核心代码如下:
绘制仪表背景
//绘制仪表背景
void CPanelMeter::DrawMeterBackground(CDC *pDC, CRect &rect)
{
    CBrush m_brushBack, pBackBrush, *pOldBrush;

    pDC->SetBkColor(m_BackColor);
    m_brushBack.CreateSolidBrush(m_BackColor);
    pOldBrush = (CBrush *)pDC->SelectObject(&m_brushBack);    
    pDC->FillRect(rect, &m_brushBack);   //绘制背景
    pDC->Rectangle(rect);                //绘制一个边框
    pDC->SelectObject(pOldBrush);
    m_brushBack.DeleteObject();

    int yHalf = rect.bottom - 8;
    float fa = (float)rect.Width() / 2;
    float fb = (float)rect.Height() - 8;
    
    CFont fScaleFont;
    fScaleFont.CreateFont(10, 12,   
        0,   
        0,   
        FW_NORMAL,
        FALSE,
        FALSE,
        FALSE,
        DEFAULT_CHARSET,
        OUT_DEFAULT_PRECIS,
        CLIP_DEFAULT_PRECIS,
        DEFAULT_QUALITY,
        DEFAULT_PITCH | FF_DONTCARE,
        "Arial");   
    
    if (m_dValidValueMin >= 0)  
    {
        CBrush brshGreen;
        CBrush brshRed;
        brshGreen.CreateSolidBrush(RGB(0, 192, 0));
        brshRed.CreateSolidBrush(RGB(230, 100, 100));
        
        float fStartAngle = (float)(m_dValidValueMin - m_dMinValue) / (float)(m_dMaxValue - m_dMinValue) * 3.14f;
        float fEndAngle   = (float)(m_dValidValueMax - m_dMinValue) / (float)(m_dMaxValue - m_dMinValue) * 3.14f;
        
        pDC->MoveTo(rect.left, yHalf);
        pDC->LineTo(rect.left + 20, yHalf);
        pDC->MoveTo(rect.right - 1, yHalf);
        pDC->LineTo(rect.right - 22, yHalf);
        
        pDC->MoveTo(rect.left, yHalf);
        for (float f = 0; f <= 3.14; f += 0.01f)
        {
            pDC->LineTo(rect.Width() / 2 - (int)(fa * cos(f)) + rect.left, yHalf - (int)(fb * sin(f)) + rect.top);
        }
        pDC->MoveTo(rect.left + 20, yHalf);
        for (f = 0; f <= 3.14; f += 0.01f)
        {
            pDC->LineTo(rect.Width() / 2 - (int)((fa - 20) * cos(f)) + rect.left, yHalf - (int)((fb - 20) * sin(f)) + rect.top);
        }

        CBrush *pOldBrush = pDC->SelectObject(&brshRed);
        pDC->FloodFill(rect.left + 10, yHalf - 10, RGB(0, 0, 0));
        
        fStartAngle *= 100;
        fStartAngle = (float)((int)fStartAngle) / 100;
        fEndAngle *= 100;
        fEndAngle = (float)((int)fEndAngle) / 100;
        
        pDC->MoveTo(rect.Width() / 2 - (int)(fa * cos(fStartAngle)) + rect.left, yHalf - (int)(fb * sin(fStartAngle)) + rect.top);
        pDC->LineTo(rect.Width() / 2 - (int)((fa - 20) * cos(fStartAngle)) + rect.left, yHalf - (int)((fb - 20) * sin(fStartAngle)) + rect.top);
        
        pDC->MoveTo(rect.Width() / 2 - (int)(fa * cos(fEndAngle)) + rect.left, yHalf - (int)(fb * sin(fEndAngle)) + rect.top);
        pDC->LineTo(rect.Width() / 2 - (int)((fa - 20) * cos(fEndAngle)) + rect.left, yHalf - (int)((fb - 20) * sin(fEndAngle)) + rect.top);
        
        pDC->SelectObject(brshGreen);
        if (fStartAngle > 0 && fEndAngle < 3.14)
        {
            pDC->FloodFill(rect.Width() / 2 - (int)((fa - 5) * cos((fStartAngle + fEndAngle) / 2)) + rect.left, yHalf - (int)((fb - 5) * sin((fStartAngle + fEndAngle) / 2)) + rect.top, RGB(0,0,0));
        }

        pDC->SelectObject(pOldBrush);
    }

    CPen pen;
    pen.CreatePen(PS_SOLID, 3, RGB(255,255,255));
    CPen *pOldPen = pDC->SelectObject(&pen);
    CFont *pOldFont = pDC->SelectObject(&fScaleFont);
    pDC->SetBkMode(TRANSPARENT);
    
    fa -= 10;
    fb -= 10;
    pDC->MoveTo(rect.left + 10, yHalf);
    for (float f = 0; f <= 3.14; f += 0.01f)
    {
        pDC->LineTo(rect.Width() / 2 - (int)(fa * cos(f)) + rect.left, yHalf - (int)(fb * sin(f)) + rect.top);
    }
    
    float fBigStep = 3.14f / ((float)(m_dMaxValue - m_dMinValue) / (float)m_dInterval) / 4;
    int nDiv = 0;
    int nScale = (int)m_dMinValue;
    for (f = 0; f <= 3.15; f += fBigStep)
    {
        pDC->MoveTo(rect.Width() / 2 - (int)(fa * cos(f)) + rect.left, yHalf - (int)(fb * sin(f)) + rect.top);
        if (nDiv == 0)
        {
            pDC->LineTo(rect.Width() / 2 - (int)((fa - 15) * cos(f)) + rect.left, yHalf - (int)((fb - 15) * sin(f)) + rect.top);
            CString str;
            str.Format("%d", nScale);
            nScale += (int)m_dInterval;
            CSize size = pDC->GetOutputTextExtent(str);
            pDC->SetTextColor(RGB(0,0,0));
            pDC->TextOut(rect.Width() / 2 - (int)((fa - 33) * cos(f)) + rect.left - size.cx / 2 + 1, yHalf - (int)((fb - 33) * sin(f)) + rect.top - size.cy / 2 + 1, str);
            pDC->SetTextColor(RGB(255, 255, 255));
            pDC->TextOut(rect.Width() / 2 - (int)((fa - 33) * cos(f)) + rect.left - size.cx / 2, yHalf - (int)((fb - 33) * sin(f)) + rect.top - size.cy / 2, str);
            nDiv++;
        }
        else
        {
            pDC->LineTo(rect.Width() / 2 - (int)((fa - 10) * cos(f)) + rect.left, yHalf - (int)((fb - 10) * sin(f)) + rect.top);
            nDiv++;
            if (nDiv == 4)
            {
                nDiv = 0;
            }
        }         
    }     
    pDC->SelectObject(pOldFont);
    fScaleFont.DeleteObject();

    CPoint m_ptMeterCenter;
    m_ptMeterCenter.x = rect.left + rect.Width() / 2;
    m_ptMeterCenter.y = rect.bottom;
    int nRadiusFrame = rect.Height();
    CFont pUnitFont;
    LOGFONT lf;
    lf.lfEscapement = 0;
    lf.lfItalic = NULL;
    lf.lfUnderline = NULL;
    lf.lfStrikeOut = NULL;
    lf.lfCharSet = DEFAULT_CHARSET;
    lf.lfHeight = nRadiusFrame / 4;
    strcpy(lf.lfFaceName, "隶书");
    pUnitFont.CreateFontIndirect(&lf);
    pOldFont = (CFont *)pDC->SelectObject(&pUnitFont);    
    pDC->SetBkMode(TRANSPARENT);
    pDC->SetTextColor(RGB(0, 0, 0));
    CRect rectUnits(int(m_ptMeterCenter.x - nRadiusFrame * 0.30f + 2),
                    int(m_ptMeterCenter.y - nRadiusFrame * 0.70f + 2),
                    int(m_ptMeterCenter.x + nRadiusFrame * 0.30f + 2),
                    int(m_ptMeterCenter.y - nRadiusFrame * 0.20f + 2));
    pDC->DrawText(m_strUnits, &rectUnits, DT_CENTER | DT_SINGLELINE | DT_VCENTER);
    pDC->SetTextColor(RGB( 255, 255, 255));
    rectUnits.SetRect(int(m_ptMeterCenter.x - nRadiusFrame * 0.30f),
                      int(m_ptMeterCenter.y - nRadiusFrame * 0.70f),
                      int(m_ptMeterCenter.x + nRadiusFrame * 0.30f),
                      int(m_ptMeterCenter.y - nRadiusFrame * 0.20f));
    pDC->DrawText(m_strUnits, rectUnits, DT_CENTER | DT_SINGLELINE | DT_VCENTER);
    
    pDC->SelectObject(pOldPen);
    pDC->SelectObject(pOldFont);
    pUnitFont.DeleteObject();
}

绘制仪表指针,采用叠加立体效果的处理技术
void CPanelMeter::DrawNeedle(CDC *pDC, CRect &rect)
{
    if (m_dCurrentValue > m_dMaxValue)
    {
        m_dCurrentValue = m_dMaxValue;
    }
    else if (m_dCurrentValue < m_dMinValue)
    {
        m_dCurrentValue = m_dMinValue;
    }   
  
    double dAngle = (m_dCurrentValue - m_dMinValue) / (m_dMaxValue - m_dMinValue) * 3.14f;
  
    float fa = (float)rect.Width() / 2;
    float fb = (float)rect.Height() - 8;

    CBrush brshWhite;
    CBrush brshShadow;
    brshWhite.CreateSolidBrush(RGB(255, 255, 255));
    brshShadow.CreateSolidBrush(RGB(0, 0, 0));
  
    CBrush *pOldBrush = pDC->SelectObject(&brshShadow);
    pDC->Ellipse(rect.Width() / 2 - 9 + rect.left, rect.Height() - 20 + rect.top, rect.Width() / 2 + 11 + rect.left, rect.Height() + rect.top);
    pDC->SelectObject(&brshWhite);
    pDC->Ellipse(rect.Width() / 2 - 10 + rect.left, rect.Height() - 21 + rect.top, rect.Width() / 2 + 10 + rect.left, rect.Height() - 1 + rect.top);
    pDC->SelectObject(pOldBrush);
  
    CPen penThick;
    CPen penThin;
    CPen penShadow;
  
    penThick.CreatePen(PS_SOLID, 5, RGB(255, 100, 50));
    penThin.CreatePen(PS_SOLID, 1, RGB(255, 255, 255));
    penShadow.CreatePen(PS_SOLID, 5, RGB(0, 0, 0));
  
    CPen *pOldPen = pDC->SelectObject(&penShadow);
    pDC->MoveTo(rect.Width() / 2 - (int)((fa - 8) * cos(dAngle)) + rect.left + 1, rect.Height() - 11 - (int)((fb - 8) * sin(dAngle)) + rect.top + 1);
    pDC->LineTo(rect.Width() / 2 + rect.left + 1, rect.Height() - 11 + rect.top + 1);
    pDC->SelectObject(&penThick);
    pDC->MoveTo(rect.Width() / 2 - (int)((fa - 8) * cos(dAngle)) + rect.left, rect.Height() - 11 - (int)((fb - 8) * sin(dAngle)) + rect.top);
    pDC->LineTo(rect.Width() / 2 + rect.left, rect.Height() - 11 + rect.top);
    pDC->SelectObject(&penThin);
    pDC->MoveTo(rect.Width() / 2 - (int)((fa - 8) * cos(dAngle)) + rect.left, rect.Height() - 11 - (int)((fb - 8) * sin(dAngle)) + rect.top);
    pDC->LineTo(rect.Width() / 2 + rect.left, rect.Height() - 11 + rect.top);
    pDC->SelectObject(pOldPen);
}

绘制实时值,同样采用叠加技术
void CPanelMeter::DrawValue(CDC *pDC, CRect &rect)
{
    char strCurrentValue[10];
    memset(strCurrentValue, 0, sizeof(strCurrentValue));

    sprintf(strCurrentValue, "%.2f", m_dCurrentValue);

    CPoint m_ptMeterCenter;
    m_ptMeterCenter.x = rect.left + rect.Width() / 2;
    m_ptMeterCenter.y = rect.bottom;
    int nRadiusFrame = rect.Height();
    CFont pUnitFont, *pOldFont;
    LOGFONT lf;
    lf.lfEscapement = 0;
    lf.lfItalic = NULL;
    lf.lfUnderline = NULL;
    lf.lfStrikeOut = NULL;
    lf.lfCharSet = DEFAULT_CHARSET;
    lf.lfHeight = nRadiusFrame / 6;
    strcpy(lf.lfFaceName, "Arial");
    pUnitFont.CreateFontIndirect(&lf);
    pOldFont = (CFont *)pDC->SelectObject(&pUnitFont);    
    pDC->SetBkMode(TRANSPARENT);
    pDC->SetTextColor(RGB(0, 0, 0));
    CRect rectUnits(int(m_ptMeterCenter.x - nRadiusFrame * 0.30f + 2),
                    int(m_ptMeterCenter.y - nRadiusFrame * 0.40f + 2),
                    int(m_ptMeterCenter.x + nRadiusFrame * 0.30f + 2),
                    int(m_ptMeterCenter.y - nRadiusFrame * 0.10f + 2));
    pDC->DrawText(strCurrentValue, &rectUnits, DT_CENTER | DT_SINGLELINE | DT_VCENTER);
    pDC->SetTextColor(RGB( 255, 255, 255));
    rectUnits.SetRect(int(m_ptMeterCenter.x - nRadiusFrame * 0.30f),
                      int(m_ptMeterCenter.y - nRadiusFrame * 0.40f),
                      int(m_ptMeterCenter.x + nRadiusFrame * 0.30f),
                      int(m_ptMeterCenter.y - nRadiusFrame * 0.10f));
    pDC->DrawText(strCurrentValue, rectUnits, DT_CENTER | DT_SINGLELINE | DT_VCENTER);
    
    pDC->SelectObject(pOldFont);
    pUnitFont.DeleteObject();
}

至此,基本绘制工作完成,我们添加一些接口以便能在程序中进行程序参数的改变。
大致运行效果见下:由于时间的关系,绘制可能比较粗糙,如果有时间的朋友可以在上面的基础上进行扩展。
按此在新窗口浏览图片



--  作者:卷积内核
--  发布时间:4/17/2008 2:06:00 PM

--  
一个工业控制管道流动控件的实现

关键字 管道 流动 工控 组态

介绍
利用双缓冲绘图的方法,结合网络上面的一些绘制渐变的方法实现管道和流动控件的绘制。

正文
从毕业到现在已经有比较长的一段时间了,在这些工作期间,编写工控软件一直是我主要从事的职业。由于当时所在的是一个中小心公司,主要负责电力行业的监控系统,同时开拓一些工控相关的业务,比如自来水,闸门,橡胶坝等系统。这类系统一个显著的特点就是系统集成度比较高,要求对现场的控制要求比较了解,然后采用PLC+工控机的模式就可以了。现在的PLC品种繁多,而且技术都非常成熟,能够稳定运行,且各PLC厂商都提供了编程软件,能够快速的进行开发,而在上位机(工控机软件)部分也有不少的组态软件提供商,比较有名的诸如组态王、Visual Graph、iFix等。但是这些组态软件很多都是根据项目授权,即投运一个项目需要得到他的一个授权。作为中小心公司,公司领导层觉得既然拥有自己的软件开发工程师如果再采用组态软件来实现的话是一笔很划不来的事情,所以公司的主张就是自己开发自己的监控软件。
    开发工控软件很重要的一个部分就是图形系统。很多组态系统提供了丰富的图形组态功能,能够通过他们提供的图形库完成各种复杂的图形开发,而且效果非常好。比如表记,LED,趋势,流动控件等。而自己开发则要单调很多。现在有有了一些诸如世纪飞扬等控件提供商,提供一系列控件开发包,不过一句话,他们都是要花米米的。作为很多开发人员是不愿意去出这些钱的,但是搜索网络上面这类控件,却几乎难得找到几个。前些时候一个朋友接了一个项目,一个泵厂的监控系统,这个系统实现中需要很多的管道和管道内部趋势图,能够实时反映水流的方向等。由于属于私人开发不可能花钱去购买那些控件,于是要我给他做一个可用于这个项目能够反映流动情况的管道控件。
    多亏了现在网络上信息的丰富,“如果说我成功那是因为我站在了巨人的肩膀上“,通过考虑,我们可以采用管道和流动相结合的方法来实现我们的控件
首先是管道,管道简单考虑就是横和竖两种(至于您有时间可以考虑那些七七八八转弯的情况),那么我们这里可以把管道定义成两种模式
typedef enum _FlowPattern
{
    FP_HORIZONTAL = 0,
    FP_VERTICAL   = 1
}FLOWPATTERN;

然后是里面的水流的流动方向,横向的可以为由左向右和由右向左,竖向的可以分为由上向下和右下向上,这样我们就可以抽象里面的流动方向:
typedef enum _DirectionType
{
    LEFT_TO_RIGHT = 0,
    RIGHT_TO_LEFT = 1,
    TOP_TO_BOTTOM = 2,
    BOTTOM_TO_TOP = 3
}DIRECTIONTYPE;

下面就是管道的绘制了,如何绘制才能达到视觉上的管道效果呢。如果是一个平面矩形是不能看起来视觉效果的管道的。参考了网络上面的一些渐变的处理方法后发现,其实我们的管道可以用这个方法来实现。如果管道的边和中央用两种不同的颜色,这两种颜色为一种颜色近似的值,一种深,一种浅。当颜色处理由边向中央绘制采用的方法为:浅—深—浅时,这样我们可以产生视觉效果的管道的凹表面,反过来则能够产生管道的凸表面。现在网络上面如何进行渐变绘图的方法实例很多,这个是没有问题的。

然后就是我们要考虑的流动的设计了。在我们平常见到过的一些流动控件中(收费控件)流动的形状可以为多种,有的是矩形,有的是小三角形等。而流动的表现就是在管道的中间绘制一些我们上面举例的小图形等。这样,我们为这些单个的小图形设计一个功能类,然后在类中间实现不同的单个小滑块进行绘制,如果我们的管道有N个这样的小滑块组成,如果我们进行动态的绘制小滑块,那么不就会产生视觉上的滑动了。

原理诸如上面所需,我们在滑动的控件中设计一个定时器,利用定时器驱动,然后将管道利用形状动态填充一个滑块列队,每次动态计算每个滑块的位置,通过动态刷新管道背景和滑块位置(擦除掉原来的那些旧的滑块位置)就可以产生滑动的效果了,管道流动控件原理基本上就是这样了。

下面是动手实现了,我们首先通过ClassWizard创建一个继承CStatic的管道流动类CFlowCtrl,然后映射WM_PAINT消息,因为在这里要完成管道和滑块的绘制。如果我们的定时器时间很小,刷新频率很快,将会出现很严重的闪烁现象,这里我们采用双缓冲,在内存中间进行复杂的图形的绘制,然后进行“贴“(BitBtn)到界面上就可以不闪烁了。为了使程序的可读性增强,我们对管道绘制和滑块绘制分两个类(CFlowImpl、CPipeImpl)来进行。

管道绘制:
采用渐变的方法进行管道的绘制,由于在绘图过程中,这里不是经常变化的,只有当管道的颜色发生变化或者其他一些管道的配置发生变化才要重绘,我们在双缓冲绘图时只要进行一次绘图就可以了,然后生成绘图句柄到内存,这样可以提高效率。
void CPipeImpl::Draw(CDC *pDC)
{
    //用渐变的方法绘制管道
    DrawGradient(pDC);

    //绘制图形周围的边框
    CRect rcClient;
    m_pOwer->GetClientRect(&rcClient);
    CBrush pBlackBrush(m_colorBorder);
    pDC->FrameRect(&rcClient, &pBlackBrush);
}

绘制渐变我们采用一个常用的方法,这里进行这个方法的简单的封装,以后还能够用这些方法绘制图形的渐变区域以及工业控制别的控件,比如容器等。
void CPipeImpl::DrawGradient(CDC *pDC)
{
    CRect        rcClient;
    CBitmap        MemBmp, *pOldMemBmp;
    CDC            MemDC;
    COLORREF    crCur;
    CPoint      ptCur;

    m_pOwer->GetClientRect(&rcClient);
    int nWidth = m_pOwer->GetFlowPattern() == FP_HORIZONTAL ?
        rcClient.Height() : rcClient.Width();
    RGBTRIPLE *pRGBTriple = new RGBTRIPLE[nWidth], *pEntry;

    if (pRGBTriple == NULL)
    {
        return;
    }

    MemBmp.CreateCompatibleBitmap(pDC,
        m_pOwer->GetFlowPattern() == FP_HORIZONTAL ? 1 : rcClient.Width(),
        m_pOwer->GetFlowPattern() == FP_HORIZONTAL ? rcClient.Height() : 1);
    MemDC.CreateCompatibleDC(pDC);
    pOldMemBmp = (CBitmap *)MemDC.SelectObject(&MemBmp);
    ptCur = CPoint(0, 0);
    m_Gradient.MakeEntries(pRGBTriple, nWidth);

    for (int i=0; i    {
        if (m_pOwer->GetFlowPattern() == FP_HORIZONTAL)
        {
            ptCur.y = i;
        }
        else
        {
            ptCur.x = i;
        }
        pEntry = &pRGBTriple[i];
        crCur = RGB(pEntry->rgbtRed, pEntry->rgbtGreen, pEntry->rgbtBlue);
        MemDC.SetPixelV(ptCur, crCur);
    }

    if (m_pOwer->GetFlowPattern() == FP_HORIZONTAL)
    {
        pDC->StretchBlt(rcClient.left, rcClient.top, rcClient.Width(), rcClient.Height(),
                        &MemDC, 0, 0, 1, nWidth, SRCCOPY);
    }
    else
    {
        pDC->StretchBlt(rcClient.left, rcClient.top, rcClient.Width(), rcClient.Height(),
                        &MemDC, 0, 0, nWidth, 1, SRCCOPY);
    }

    MemDC.SelectObject(pOldMemBmp);
    MemBmp.DeleteObject();
    MemDC.DeleteDC();

    delete [] pRGBTriple;
}

FlowCtrl在OnPaint函数中通过调用PipeImpl的Draw就能够完成背景的绘制了。

下面是滑块的绘制,根据开始的原理,我们对每个滑块抽象一个小类CFlowUnit,根据滑块的形状我们可以抽象滑块的外观:
typedef enum _UnitPattern
{
    UP_RECTANGLE = 0,   
    UP_CIRCLE    = 1,
    UP_DLINE     = 2,    // 象 >> 类别
    UP_TRIANGLE  = 3
}UNITPATTERN;

然后实现一个Draw函数完成这些不同形状的滑块的绘制工作。
void CFlowUnit::Draw(CDC *pDC, CRect &rcClient)
{
    CBrush pBackBrush, *pOldBrush;

    if (m_pParentWnd == NULL)
    {
        return;
    }
    
    pBackBrush.CreateSolidBrush(m_pParentWnd->GetUnitBackColor());
    pOldBrush = (CBrush *)pDC->SelectObject(&pBackBrush);    

    switch (m_pParentWnd->GetUnitPattern())
    {
    case UP_RECTANGLE:
        pDC->FillRect(&m_rectClient, &pBackBrush);
        break;

    case UP_CIRCLE:
        pDC->SelectObject(::GetStockObject(NULL_PEN));
        pDC->Ellipse(&m_rectClient);
        break;

    case UP_DLINE:
        {
            int xCenter = 0, yCenter = 0;
            
            if ((m_pParentWnd->GetUnitDLinePattern() == DP_RIGHT) || (m_pParentWnd->GetUnitDLinePattern() == DP_LEFT))
            {
                xCenter = m_rectClient.left + (m_rectClient.right - m_rectClient.left) / 2;
                yCenter = (rcClient.bottom - rcClient.top) / 2;
            }
            else
            {
                xCenter = (rcClient.right - rcClient.left) / 2;
                yCenter = m_rectClient.top + (m_rectClient.bottom - m_rectClient.top) / 2;
            }
            DrawDLine(pDC, CPoint(xCenter, yCenter));
        }
        break;

    case UP_TRIANGLE:
        pDC->SelectObject(::GetStockObject(NULL_PEN));
        DrawTriangle(pDC);
        break;
    }

    pDC->SelectObject(pOldBrush);
    pBackBrush.DeleteObject();
}

void CFlowUnit::DrawDLine(CDC *pDC, CPoint ptCenter)
{
    if (m_pParentWnd == NULL)
    {
        return;
    }

    switch (m_pParentWnd->GetUnitDLinePattern())
    {
    case DP_RIGHT:
        _D_LINE_H(ptCenter.x + 0, ptCenter.y + 0, m_pParentWnd->GetUnitBackColor());
        _D_LINE_H(ptCenter.x + 1, ptCenter.y + 1, m_pParentWnd->GetUnitBackColor());
        _D_LINE_H(ptCenter.x + 2, ptCenter.y + 2, m_pParentWnd->GetUnitBackColor());
        _D_LINE_H(ptCenter.x + 1, ptCenter.y + 3, m_pParentWnd->GetUnitBackColor());
        _D_LINE_H(ptCenter.x + 0, ptCenter.y + 4, m_pParentWnd->GetUnitBackColor());
        break;

    case DP_LEFT:
        _D_LINE_H(ptCenter.x + 2, ptCenter.y + 0, m_pParentWnd->GetUnitBackColor());
        _D_LINE_H(ptCenter.x + 1, ptCenter.y + 1, m_pParentWnd->GetUnitBackColor());
        _D_LINE_H(ptCenter.x + 0, ptCenter.y + 2, m_pParentWnd->GetUnitBackColor());
        _D_LINE_H(ptCenter.x + 1, ptCenter.y + 3, m_pParentWnd->GetUnitBackColor());
        _D_LINE_H(ptCenter.x + 2, ptCenter.y + 4, m_pParentWnd->GetUnitBackColor());
        break;

    case DP_DOWN:
        _D_LINE_V(ptCenter.x + 0, ptCenter.y + 0, m_pParentWnd->GetUnitBackColor());
        _D_LINE_V(ptCenter.x + 1, ptCenter.y + 1, m_pParentWnd->GetUnitBackColor());
        _D_LINE_V(ptCenter.x + 2, ptCenter.y + 2, m_pParentWnd->GetUnitBackColor());
        _D_LINE_V(ptCenter.x + 3, ptCenter.y + 1, m_pParentWnd->GetUnitBackColor());
        _D_LINE_V(ptCenter.x + 4, ptCenter.y + 0, m_pParentWnd->GetUnitBackColor());
        break;

    case DP_UP:
        _D_LINE_V(ptCenter.x + 0, ptCenter.y + 2, m_pParentWnd->GetUnitBackColor());
        _D_LINE_V(ptCenter.x + 1, ptCenter.y + 1, m_pParentWnd->GetUnitBackColor());
        _D_LINE_V(ptCenter.x + 2, ptCenter.y + 0, m_pParentWnd->GetUnitBackColor());
        _D_LINE_V(ptCenter.x + 3, ptCenter.y + 1, m_pParentWnd->GetUnitBackColor());
        _D_LINE_V(ptCenter.x + 4, ptCenter.y + 2, m_pParentWnd->GetUnitBackColor());
        break;

    default:
        break;
    }
}

void CFlowUnit::DrawTriangle(CDC *pDC)
{
    if (m_pParentWnd == NULL)
    {
        return;
    }

    CPoint ptRgn[3];

    switch (m_pParentWnd->GetFlowCtrl()->GetDirectionType())
    {
    case LEFT_TO_RIGHT:
        ptRgn[0].x = m_rectClient.left;
        ptRgn[0].y = m_rectClient.top;
        ptRgn[1].x = m_rectClient.left;
        ptRgn[1].y = m_rectClient.bottom;
        ptRgn[2].x = m_rectClient.right;
        ptRgn[2].y = m_rectClient.top + (m_rectClient.bottom - m_rectClient.top) / 2;
        break;

    case RIGHT_TO_LEFT:
        ptRgn[0].x = m_rectClient.right;
        ptRgn[0].y = m_rectClient.top;
        ptRgn[1].x = m_rectClient.right;
        ptRgn[1].y = m_rectClient.bottom;
        ptRgn[2].x = m_rectClient.left;
        ptRgn[2].y = m_rectClient.top + (m_rectClient.bottom - m_rectClient.top) / 2;
        break;

    case TOP_TO_BOTTOM:
        ptRgn[0].x = m_rectClient.left;
        ptRgn[0].y = m_rectClient.top;
        ptRgn[1].x = m_rectClient.right;
        ptRgn[1].y = m_rectClient.top;
        ptRgn[2].x = m_rectClient.left + (m_rectClient.right - m_rectClient.left) / 2;
        ptRgn[2].y = m_rectClient.bottom;
        break;

    case BOTTOM_TO_TOP:
        ptRgn[0].x = m_rectClient.left;
        ptRgn[0].y = m_rectClient.bottom;
        ptRgn[1].x = m_rectClient.right;
        ptRgn[1].y = m_rectClient.bottom;
        ptRgn[2].x = m_rectClient.left + (m_rectClient.right - m_rectClient.left) / 2;
        ptRgn[2].y = m_rectClient.top;
        break;

    default:
        break;
    }

    CBrush pBackBrush, *pOldBrush;
    pBackBrush.CreateSolidBrush(m_pParentWnd->GetUnitBackColor());
    pOldBrush = (CBrush *)pDC->SelectObject(&pBackBrush);
    pDC->SetPolyFillMode(WINDING);
    pDC->Polygon(ptRgn, 3);
    pDC->SelectObject(pOldBrush);
    pBackBrush.DeleteObject();
}

剩下来的就是我们实现滑块的位置控制了。我们可以采用一个简单的实现方法,比如采用每个定时器到达的时候移动几个像素点的方法,通过向管道里面的滑块刘表传递下一个区域就可以了。当这个区域处于滑出去的区域时候,我们自动销毁这个滑块对象,然后重新添加一个滑块对象到最开始的地方就可以了。

我们将在FlowImpl中定义一个CList m_arrayFlowUnit;链表,然后在初始化的时候通过滑块的长,宽等信息计算管道可以容纳的滑块数量和位置。
void CFlowImpl::InitFlowUnit()
{
    CRect rcUnit, rcClient;
    m_pOwer->GetClientRect(&rcClient);
    m_arrayFlowUnit.RemoveAll();

    if (m_pOwer->GetDirectionType() == LEFT_TO_RIGHT
        || m_pOwer->GetDirectionType() == RIGHT_TO_LEFT)
    {
        int nUnits = rcClient.Width() / (UNIT_WIDTH + UNIT_DISC);  
        int nResidue = (rcClient.bottom - rcClient.top) % 2;
        int yCenter = (rcClient.bottom - rcClient.top) / 2;
        for (int i=0; i        {         
            rcUnit.SetRect(rcClient.left + i * (UNIT_WIDTH + UNIT_DISC),
                           yCenter - UNIT_HEIGHT / 2,
                           rcClient.left + i * (UNIT_WIDTH + UNIT_DISC) + UNIT_WIDTH,
                           yCenter + UNIT_HEIGHT / 2 + nResidue);
            
            CFlowUnit *pFlowUnit = new CFlowUnit();
            pFlowUnit->SetRect(rcUnit);
            pFlowUnit->SetParentWnd(this);
            m_arrayFlowUnit.AddTail(pFlowUnit);
        }
    }
    else
    {
        int nUnits = rcClient.bottom / (UNIT_WIDTH + UNIT_DISC);
        int nResidue = (rcClient.right - rcClient.left) % 2;
        int xCenter = (rcClient.right - rcClient.left) / 2;
        for (int i=0; i        {         
            rcUnit.SetRect(xCenter - UNIT_HEIGHT / 2,
                           rcClient.top + i * (UNIT_WIDTH + UNIT_DISC),
                           xCenter + UNIT_HEIGHT / 2 + nResidue,
                           rcClient.top + i * (UNIT_WIDTH + UNIT_DISC) + UNIT_WIDTH);
            
            CFlowUnit *pFlowUnit = new CFlowUnit();
            pFlowUnit->SetRect(rcUnit);
            pFlowUnit->SetParentWnd(this);
            m_arrayFlowUnit.AddTail(pFlowUnit);
        }
    }
}

然后通过定时器到达的时候动态计算链表滑块的每个对象的下一个要显示的CRect,然后进行管道背景刷新就可以完成了。
void CFlowImpl::CalculateFlowUnit()
{
    CRect rcClient;
    m_pOwer->GetClientRect(&rcClient);

    int xResidue = (rcClient.right - rcClient.left) % 2;
    int xCenter = (rcClient.right - rcClient.left) / 2;
    int yCenter = (rcClient.bottom - rcClient.top) / 2;
    int yResidue = (rcClient.bottom - rcClient.top) % 2;

    POSITION pos = m_arrayFlowUnit.GetHeadPosition();
    while (pos != NULL)
    {
        CFlowUnit *pFlowUnit = m_arrayFlowUnit.GetAt(pos);

        //从左至右
        if (m_pOwer->GetDirectionType() == LEFT_TO_RIGHT)
        {
            CRect rcUnit = pFlowUnit->GetRect();
            rcUnit.DeflateRect(UNIT_STEP, 0, -UNIT_STEP, 0);
            pFlowUnit->SetRect(rcUnit);         
            
            if (rcUnit.left >= rcClient.right)
            {
                pFlowUnit = m_arrayFlowUnit.GetHead();
                
                CRect rcAddUnit;
                rcAddUnit.SetRect(pFlowUnit->GetRect().left - UNIT_DISC - UNIT_WIDTH,
                                  yCenter - UNIT_HEIGHT / 2,
                                  pFlowUnit->GetRect().left - UNIT_DISC,
                                  yCenter + UNIT_HEIGHT / 2 + yResidue);
                CFlowUnit *pAddFlowUnit = new CFlowUnit();
                pAddFlowUnit->SetRect(rcAddUnit);
                pAddFlowUnit->SetParentWnd(this);
                m_arrayFlowUnit.AddHead(pAddFlowUnit);
                
                m_arrayFlowUnit.RemoveAt(pos);
                break;
            }
        }
        //从右至左
        else if (m_pOwer->GetDirectionType() == RIGHT_TO_LEFT)
        {
            CRect rcUnit = pFlowUnit->GetRect();
            rcUnit.DeflateRect(-UNIT_STEP, 0, +UNIT_STEP, 0);
            pFlowUnit->SetRect(rcUnit);         
            
            if (rcUnit.right <= rcClient.left)
            {
                pFlowUnit = m_arrayFlowUnit.GetTail();
                
                CRect rcAddUnit;
                rcAddUnit.SetRect(pFlowUnit->GetRect().right + UNIT_DISC,
                                  yCenter - UNIT_HEIGHT / 2,
                                  pFlowUnit->GetRect().right + UNIT_DISC + UNIT_WIDTH,
                                  yCenter + UNIT_HEIGHT / 2 + yResidue);
                CFlowUnit *pAddFlowUnit = new CFlowUnit();
                pAddFlowUnit->SetRect(rcAddUnit);
                pAddFlowUnit->SetParentWnd(this);
                m_arrayFlowUnit.AddTail(pAddFlowUnit);
                
                m_arrayFlowUnit.RemoveAt(pos);
                break;
            }
        }
        //从上至下
        else if (m_pOwer->GetDirectionType() == TOP_TO_BOTTOM)
        {
            CRect rcUnit = pFlowUnit->GetRect();
            rcUnit.DeflateRect(0, UNIT_STEP, 0, -UNIT_STEP);
            pFlowUnit->SetRect(rcUnit);         
            
            if (rcUnit.top >= rcClient.bottom)
            {
                pFlowUnit = m_arrayFlowUnit.GetHead();
                
                CRect rcAddUnit;
                rcAddUnit.SetRect(xCenter - UNIT_HEIGHT / 2,
                                  pFlowUnit->GetRect().top - UNIT_DISC - UNIT_WIDTH,
                                  xCenter + UNIT_HEIGHT / 2 + xResidue,
                                  pFlowUnit->GetRect().top - UNIT_DISC);
                CFlowUnit *pAddFlowUnit = new CFlowUnit();
                pAddFlowUnit->SetRect(rcAddUnit);
                pAddFlowUnit->SetParentWnd(this);
                m_arrayFlowUnit.AddHead(pAddFlowUnit);
                
                m_arrayFlowUnit.RemoveAt(pos);
                break;
            }
        }
        //从下至上
        else if (m_pOwer->GetDirectionType() == BOTTOM_TO_TOP)
        {
            CRect rcUnit = pFlowUnit->GetRect();
            rcUnit.DeflateRect(0, -UNIT_STEP, 0, +UNIT_STEP);
            pFlowUnit->SetRect(rcUnit);         
            
            if (rcUnit.bottom <= rcClient.top)
            {
                pFlowUnit = m_arrayFlowUnit.GetTail();
                
                CRect rcAddUnit;
                rcAddUnit.SetRect(xCenter - UNIT_HEIGHT / 2,
                                  pFlowUnit->GetRect().bottom + UNIT_DISC,
                                  xCenter + UNIT_HEIGHT / 2 + xResidue,
                                  pFlowUnit->GetRect().bottom + UNIT_DISC + UNIT_WIDTH);
                CFlowUnit *pAddFlowUnit = new CFlowUnit();
                pAddFlowUnit->SetRect(rcAddUnit);
                pAddFlowUnit->SetParentWnd(this);
                m_arrayFlowUnit.AddTail(pAddFlowUnit);
                
                m_arrayFlowUnit.RemoveAt(pos);
                break;
            }
        }

        m_arrayFlowUnit.GetNext(pos);
    }
}

最后我们定义一些控件的接口完成对控件的参数的设置。
我们可以看到程序的运行效果:
按此在新窗口浏览图片

按此在新窗口浏览图片

如果您还有更好的方法实现此类控件或者您已经实现了那些七七八八拐弯的功能,请您将您的经验写下来和我们分享。




--  作者:卷积内核
--  发布时间:4/17/2008 2:16:00 PM

--  
工控软件图形界面-控件实现(温度计控件)
介绍
在工业控制系统开发过程中,图形显示方面占有着很重要的作用。比起很多专用的组态软件,他们有着强大的在图形系统,能够组态出来非常漂亮的系统。现在的很多的工业图形开发包都需要支付费用,很多漂亮的控件比如仪表等只能看图兴叹了。前些天一个朋友做一个泵站的监控系统,由于缺少相关的控件,在研究了该类控件的编程方法上,借鉴网络上的一些编程资料,完成了一些可用于工业控制系统开发使用的控件

正文
仪表控件,温度计控件,LED控件等是工业组态软件中最常用的人际交互控件,能够提供一种更友好的界面展示方法。而温度计控件其实综合起来就是一个水银柱绘制和一个刻度的绘制的过程。由于考虑到刷新可能对界面的产生的影响,我们采用双缓冲技术实现。
    通过VC的ClassWizard建立一个温度计显示控件类,继承CStatic。我们映射WM_PAINT消息,在这里完成温度计的各种绘制工作就可以了。这个和前面的仪表控件绘制原理是一致的。唯一的区别就是在界面展示不同的绘制效果而已。
void CThermoMeter::OnPaint()
{
    CPaintDC dc(this);

    // 获得控件区域
    GetClientRect (&m_rectCtrl);

    CMemDC memDC(&dc, &m_rectCtrl);

    //绘制仪表盘
    if (m_dcMeterPlate.GetSafeHdc() == NULL || (m_bitmapMeterPlate.m_hObject == NULL))
    {
        m_dcMeterPlate.CreateCompatibleDC(&dc);
        m_bitmapMeterPlate.CreateCompatibleBitmap(&dc, m_rectCtrl.Width(),                                                                                     m_rectCtrl.Height()) ;
        m_pbitmapOldMeterPlate = m_dcMeterPlate.SelectObject(&m_bitmapMeterPlate) ;
        DrawMeterBackground(&m_dcMeterPlate, m_rectCtrl);

    }
    memDC.BitBlt(0, 0, m_rectCtrl.Width(), m_rectCtrl.Height(),
                       &m_dcMeterPlate, 0, 0, SRCCOPY);

    DrawScale(&memDC);
    m_ctrlUnit.Draw(&memDC);
}

由于在双缓冲绘制过程中,我们的背景可以认为是不变的,所以在程序运行的数据刷新过程中,我们可以在初始化的时候在内存中间完成这部分的绘制工作,然后调用的时候将他Bitbtn到界面就可以了。刷新数据的时候每次调用以前绘制的成果BitBtn(贴)到界面上,这样就可以省掉了刷新的时候给人的闪烁的感觉了。然后完成水银柱的绘制以及实时值的绘制。界面绘制为这个控件的核心部分了。
//绘制仪表背景
void CThermoMeter::DrawMeterBackground(CDC *pDC, CRect &rect)
{
    CString strScale;
    CFont fScaleFont, *pOldFont;
    CPen penThick, penThin, penShadow, penScale, *pOldPen;
    CBrush m_brushBack, pBackBrush, *pOldBrush;
    pDC->SetBkColor(m_BackColor);
    m_brushBack.CreateSolidBrush(m_BackColor);
    pOldBrush = (CBrush *)pDC->SelectObject(&m_brushBack);    
    pDC->FillRect(rect, &m_brushBack);   //绘制背景
    pDC->SelectObject(pOldBrush);
    m_brushBack.DeleteObject();

    //绘制边框的立体效果    
    penThick.CreatePen(PS_SOLID, 1, RGB(172, 168, 153));
    penThin.CreatePen(PS_SOLID, 1, RGB(113, 111, 110));
    penShadow.CreatePen(PS_SOLID, 1, RGB(255, 255, 255));
    pOldPen = (CPen *)pDC->SelectObject(&penShadow);
    pDC->MoveTo(rect.left, rect.top);
    pDC->LineTo(rect.right - 1, rect.top);
    pDC->MoveTo(rect.left + 1, rect.top + 1);
    pDC->LineTo(rect.right - 2, rect.top + 1);
    pDC->MoveTo(rect.left, rect.top);
    pDC->LineTo(rect.left, rect.bottom - 1);
    pDC->MoveTo(rect.left + 1, rect.top + 1);
    pDC->LineTo(rect.left + 1, rect.bottom - 2);
    pDC->SelectObject(&penThick);
    pDC->MoveTo(rect.left + 1, rect.bottom - 1);
    pDC->LineTo(rect.right - 1, rect.bottom - 1);
    pDC->LineTo(rect.right - 1, rect.top + 1);
    pDC->SelectObject(&penThin);
    pDC->MoveTo(rect.left, rect.bottom);
    pDC->LineTo(rect.right, rect.bottom);
    pDC->LineTo(rect.right, rect.top);
    pDC->SelectObject(pOldPen);    

    //绘制指针显示区域
    CRect rectScale;
    rectScale.SetRect(rect.left + rect.Width() / 4,
                      rect.top + rect.Height() / 12,
                      rect.left + rect.Width() / 4 + rect.Width() / 10,
                      rect.bottom - rect.Height() / 12);
    pBackBrush.CreateSolidBrush(RGB(128, 0, 0));
    pOldBrush = (CBrush *)pDC->SelectObject(&pBackBrush);
    pDC->FillRect(&rectScale, &pBackBrush);
    pDC->SelectObject(pOldBrush);
    pBackBrush.DeleteObject();

    //绘制指针区域立体效果
    pDC->SelectObject(&penThick);
    pDC->MoveTo(rectScale.left, rectScale.bottom);
    pDC->LineTo(rectScale.left, rectScale.top);
    pDC->LineTo(rectScale.right, rectScale.top);
    pDC->SelectObject(&penThin);
    pDC->MoveTo(rectScale.left + 1, rectScale.bottom);
    pDC->LineTo(rectScale.left + 1, rectScale.top + 1);
    pDC->LineTo(rectScale.right, rectScale.top + 1);
    pDC->SelectObject(&penShadow);
    pDC->MoveTo(rectScale.right - 1, rectScale.top + 1);
    pDC->LineTo(rectScale.right - 1, rectScale.bottom - 1);
    pDC->LineTo(rectScale.left + 1, rectScale.bottom - 1);
    pDC->MoveTo(rectScale.right, rectScale.top);
    pDC->LineTo(rectScale.right, rectScale.bottom);
    pDC->LineTo(rectScale.left, rectScale.bottom);

    m_rectScale = rectScale;
    m_rectScale.DeflateRect(2, 2, 2, 2);

    //绘制刻度
    fScaleFont.CreateFont(rect.Height() / 8, 0,   
        0,   
        0,   
        FW_NORMAL,
        FALSE,
        FALSE,
        FALSE,
        DEFAULT_CHARSET,
        OUT_DEFAULT_PRECIS,
        CLIP_DEFAULT_PRECIS,
        DEFAULT_QUALITY,
        DEFAULT_PITCH | FF_DONTCARE,
        "System");  
    pOldFont = (CFont *)pDC->SelectObject(&fScaleFont);
    penScale.CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
    for (int i=0; i    {
        CPoint ptStartTick, ptEndTick;
        int nTickDisc = rect.Height() * 5 * i / (6 * m_nTicks);
        ptStartTick.x = rect.left + rect.Width() * 3 / 7;
        ptStartTick.y = rect.bottom - rect.Height() / 12 - nTickDisc;
        ptEndTick.x = rect.left + rect.Width() * 3 / 5;
        ptEndTick.y = rect.bottom - rect.Height() / 12 - nTickDisc;

        pDC->SelectObject(penScale);
        pDC->MoveTo(ptStartTick);
        pDC->LineTo(ptEndTick);

        //绘制立体感觉
        CPoint ptShadowStartTick, ptShadowEndTick;
        ptShadowStartTick = ptStartTick;
        ptShadowEndTick = ptEndTick;
        ptShadowStartTick.y--;
        ptShadowEndTick.y--;
        pDC->SelectObject(penShadow);
        pDC->MoveTo(ptShadowStartTick);
        pDC->LineTo(ptShadowEndTick);        

        //绘制子刻度
        for (int j=0; j        {
            if (i < m_nTicks)
            {
                CPoint ptSubStartTick, ptSubEndTick;
                int nSubTickDisc = (rect.Height() * 5 / (6 * m_nTicks)) * j / m_nSubTicks;
                ptSubStartTick.x = ptStartTick.x;
                ptSubStartTick.y = ptStartTick.y - nSubTickDisc;
                ptSubEndTick.x = ptSubStartTick.x + (ptEndTick.x - ptSubStartTick.x) / 2;
                ptSubEndTick.y = ptSubStartTick.y;
                pDC->SelectObject(penScale);
                pDC->MoveTo(ptSubStartTick);
                pDC->LineTo(ptSubEndTick);

                CPoint ptShadowSubStartTick, ptShadowSubEndTick;
                ptShadowSubStartTick = ptSubStartTick;
                ptShadowSubEndTick = ptSubEndTick;
                ptShadowSubStartTick.y--;
                ptShadowSubEndTick.y--;
                pDC->SelectObject(penShadow);
                pDC->MoveTo(ptShadowSubStartTick);
                pDC->LineTo(ptShadowSubEndTick);
            }
        }

        //绘制刻度
        CRect ptScale;
        pDC->SetBkMode(TRANSPARENT);
        strScale.Format("%.0f", (m_dMaxValue - m_dMinValue) * i / m_nTicks);
        CSize size = pDC->GetTextExtent(strScale);
        pDC->SetTextColor(RGB(255, 255, 255));
        ptScale.SetRect(rect.left + rect.Width() * 2 / 3,
                        ptStartTick.y - size.cy / 2,
                        rect.right,
                        ptStartTick.y + size.cy / 2);
        pDC->DrawText(strScale, ptScale, DT_LEFT | DT_VCENTER | DT_SINGLELINE);
        pDC->SetTextColor(RGB(0, 0, 0));
        ptScale.DeflateRect(-1, -1, 1, 1);
        pDC->DrawText(strScale, ptScale, DT_LEFT | DT_VCENTER | DT_SINGLELINE);
    }

    pDC->SelectObject(pOldPen);
    pDC->SelectObject(pOldBrush);
    pDC->SelectObject(pOldFont);
    penThick.DeleteObject();
    penThin.DeleteObject();
    penShadow.DeleteObject();
    m_brushBack.DeleteObject();
    pBackBrush.DeleteObject();
    fScaleFont.DeleteObject();
    penScale.DeleteObject();
}

下面是温度计水银柱的绘制。
void CThermoMeter::DrawScale(CDC *pDC)
{
    if (m_dCurrentValue > m_dMaxValue)
    {
        m_dCurrentValue = m_dMaxValue;
    }
    else if (m_dCurrentValue < m_dMinValue)
    {
        m_dCurrentValue = m_dMinValue;
    }

    CRect rectScale;
    double yScale = (m_dCurrentValue - m_dMinValue) / (m_dMaxValue - m_dMinValue);
    rectScale.SetRect(m_rectScale.left,
                      m_rectScale.bottom - int(yScale * (m_rectScale.Height())),
                      m_rectScale.right,
                      m_rectScale.bottom);
    
    CBrush pBackBrush, *pOldBrush;
    pBackBrush.CreateSolidBrush(RGB(255, 0, 0));
    pOldBrush = (CBrush *)pDC->SelectObject(&pBackBrush);
    pDC->FillRect(&rectScale, &pBackBrush);

    pDC->SelectObject(pOldBrush);
    pBackBrush.DeleteObject();

    m_ctrlUnit.SetRect(GetUnitRect());
}

然后在工程中运用这个控件,我们就可以看到大略的效果了。如下:
按此在新窗口浏览图片

其中温度计显示的标识和实时值标识这里没有显示出来,各位有兴趣可以参考我前面的仪表绘制的实现方法添加实现就可以了。



--  作者:卷积内核
--  发布时间:4/17/2008 2:18:00 PM

--  
一个支持各种交叉等形状工业控制管道控件的实现

关键字 管道 工控 组态 控件
原作者姓名 戚高

介绍
    工业控制系统编程过程中,在模拟现场等环境系统中,管道控件是一个非常重要并且非常有用的控件。很多工业组态软件能够利用他的图形库制作出来非常漂亮的三维现场画面,这样给程序注入了很多的生机。而在个人编写的工业程序中,由于没有图形库等支持,那么显示就显得非常的单调。如果我们可以加入此类控件的支持,则会使得我们的程序变得更加的专业。

正文
    现在网络上也有一部分提供了管道控件的编程方法,不过基本上都是比较粗糙,效果不理想,而且在管道实体经常有各种的交叉等形状,而网络上面几乎找不到那种支持各种形状的管道控件(一般就提供了横向和纵向两种形状的支持)。利用这些控件完成的界面中,当那些复杂的拐角等区域就会显得非常的别扭。在分析和收集这些信息的基础上,我对管道等进行了一些控件的开发,完成了各种拐角等交叉形状的 编写。希望我的这些经验对从事这个方面或者寻求相关信息的人有帮助。
     首先是管道控件的实现。管道就是一种圆形的设备,从外观上看我们大致可以看到是一种中间向两边变暗的过程。在我的另一篇文章“一个工业控制管道流动控件的实现”中采用了渐变的方法实现了实体的绘制。那个适合于简单的管道的实现。比如横向和纵向等。但是在复杂的控件实现中,这种方法实现拐角等是非常的麻烦,所以我们不采用这种方法。
    我们在windows的画图程序中可以做这样的试验,我们用画刷采用不同的颜色绘制一些不同颜色的直线,当我们的颜色采用一种颜色向另外一种颜色渐变的时候就会产生视觉上的一种“凹凸”的效果。由于管道一般是黑白颜色,我们这里可以用黑白的大致渐变颜色进行绘制,显示效果见下:大致可以看出,用这种不同的颜色进行一些区域的绘制就可以形成那种视觉效果上的管道的绘制。


     上图所示的我们可以得到是一种能够类似横向管道的控件绘制的方法,那么同样道理,我们可以这样得到纵向管道的控件的绘制方法。当时在拐角的实现中,我们要进行一些别的处理。很多时候我们看到一些师傅进行实体连接的时候,当进行管道拐角的时候,需要将两根管道在接口处进行鞋45度的切割,然后就实现了不同管道的交叉了。我们可以这种方法进行交叉的处理就可以了。相信可以见后面的代码实现。
    通过对管道的分析,大致可以得到管道的一些通用的形状,大概氛围下面11种情况,左上的拐角、右上的拐角、右下的拐角、左下的拐角、竖向、横向、中央交叉、左边交叉、顶边交叉、上部交叉、底边交叉。通过设置控件的属性进行不同形状的绘制工作。我们可以在这里进行形状的大致抽象
//管道的形状
typedef enum _PipeFace
{
    PF_CORNER_LEFT_TOP     = 0,         //左上的拐角
    PF_CORNER_RIGHT_TOP       = 2,      //右上的拐角
    PF_CORNER_RIGHT_BOTTOM = 3,         //右下的拐角
    PF_CORNER_LEFT_BOTTOM  = 4,         //左下的拐角
    PF_VERTICAL               = 5,         //竖向
    PF_HORIZONTAL           = 6,         //横向
    PF_CROSS_CENTER        = 7,      //中央交叉
    PF_CROSS_LEFT          = 8,      //左边交叉
    PF_CROSS_TOP           = 9,      //顶边交叉
    PF_CROSS_RIGHT         = 10,     //上边交叉
    PF_CROSS_BOTTOM        = 11      //底边交叉
}PIPEFACE;

下面就是我们管道的绘制工作了,触发WM_PAINT消息完成绘制工作。
void CPipeCtrl::OnPaint()
{
    CPaintDC dc(this);
    
    RECT rcClient;
    GetClientRect(&rcClient);

    switch (m_enumFace)
    {
    case PF_HORIZONTAL:
        DrawHorizontal(&dc, rcClient);
        break;

    case PF_VERTICAL:
        DrawVertical(&dc, rcClient);
        break;

    case PF_CORNER_LEFT_TOP:
        DrawCornerLeftTop(&dc, rcClient);
        break;

    case PF_CORNER_RIGHT_TOP:
        DrawCornerRightTop(&dc, rcClient);
        break;

    case PF_CORNER_RIGHT_BOTTOM:
        DrawCornerRightBottom(&dc, rcClient);
        break;

    case PF_CORNER_LEFT_BOTTOM:
        DrawCornerLeftBottom(&dc, rcClient);
        break;

    case PF_CROSS_CENTER:
        DrawCrossCenter(&dc, rcClient);
        break;

    case PF_CROSS_TOP:
        DrawCrossTop(&dc, rcClient);
        break;

    case PF_CROSS_RIGHT:
        DrawCrossRight(&dc, rcClient);
        break;

    case PF_CROSS_BOTTOM:
        DrawCrossBottom(&dc, rcClient);
        break;

    case PF_CROSS_LEFT:
        DrawCrossLeft(&dc, rcClient);
        break;
    }
}

下面是关于管道的详细绘制方法代码,属于这个控件绘制的核心部分
void CPipeCtrl::DrawHorizontal(CDC *pDC, RECT &rectClient)
{
    RECT rcSect;
    CBrush pBackBrush, *pOldBrush;
    int nSectWidth = m_nWidth / SECTCOUNT;

    for (int i=0; i    {
        pBackBrush.CreateSolidBrush(GetSectColor(i));
        pOldBrush = (CBrush *)pDC->SelectObject(&pBackBrush);            
        SetRect(&rcSect, rectClient.left, rectClient.top + i * nSectWidth,
                rectClient.right, rectClient.top + (i + 1) * nSectWidth);
        pDC->FillRect(&rcSect, &pBackBrush);

        pDC->SelectObject(pOldBrush);
        pBackBrush.DeleteObject();
    }
}

void CPipeCtrl::DrawVertical(CDC *pDC, RECT &rectClient)
{
    RECT rcSect;
    CBrush pBackBrush, *pOldBrush;
    int nSectWidth = m_nWidth / SECTCOUNT;

    for (int i=0; i    {
        pBackBrush.CreateSolidBrush(GetSectColor(i));
        pOldBrush = (CBrush *)pDC->SelectObject(&pBackBrush);            
        SetRect(&rcSect, rectClient.left + i * nSectWidth, rectClient.top,
                rectClient.left + (i + 1) * nSectWidth, rectClient.bottom);
        pDC->FillRect(&rcSect, &pBackBrush);

        pDC->SelectObject(pOldBrush);
        pBackBrush.DeleteObject();
    }
}

void CPipeCtrl::DrawCornerLeftTop(CDC *pDC, RECT &rectClient)
{
    RECT rcSect;
    CBrush pBackBrush, *pOldBrush;
    int nSectWidth = m_nWidth / SECTCOUNT;

    for (int i=0; i    {         
        pBackBrush.CreateSolidBrush(GetSectColor(i));
        pOldBrush = (CBrush *)pDC->SelectObject(&pBackBrush);            
        //绘制横向
        SetRect(&rcSect, rectClient.left + i * nSectWidth, rectClient.top + i * nSectWidth,
                rectClient.right, rectClient.top + (i + 1) * nSectWidth);
        pDC->FillRect(&rcSect, &pBackBrush);
        //绘制竖向
        SetRect(&rcSect, rectClient.left + i * nSectWidth, rectClient.top + i * nSectWidth,
                rectClient.left + (i + 1) * nSectWidth, rectClient.bottom);
        pDC->FillRect(&rcSect, &pBackBrush);

        pDC->SelectObject(pOldBrush);
        pBackBrush.DeleteObject();         
    }
}

void CPipeCtrl::DrawCornerRightBottom(CDC *pDC, RECT &rectClient)
{
    RECT rcSect;
    CBrush pBackBrush, *pOldBrush;
    int nSectWidth = m_nWidth / SECTCOUNT;

    for (int i=0; i    {         
        pBackBrush.CreateSolidBrush(GetSectColor(i));
        pOldBrush = (CBrush *)pDC->SelectObject(&pBackBrush);            
        //绘制横向
        SetRect(&rcSect, rectClient.left, rectClient.bottom - (SECTCOUNT - i) * nSectWidth,
                rectClient.right - (SECTCOUNT - i) * nSectWidth, rectClient.bottom - (SECTCOUNT - i - 1) * nSectWidth);
        pDC->FillRect(&rcSect, &pBackBrush);
        //绘制竖向
        SetRect(&rcSect, rectClient.right - (SECTCOUNT - i) * nSectWidth, rectClient.top,
                rectClient.right - (SECTCOUNT - i - 1) * nSectWidth, rectClient.bottom - (SECTCOUNT - i - 1) * nSectWidth);
        pDC->FillRect(&rcSect, &pBackBrush);

        pDC->SelectObject(pOldBrush);
        pBackBrush.DeleteObject();         
    }
}

void CPipeCtrl::DrawCornerRightTop(CDC *pDC, RECT &rectClient)
{
    RECT rcSect;
    CBrush pBackBrush, *pOldBrush;
    int nSectWidth = m_nWidth / SECTCOUNT;

    for (int i=0; i    {         
        pBackBrush.CreateSolidBrush(GetSectColor(i));
        pOldBrush = (CBrush *)pDC->SelectObject(&pBackBrush);            
        //绘制横向
        SetRect(&rcSect, rectClient.left, rectClient.top + i * nSectWidth,
                rectClient.right - i * nSectWidth, rectClient.top + (i + 1) * nSectWidth);
        pDC->FillRect(&rcSect, &pBackBrush);
        //绘制竖向
        SetRect(&rcSect, rectClient.right - (SECTCOUNT -i) * nSectWidth, rectClient.top + (SECTCOUNT - i) * nSectWidth,
                rectClient.right - (SECTCOUNT - i - 1) * nSectWidth, rectClient.bottom);
        pDC->FillRect(&rcSect, &pBackBrush);

        pDC->SelectObject(pOldBrush);
        pBackBrush.DeleteObject();         
    }
}

void CPipeCtrl::DrawCornerLeftBottom(CDC *pDC, RECT &rectClient)
{
    RECT rcSect;
    CBrush pBackBrush, *pOldBrush;
    int nSectWidth = m_nWidth / SECTCOUNT;

    for (int i=0; i    {         
        pBackBrush.CreateSolidBrush(GetSectColor(i));
        pOldBrush = (CBrush *)pDC->SelectObject(&pBackBrush);            
        //绘制横向
        SetRect(&rcSect, rectClient.left + (SECTCOUNT - i) * nSectWidth, rectClient.bottom - (SECTCOUNT - i) * nSectWidth,
                rectClient.right, rectClient.bottom);
        pDC->FillRect(&rcSect, &pBackBrush);
        //绘制竖向
        SetRect(&rcSect, rectClient.left + i * nSectWidth, rectClient.top,
                rectClient.left + (i + 1) * nSectWidth, rectClient.bottom - i * nSectWidth);
        pDC->FillRect(&rcSect, &pBackBrush);

        pDC->SelectObject(pOldBrush);
        pBackBrush.DeleteObject();         
    }
}

void CPipeCtrl::DrawCrossCenter(CDC *pDC, RECT &rectClient)
{
    RECT rcSect;
    CBrush pBackBrush, *pOldBrush;
    int nSectWidth = m_nWidth / SECTCOUNT;
    int xCenter = rectClient.left + (rectClient.right - rectClient.left) / 2;
    int yCenter = rectClient.top + (rectClient.bottom - rectClient.top) / 2;
    int xLeft = xCenter - SECTCOUNT * nSectWidth / 2;
    int yTop = yCenter - SECTCOUNT * nSectWidth / 2;

    //绘制横向
    for (int i=0; i    {
        pBackBrush.CreateSolidBrush(GetSectColor(i));
        pOldBrush = (CBrush *)pDC->SelectObject(&pBackBrush);        
        SetRect(&rcSect, rectClient.left, yTop + i * nSectWidth,
                rectClient.right, yTop + (i + 1) * nSectWidth);
        pDC->FillRect(&rcSect, &pBackBrush);         
        
        pDC->SelectObject(pOldBrush);
        pBackBrush.DeleteObject();         
    }    

    //绘制竖向
    for (i=0; i<=SECTCOUNT / 2; i++)
    {
        pBackBrush.CreateSolidBrush(GetSectColor(i));
        pOldBrush = (CBrush *)pDC->SelectObject(&pBackBrush);
        //上左
        SetRect(&rcSect, xLeft + i * nSectWidth, rectClient.top,
                xLeft + (i + 1) * nSectWidth, yTop + i * nSectWidth);
        pDC->FillRect(&rcSect, &pBackBrush);     
        //上右
        SetRect(&rcSect, xLeft + (SECTCOUNT - i -1) * nSectWidth, rectClient.top,
                xLeft + (SECTCOUNT - i) * nSectWidth, yTop + i * nSectWidth);
        pDC->FillRect(&rcSect, &pBackBrush);
        //下左
        SetRect(&rcSect, xLeft + i * nSectWidth, yTop + (SECTCOUNT - i) * nSectWidth,
                xLeft + (i + 1) * nSectWidth, rectClient.bottom);
        pDC->FillRect(&rcSect, &pBackBrush);     
        //下右
        SetRect(&rcSect, xLeft + (SECTCOUNT - i -1) * nSectWidth, yTop + (SECTCOUNT - i) * nSectWidth,
                xLeft + (SECTCOUNT - i) * nSectWidth, rectClient.bottom);
        pDC->FillRect(&rcSect, &pBackBrush);
        
        pDC->SelectObject(pOldBrush);
        pBackBrush.DeleteObject();     
    }
}

void CPipeCtrl::DrawCrossTop(CDC *pDC, RECT &rectClient)
{
    RECT rcSect;
    CBrush pBackBrush, *pOldBrush;
    int nSectWidth = m_nWidth / SECTCOUNT;
    int xCenter = rectClient.left + (rectClient.right - rectClient.left) / 2;    

    //绘制横向
    for (int i=0; i    {
        pBackBrush.CreateSolidBrush(GetSectColor(i));
        pOldBrush = (CBrush *)pDC->SelectObject(&pBackBrush);        
        SetRect(&rcSect, rectClient.left, rectClient.top + i * nSectWidth,
                rectClient.right, rectClient.top + (i + 1) * nSectWidth);
        pDC->FillRect(&rcSect, &pBackBrush);         
        
        pDC->SelectObject(pOldBrush);
        pBackBrush.DeleteObject();         
    }    

    //绘制竖向
    int xLeft = xCenter - SECTCOUNT * nSectWidth / 2;
    for (i=0; i<=SECTCOUNT / 2; i++)
    {
        pBackBrush.CreateSolidBrush(GetSectColor(i));
        pOldBrush = (CBrush *)pDC->SelectObject(&pBackBrush);
        //下左
        SetRect(&rcSect, xLeft + i * nSectWidth, rectClient.top + (SECTCOUNT - i) * nSectWidth,
                xLeft + (i + 1) * nSectWidth, rectClient.bottom);
        pDC->FillRect(&rcSect, &pBackBrush);     
        //下右
        SetRect(&rcSect, xLeft + (SECTCOUNT - i - 1) * nSectWidth, rectClient.top + (SECTCOUNT - i) * nSectWidth,
                xLeft + (SECTCOUNT - i) * nSectWidth, rectClient.bottom);
        pDC->FillRect(&rcSect, &pBackBrush);
        
        pDC->SelectObject(pOldBrush);
        pBackBrush.DeleteObject();     
    }
}

void CPipeCtrl::DrawCrossRight(CDC *pDC, RECT &rectClient)
{
    RECT rcSect;
    CBrush pBackBrush, *pOldBrush;
    int nSectWidth = m_nWidth / SECTCOUNT;
    int yCenter = rectClient.top + (rectClient.bottom - rectClient.top) / 2;    

    //绘制竖向
    for (int i=0; i    {
        pBackBrush.CreateSolidBrush(GetSectColor(i));
        pOldBrush = (CBrush *)pDC->SelectObject(&pBackBrush);        
        SetRect(&rcSect, rectClient.right - (SECTCOUNT - i) * nSectWidth, rectClient.top,
                rectClient.right - (SECTCOUNT - i - 1) * nSectWidth, rectClient.bottom);
        pDC->FillRect(&rcSect, &pBackBrush);         
        
        pDC->SelectObject(pOldBrush);
        pBackBrush.DeleteObject();         
    }    

    //绘制横向
    int yTop = yCenter - SECTCOUNT * nSectWidth / 2;
    for (i=0; i<=SECTCOUNT / 2; i++)
    {
        pBackBrush.CreateSolidBrush(GetSectColor(i));
        pOldBrush = (CBrush *)pDC->SelectObject(&pBackBrush);
        //上
        SetRect(&rcSect, rectClient.left, yTop + i * nSectWidth,
                rectClient.right - (SECTCOUNT - i) * nSectWidth, yTop + (SECTCOUNT - i - 1) * nSectWidth);
        pDC->FillRect(&rcSect, &pBackBrush);     
        //下
        SetRect(&rcSect, rectClient.left, yTop + (SECTCOUNT - i) * nSectWidth,
                rectClient.right - (SECTCOUNT - i) * nSectWidth, yTop + (SECTCOUNT - i - 1) * nSectWidth);
        pDC->FillRect(&rcSect, &pBackBrush);
        
        pDC->SelectObject(pOldBrush);
        pBackBrush.DeleteObject();     
    }
}

void CPipeCtrl::DrawCrossBottom(CDC *pDC, RECT &rectClient)
{
    RECT rcSect;
    CBrush pBackBrush, *pOldBrush;
    int nSectWidth = m_nWidth / SECTCOUNT;
    int xCenter = rectClient.left + (rectClient.right - rectClient.left) / 2;    

    //绘制横向
    for (int i=0; i    {
        pBackBrush.CreateSolidBrush(GetSectColor(i));
        pOldBrush = (CBrush *)pDC->SelectObject(&pBackBrush);        
        SetRect(&rcSect, rectClient.left, rectClient.bottom - (i + 1) * nSectWidth,
                rectClient.right, rectClient.bottom - i * nSectWidth);
        pDC->FillRect(&rcSect, &pBackBrush);         
        
        pDC->SelectObject(pOldBrush);
        pBackBrush.DeleteObject();         
    }    

    //绘制竖向
    int xLeft = xCenter - SECTCOUNT * nSectWidth / 2;
    for (i=0; i<=SECTCOUNT / 2; i++)
    {
        pBackBrush.CreateSolidBrush(GetSectColor(i));
        pOldBrush = (CBrush *)pDC->SelectObject(&pBackBrush);
        //下左
        SetRect(&rcSect, xLeft + i * nSectWidth, rectClient.top,
                xLeft + (i + 1) * nSectWidth, rectClient.bottom - (SECTCOUNT - i) * nSectWidth);
        pDC->FillRect(&rcSect, &pBackBrush);     
        //下右
        SetRect(&rcSect, xLeft + (SECTCOUNT - i - 1) * nSectWidth, rectClient.top,
                xLeft + (SECTCOUNT - i) * nSectWidth, rectClient.bottom - (SECTCOUNT - i) * nSectWidth);
        pDC->FillRect(&rcSect, &pBackBrush);
        
        pDC->SelectObject(pOldBrush);
        pBackBrush.DeleteObject();     
    }
}

void CPipeCtrl::DrawCrossLeft(CDC *pDC, RECT &rectClient)
{
    RECT rcSect;
    CBrush pBackBrush, *pOldBrush;
    int nSectWidth = m_nWidth / SECTCOUNT;
    int yCenter = rectClient.top + (rectClient.bottom - rectClient.top) / 2;    

    //绘制竖向
    for (int i=0; i    {
        pBackBrush.CreateSolidBrush(GetSectColor(i));
        pOldBrush = (CBrush *)pDC->SelectObject(&pBackBrush);        
        SetRect(&rcSect, rectClient.left + (SECTCOUNT - i) * nSectWidth, rectClient.top,
                rectClient.left + (SECTCOUNT - i + 1) * nSectWidth, rectClient.bottom);
        pDC->FillRect(&rcSect, &pBackBrush);         
        
        pDC->SelectObject(pOldBrush);
        pBackBrush.DeleteObject();         
    }    

    //绘制横向
    int yTop = yCenter - SECTCOUNT * nSectWidth / 2;
    for (i=0; i<=SECTCOUNT / 2; i++)
    {
        pBackBrush.CreateSolidBrush(GetSectColor(i));
        pOldBrush = (CBrush *)pDC->SelectObject(&pBackBrush);
        //上
        SetRect(&rcSect, rectClient.left + (SECTCOUNT - i) * nSectWidth, yTop + i * nSectWidth,
                rectClient.right, yTop + (SECTCOUNT - i - 1) * nSectWidth);
        pDC->FillRect(&rcSect, &pBackBrush);     
        //下
        SetRect(&rcSect, rectClient.left + (SECTCOUNT - i) * nSectWidth, yTop + (SECTCOUNT - i) * nSectWidth,
                rectClient.right, yTop + (SECTCOUNT - i - 1) * nSectWidth);
        pDC->FillRect(&rcSect, &pBackBrush);
        
        pDC->SelectObject(pOldBrush);
        pBackBrush.DeleteObject();     
    }
}

到这里,我们的定义的各种形状就绘制完毕了,编译控件运行,我们可以看到下面的效果:


由于时间关系,这里控件就固定采用了黑白的模式,没有提供颜色动态修改等方法,然后控件的交叉大致实现了基本的各种交叉,可能还有一些别的形状等。





W 3 C h i n a ( since 2003 ) 旗 下 站 点
苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
687.500ms