Window Form步骤条控件实现

在当前的不少电商或者物流等应用程序中,为了清晰的表明某些事件的当前状态,以及历史时序记录情况,经常可以看到一个步骤条控件,它分成几个节点,每个节点代表一个核心状态,每个状态之间通过线条进行连接,以经过的节点高亮显示,未经过的线条灰度显示。其中,每个节点下可以通过文字进行简要的描述。本文将利用C#中的GDI+技术,自动绘制相关的UI元素,实现Window Form的步骤条控件。

1 项目结构


    利用Visual Studio 社区版,创建一个Window应用程序项目WinControls,其中在资源文件中添加一个图形,用于绘制经过步骤条节点的✔ 状态。并添加几个类,具体项目结构如下图所示:

Window Form步骤条控件实现

其中的check_lightblue.png图片代表的是✔ 状态。可以通过Properties.Resources.check_lightblue进行访问。eumStepState.cs是一个枚举类型,表示节点的状态信息,核心代码如下:

namespace WinControls
{
    public enum eumStepState
    {
        Waiting,
        Completed,
        OutTime
    }
}

而 StepEntity.cs 文件是代表一个步骤条节点的实体对象,其中具备的属性有节点ID,节点名称,节点状态,节点顺序,节点描述等,核心代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WinControls
{
    public class StepEntity
    {
        public string Id { get; set; }
        public string StepName { get; set; }
        public int StepOrder { get; set; }
        public eumStepState StepState { get; set; }
        public string StepDesc { get; set; }
        public object StepTag { get; set; }
        public StepEntity(string id, string stepname, int steporder, 
                          string stepdesc, eumStepState stepstate, object tag)
        {
            this.Id = id;
            this.StepName = stepname;
            this.StepOrder = steporder;
            this.StepDesc = stepdesc;
            this.StepTag = tag;
            this.StepState = stepstate;
        }
    }
}

2 步骤条实现


    在项目WinControls中添加一个名为StepViewer的用户控件,具体如下图所示:

Window Form步骤条控件实现

核心代码如下:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WinControls
{
    public partial class StepViewer : UserControl
    {
        public StepViewer()
        {
            InitializeComponent();
            this.Height = 68;
            this.Paint += StepViewer_Paint;
        }
        private List<StepEntity> _dataSourceList = null;
        private Color _Gray = Color.FromArgb(189, 195, 199);
        private Color _DarkGray = Color.FromArgb(149, 165, 166);
        private Color _Blue = Color.FromArgb(52, 152, 219);
        private Color _Red = Color.FromArgb(231, 76, 60);

        [Browsable(true), Category("StepViewer")]
        public List<StepEntity> ListDataSource
        {
            get
            {
                return _dataSourceList;
            }
            set
            {
                if (_dataSourceList != value)
                {
                    _dataSourceList = value;
                    Invalidate();
                }
            }
        }
        private int _currentStep = 0;
        public int CurrentStep {
            get
            {
                return _currentStep;
            }
            set
            {
                if (_currentStep != value)
                {
                    _currentStep = value;
                    Invalidate();
                }
            }
        }

      
        private void StepViewer_Paint(object sender, PaintEventArgs e)
        {
            if (this.ListDataSource != null)
            {
                int CenterY = this.Height / 2;
                int index = 1;
                int count = ListDataSource.Count;
                int lineWidth = 120;
                int StepNodeWH = 28;
                //this.Width = 32 * count + lineWidth * (count - 1) + 6+300;
                //defalut pen & brush
                e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
                Brush brush = new SolidBrush(_Gray);
                Pen p = new Pen(brush, 1f);
                Brush brushNode = new SolidBrush(_DarkGray);
                Pen penNode = new Pen(brushNode, 1f);

                Brush brushNodeCompleted = new SolidBrush(_Blue);
                Pen penNodeCompleted = new Pen(brushNodeCompleted, 1f);


                int initX = 6;
                //string
                Font nFont = new Font("微软雅黑", 12);
                Font stepFont = new Font("微软雅黑", 11, FontStyle.Bold);
                int NodeNameWidth = 0;

                foreach (var item in ListDataSource)
                {

                    //round

                    Rectangle rec = new Rectangle(initX, CenterY - StepNodeWH / 2, StepNodeWH, StepNodeWH);
                    if (CurrentStep == item.StepOrder)
                    {
                        if (item.StepState == eumStepState.OutTime)
                        {
                            e.Graphics.DrawEllipse(new Pen(_Red, 1f), rec);
                            e.Graphics.FillEllipse(new SolidBrush(_Red), rec);
                        }
                        else
                        {
                            e.Graphics.DrawEllipse(penNodeCompleted, rec);
                            e.Graphics.FillEllipse(brushNodeCompleted, rec);
                        }

                        //白色字体
                        SizeF fTitle = e.Graphics.MeasureString(index.ToString(), stepFont);
                        Point pTitle = new Point(initX + StepNodeWH / 2 - (int)Math.Round(fTitle.Width) / 2, CenterY - (int)Math.Round(fTitle.Height / 2));
                        e.Graphics.DrawString(index.ToString(), stepFont, Brushes.White, pTitle);


                        //nodeName
                        SizeF sNode = e.Graphics.MeasureString(item.StepName, nFont);
                        Point pNode = new Point(initX + StepNodeWH, CenterY - (int)Math.Round(sNode.Height / 2) + 2);

                        e.Graphics.DrawString(item.StepName, new Font(nFont, FontStyle.Bold), brushNode, pNode);
                        NodeNameWidth = (int)Math.Round(sNode.Width);
                        if (index < count)
                        {
                            e.Graphics.DrawLine(p, initX + StepNodeWH + NodeNameWidth, CenterY, initX + StepNodeWH + NodeNameWidth + lineWidth, CenterY);
                        }

                    }
                    else if (item.StepOrder < CurrentStep)
                    {
                        //completed
                        e.Graphics.DrawEllipse(penNodeCompleted, rec);
                        //image
                        RectangleF recRF = new RectangleF(rec.X + 6, rec.Y + 6, rec.Width - 12, rec.Height - 12);
                        e.Graphics.DrawImage(Properties.Resources.check_lightblue, recRF);
                        
                        //nodeName
                        SizeF sNode = e.Graphics.MeasureString(item.StepName, nFont);
                        Point pNode = new Point(initX + StepNodeWH, CenterY - (int)Math.Round(sNode.Height / 2) + 2);
                        e.Graphics.DrawString(item.StepName, nFont, brushNode, pNode);
                        NodeNameWidth = (int)Math.Round(sNode.Width);

                        if (index < count)
                        {
                            e.Graphics.DrawLine(penNodeCompleted, initX + StepNodeWH + NodeNameWidth, CenterY, initX + StepNodeWH + NodeNameWidth + lineWidth, CenterY);
                        }

                    }
                    else
                    {
                        e.Graphics.DrawEllipse(p, rec);
                        //
                        SizeF fTitle = e.Graphics.MeasureString(index.ToString(), stepFont);
                        Point pTitle = new Point(initX + StepNodeWH / 2 - (int)Math.Round(fTitle.Width) / 2, CenterY - (int)Math.Round(fTitle.Height / 2));
                        e.Graphics.DrawString(index.ToString(), stepFont, brush, pTitle);
                        //nodeName
                        SizeF sNode = e.Graphics.MeasureString(item.StepName, nFont);
                        Point pNode = new Point(initX + StepNodeWH, CenterY - (int)Math.Round(sNode.Height / 2) + 2);
                        e.Graphics.DrawString(item.StepName, nFont, brushNode, pNode);
                        NodeNameWidth = (int)Math.Round(sNode.Width);
                        if (index < count)
                        {
                            //line
                            e.Graphics.DrawLine(p, initX + StepNodeWH + NodeNameWidth, CenterY, initX + StepNodeWH + NodeNameWidth + lineWidth, CenterY);
                        }
                    }

                    //描述信息
                    if (item.StepDesc != "")
                    {
                        Point pNode = new Point(initX + StepNodeWH, CenterY + 10);
                        e.Graphics.DrawString(item.StepDesc, new Font(nFont.FontFamily, 10), brush, pNode);
                    }
                    index++;
                    //8 is space width
                    initX = initX + lineWidth + StepNodeWH + NodeNameWidth + 8;
                }
            }
        }
    }
}

