1概述

最近在项目中需要显示实时监控的折线图,通过在网上搜索解决方案,发现了一个开源的基于MFC的类实现了Windows任务管理器性能使用记录的显示。在此基础上进行修改。在这里对该类进行详细的分析,并且把其实现原理进行分析,通过对原理分析,进而通过修改源码能够随心所欲的制作我们需要的使用记录窗口。

这是该类原主页,是codeproject上的,有兴趣的可以参考下。这是Mehdi Mousavi 先给他的爱人的,外国人都是这么浪漫~~O(_)O~

CHistogramCtrl, a windows 2000 like histogram control

下面对该类进行详细分析

首先我们分析一下任务管理器中显示CPU使用记录的表现,绿色网格在不停的向左滚动,在每次滚动中刷新出新的数据。表面上看上去是显示的曲线,但是实际上如果通过两点连线的方式,把每两个点之间的距离缩小,看上去非常像曲线。所以可以这样理解,每次绿色网格向做滚动时,最右面会出现一个点,这时会从原来的点向新出现的点画线。

分析完曲线形成的原理,下面想办法实现绿色网格的滚动显示。其实眼睛是可以被欺骗的,下面这种方法便可以实现看上去是滚动的效果。

看上图,方法时通过复制红色框住的部分然后向左移动一小段距离粘贴显示,然后重绘右边空出的一小条屏幕,这样让人感觉整个屏幕在向左不断的滚动。当然这里需要好好处理一下绿色线条如何重绘。对于复制的方法下面会详细解释。

2.实现方法

继续前面的介绍该功能如何实现,其实全部功能都通过一个类来实现的。即为CHistogramCtrl(原文的类),该类中主要封装了一些设置函数然后就是实现的方法。

其中最重要的函数就是create函数和drawline函数,前者是窗口的创造者,而在后者中实现了滚动效果及折线的显示。下面重点说一下这两部分的实现。

Create函数

对于create函数部分大家只需要了解一下其实现的方法即可,实际使用中并不需要对其进行更改。

   该类是继承了CWnd类的,他使用了父类CWndCreate方法创建了一个窗口,实际使用时,我们是通过创建一个静态STATIC来传到这个方法中进而在STATIC中创建出该窗口的。后面使用方法会详细介绍如何使用。

Drawline函数

该类中添加了一个定时器句柄,在定时器中定时执行Drawline函数,从而实现滚动效果。

在介绍Drawline之前,首先是要初始网格,在Invalidate里面进行的。

for(register i = m_rcClient.left - 1; i < m_rcClient.right; i += 40)

{//横向宽度为40个像素点

       m_pMemDC->MoveTo(i, m_rcClient.top);

       m_pMemDC->LineTo(i, m_rcClient.bottom);

}

 

for(register j = m_rcClient.top - 1; j < m_rcClient.bottom; j += 13)

{//纵向宽度为13个像素点

       m_pMemDC->MoveTo(m_rcClient.left, j);

       m_pMemDC->LineTo(m_rcClient.right, j);

}

其中标红的部分时控制的初始网格宽度,本人希望在后面对这个类进行更改,增加更多的外部接口,从而使用更加方便,即不用过多的更改源代码。下面具体介绍Drawline函数的实现。

首先就是通过下面这段代码实现前面提到的区域拷贝并复制。

       //该矩形为每次定时器刷新 需要重绘的举行,跟每次刷新的像素数有关,现在是每次刷新10个像素

       CRect bkRect(m_rcClient.right - 20, m_rcClient.top, m_rcClient.right, m_rcClient.bottom);

       CBrush bkBrush;

       bkBrush.CreateSolidBrush(m_crBackGround);

       m_pMemDC->FillRect(bkRect, &bkBrush);

       //获取内存中DC,并将其向左错位复制到显示DC,其实倒数第三个参数就是其错位的像素数,现在错位10个像素

       //通过该机制使得屏幕实现向左推移的效果。

       m_pMemDC->BitBlt(0, 0, m_rcClient.Width(), m_rcClient.Height(), m_pMemDC, 20, 0, SRCCOPY);

其中m_rcClient就是去的整个显示窗口的区域。这段代码的重点就是CDC的一个成员函数BitBlt,该函数实现了从源设备上下文拷贝位图到这个当前设备上下文。可以Google一下该函数,具体用法如下:

BOOL   BitBlt(   int   x,   int   y,   int   nWidth,   int   nHeight,   CDC*   pSrcDC,   int   xSrc,   int   ySrc,   DWORD   dwRop   );

返回值:函数成功,返回非零值,否则为0

