C#封装类似任务管理器CPU使用记录图

  在逛CodeProject的时候,偶然发现了一个老外写的代码,里面有一个自定义的用户控件,类似任务管理器里面CPU使用记录的图表,如下截图:

  

  因为自己之前没有做过这样的图表,觉得很赞,所以将这个用户控件给抠了下来,做了一个小的demo,接下来我就分析下这个用户控件的实现过程。

  先看代码:

/// <summary>
/// Summary description for UsageHistoryControl.
/// </summary>
public class UsageHistoryControl : UserControl
{
        /// <summary> 
        /// Required designer variable.
        /// </summary>
        private Container components = null;

        private const int squareWidth = 12;
        private const int maxLastValuesCount = 2000;
        private const int shiftStep = 3;

        private int shift = 0;

        private int [] lastValues1 = new int[maxLastValuesCount];
        private int [] lastValues2 = new int[maxLastValuesCount];
        private int nextValueIndex;
        private int lastValuesCount;

        private int max = 100;
        private int min = 0;

        public UsageHistoryControl()
        {
            // This call is required by the Windows.Forms Form Designer.
            InitializeComponent();

            EnableDoubleBuffering();
            Reset();
        }

        public void Reset()
        {
            lastValues1.Initialize();
            lastValues2.Initialize();
            nextValueIndex = 0;
            lastValuesCount = 0;
            Invalidate();
        }

        /// <summary> 
        /// Clean up any resources being used.
        /// </summary>
        protected override void Dispose( bool disposing )
        {
            if( disposing )
            {
                if(components != null)
                {
                    components.Dispose();
                }
            }
            base.Dispose( disposing );
        }

        #region Component Designer generated code
        /// <summary> 
        /// Required method for Designer support - do not modify 
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            // 
            // UsageHistoryControl
            // 
            this.BackColor = System.Drawing.Color.Black;
            this.Name = "UsageHistoryControl";

        }
        #endregion

        private void EnableDoubleBuffering()
        {
            // Set the value of the double-buffering style bits to true.
            SetStyle(ControlStyles.DoubleBuffer | 
                ControlStyles.UserPaint | 
                ControlStyles.AllPaintingInWmPaint,
                true);
            UpdateStyles();
        }

        protected override void OnResize(EventArgs e)
        {
            // Invalidate the control to get a repaint.
            Invalidate();
        }

        //在任何需要引发Paint事件时,都会执行这个方法,如大小变动,invalidate()等 
        protected override void OnPaint(PaintEventArgs e)
        {
            Graphics g = e.Graphics;

            for(int i = 0; i <= ClientRectangle.Width+squareWidth; i += squareWidth)
            {
                g.DrawLine(Pens.Green, i-shift, 0, i-shift, ClientRectangle.Height);
            }

            for(int i = 0; i < ClientRectangle.Height; i += squareWidth)
            {
                g.DrawLine(Pens.Green, 0, i, ClientRectangle.Width, i);
            }

            int startValueIndex = (nextValueIndex-1+maxLastValuesCount)%maxLastValuesCount;

            int prevVal1 = GetRelativeValue(lastValues1[startValueIndex]);
            int prevVal2 = GetRelativeValue(lastValues2[startValueIndex]);

            for(int i = 1; i < lastValuesCount; ++i)
            {
                int index = nextValueIndex - 1 - i;
                if (index < 0)
                {
                    index += maxLastValuesCount;
                }

                int val1 = GetRelativeValue(lastValues1[index]);
                int val2 = GetRelativeValue(lastValues2[index]);

                g.DrawLine(
                    Pens.Red,
                    ClientRectangle.Width - (i - 1) * shiftStep, ClientRectangle.Height - prevVal2,
                    ClientRectangle.Width - i * shiftStep, ClientRectangle.Height - val2);

                g.DrawLine(
                    Pens.LawnGreen,
                    ClientRectangle.Width-(i-1)*shiftStep, ClientRectangle.Height-prevVal1,
                    ClientRectangle.Width-i*shiftStep, ClientRectangle.Height-val1);

                prevVal1 = val1;
                prevVal2 = val2;
            }
        }

        private int GetRelativeValue(int val)
        {
            int result = val * (ClientRectangle.Height-2) / max + 1;
            return result;
        }

        public void AddValues(int val1,int val2)
        {
            lastValues1[nextValueIndex] = val1;
            lastValues2[nextValueIndex] = val2;
            
            nextValueIndex++;
            nextValueIndex %= maxLastValuesCount;
            lastValuesCount++;
            if (lastValuesCount > maxLastValuesCount)
            {
                lastValuesCount = maxLastValuesCount;
            }
            
            shift += shiftStep;
            //shift %= squareWidth; 
            shift = shift % squareWidth; 

            Invalidate();
        }

        public int Maximum
        {
            get
            {
                return max;
            }

            set
            {
                // Make sure that the maximum value is never set lower than the minimum value.
                if (value < min)
                {
                    min = value;
                }

                max = value;

                // Invalidate the control to get a repaint.
                Invalidate();
            }
        }

}

  注意到构造函数中的方法“EnableDoubleBuffering() ”,它的作用是启用GDI+的双缓冲,可以有效防止界面在重绘的时候发生闪烁。关于双缓冲的介绍可以参考这里:http://www.cnblogs.com/bnuvincent/archive/2009/08/04/1538484.html,http://blog.csdn.net/gisfarmer/article/details/4366707。另一个方法Reset() 就是初始化一些变量,不过其中值得一提的就是”值类型的默认构造函数“,这个大家平时可能很少见,最多的就是引用类型的构造函数,比喻说类。

  具体实现画线的方法就是重写了基类的OnPaint方法,因为在实现双缓冲的时候必须要重写这个方法。该方法先是重绘原生的界面,上面没有任何的线条,然后在更具传进来的参数画线,不过这里

for(int i = 1; i < lastValuesCount; ++i){}

感觉不是很好,随着lastValuesCount的值越来越大,每次循环的次数就越来越多了,因为从未做过类似的事情,不知画图是不是真的这样?

  具体调用就很简单了,因为要实现类似CPU那样的使用记录,所以在调用界面上拖了一个时间控件,在该控件的Tick事件里面传值

private void timer1_Tick(object sender, EventArgs e)
{
  Random rand = new Random();
  this.usageHistoryControl1.AddValues(rand.Next(100),rand.Next(50,100));
}

  哦,原来这个图是这样实现的,想当初学习GDI+的时候,就怎么没有想到做一个这样的Demo了?

  老外文章地址:http://www.codeproject.com/Articles/7933/Smart-Thread-Pool 

  示例代码下载:https://files.cnblogs.com/wucj/DrawLine.rar

原文地址:https://www.cnblogs.com/wucj/p/3106106.html