其中,首先定义了一组颜色,代码如下:

 private Color _Gray = Color.FromArgb(189, 195, 199); 
 private Color _DarkGray = Color.FromArgb(149, 165, 166);
 private Color _Blue = Color.FromArgb(52, 152, 219);
 private Color _Red = Color.FromArgb(231, 76, 60);

其次,由于步骤条的节点有多个,是一个列表,因此这里用private List<StepEntity> _dataSourceList = null;进行定义一个数据源。[Browsable(true), Category("StepViewer")]则表示ListDataSource属性在控件的属性列表中可见。在赋值后会调用 Invalidate()方法进行UI重绘。

再次,此控件初始化时,执行如下代码:

 public StepViewer()
 {
      InitializeComponent();
      this.Height = 68;
      this.Paint += StepViewer_Paint;
 }

即限定控件的高度为68,同时绑定绘制事件Paint,实现绘制的方法为 StepViewer_Paint,这是控件的核心,其中使用了  e.Graphics下的API可以绘制圆形,线条和图片,以及文本信息。

3 步骤条效果


    将控件添加到Form1窗口上并在初始化方法中维护数据源信息,核心代码如下:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WinControls
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            this.BackColor = Color.White;
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            List<StepEntity> list = new List<StepEntity>();
            list.Add(new StepEntity("1", "新开单", 1, "这里是该步骤的描述信息", eumStepState.Completed, null));
            list.Add(new StepEntity("2", "主管审批", 2, "这里是该步骤的描述信息", eumStepState.Waiting, null));
            list.Add(new StepEntity("3", "总经理审批", 3, "这里是该步骤的描述信息", eumStepState.OutTime, null));
            list.Add(new StepEntity("2", "完成", 4, "这里是该步骤的描述信息", eumStepState.Waiting, null));

            this.stepViewer1.CurrentStep = 3;
            this.stepViewer1.ListDataSource = list;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            this.stepViewer1.CurrentStep--;
        }

        private void button2_Click(object sender, EventArgs e)
        {
            this.stepViewer1.CurrentStep++;
        }
    }
}

执行项目,Form1界面具体如下图所示:

Window Form步骤条控件实现

上一篇:《Springboot极简教程》MappingMongoConverter:Failed to convert from type [java.lang.String] to type [long] for value 'null'; nested exception is java.lang.Il


下一篇:2013最好的PHP开发框架