参数:   x   指定目标矩形左上角的逻辑x坐标。    

y   指定目标矩形左上角的逻辑y坐标。    

nWidth   指定目标矩形和源位图的宽度(逻辑单位)。    

nHeight   指定目标矩形和源位图的高度(逻辑单位)。    

pSrcDC   指向CDC对象的指针,标识待拷贝位图的设备上下文。如果dwRop指定不包括源的光栅操作,则它必须为NULL    

xSrc   指定源位图左上角的逻辑X坐标。    

ySrc   指定源位图左上角的逻辑Y坐标。    

dwRop   指定要执行的光栅操作。光栅操作代码定义GDC如何合并输出操作中的颜色,包括当前画刷、可能的源位图和目标位图。

通过函数参数的介绍可知,程序中BitBlt函数中的倒数第三个参数20正式每次向左移动的像素数。至此实现了拷贝,下面就是剩下那部分区域的重绘了。

重绘也很简单,无非分两部分,一部分是画网格,另一部分就是画我们所需要的折线。

画网格:这一部分画横线很简单,跟前面一致就可以了,但是画竖线需要计算一下在那个位置画,因为必须保证画出来后和前面拼接后仍然保持网格状,我认为这是本程序最难处理的部分。代码如下:

       //m_nFirstLinePos 初始为宽度,每次要减去想做错位的像素数,

       m_nFirstLinePos -= 20;

       if(m_nFirstLinePos < 0)

              m_nFirstLinePos += 40;  

//画新刷出来的竖线

       int nX = m_rcClient.right - ((m_rcClient.right - m_nFirstLinePos) % 40) - 1;

       m_pMemDC->MoveTo(nX, m_rcClient.top);

       m_pMemDC->LineTo(nX, m_rcClient.bottom);

这一部分我是通过反复画图,尝试才最终明白原理,原来左面网格是对齐的,通过移动后,会出现左右分别有不完成的网格,而中间是完整的网格,而左边不完整的部分是可以计算的,就是用网格的宽度减去错位的宽度。这时用总的长度减去左半部分不足一个网格的宽度后,再对网格宽度取模,剩下的部分就是右边不足一个网格宽度,用总的减去它,就是最右边那条线应该画的位置,如果在应该重绘的区域内就画出来,如果不在就不画。-1是为了应付如果正好在边框处,这样也可以显示。

画折线:这部分比较简单了,代码如下:

       for(int k=0; k<m_nLineNum ;k++)

       {

              m_pMemDC->SelectObject(m_colorPenAll[k]);

              //要减去 错位的像素

              m_pMemDC->MoveTo(m_rcClient.right - 25 - 20, m_yPreviousPosAll[k]);

              m_pMemDC->LineTo(m_rcClient.right - 25, m_yPosAll[k]);

              m_yPreviousPosAll[k] = m_yPosAll[k];

       }

原类只实现了一条折线,本类能够实现多条线,其实就是通过设置一下线的数目,然后用一个数组存储各个位置。

3补充说明一些问题

原文中提到使用了一个CList存储位置,是为了如果在我们定时器刷新显示的过程中有多个数据传到我们的位置数据结构中,我们会全部保存下来,然后在下次刷新显示时,取所有数据的平均值进行显示。

本类没有使用,当然这也是根据实际需要进行的,我需要的是最新值,不关心个别值,而且也可以通过控制刷新频率减小误差。

4使用方法

原文对使用方法有了很详细的注解,在此不对设置函数进行解释,只对如何将该类显示在我们静态控件中。

使用起来非常简单。

1。在对话框中加入一个静态控件,命名为IDC_STATIC_HISTOGRAM,然后在对话框初始化函数中(如果没有自己添加句柄)定义

CHistogramCtrl  m_ctrlHistogram;

然后添加如下代码即可显示

CRect rect;

// 获取静态控件的举行

GetDlgItem(IDC_STATIC_HISTOGRAM)->GetWindowRect(rect);

// 将该矩形转化为客户窗口

ScreenToClient(rect);

// 调用Create函数,并将上面的矩形传入

m_ctrlHistogram.Create(WS_VISIBLE | WS_CHILD

      | WS_TABSTOP, rect, this, IDC_STATIC_HISTOGRAM);

这是就会在静态框中显示滚动的网格了

当然如果你要想显示折线,需要定时的使用SetPos()设置每个时刻的位置,形成折线。我的类中重载了该方法,可以设置多条线的位置。

 

CHistogramCtrl就介绍这么多,等有空后再把该类好好修改下,传上来供大家参考。

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