在当前的不少电商或者物流等应用程序中,为了清晰的表明某些事件的当前状态,以及历史时序记录情况,经常可以看到一个步骤条控件,它分成几个节点,每个节点代表一个核心状态,每个状态之间通过线条进行连接,以经过的节点高亮显示,未经过的线条灰度显示。其中,每个节点下可以通过文字进行简要的描述。本文将利用C#中的GDI+技术,自动绘制相关的UI元素,实现Window Form的步骤条控件。
1 项目结构
利用Visual Studio 社区版,创建一个Window应用程序项目WinControls,其中在资源文件中添加一个图形,用于绘制经过步骤条节点的✔ 状态。并添加几个类,具体项目结构如下图所示:
其中的check_lightblue.png图片代表的是✔ 状态。可以通过Properties.Resources.check_lightblue进行访问。eumStepState.cs是一个枚举类型,表示节点的状态信息,核心代码如下:
namespaceWinControls{ publicenumeumStepState { Waiting, Completed, OutTime } }
而 StepEntity.cs 文件是代表一个步骤条节点的实体对象,其中具备的属性有节点ID,节点名称,节点状态,节点顺序,节点描述等,核心代码如下:
usingSystem; usingSystem.Collections.Generic; usingSystem.Linq; usingSystem.Text; usingSystem.Threading.Tasks; namespaceWinControls{ publicclassStepEntity { publicstringId { get; set; } publicstringStepName { get; set; } publicintStepOrder { get; set; } publiceumStepStateStepState { get; set; } publicstringStepDesc { get; set; } publicobjectStepTag { get; set; } publicStepEntity(stringid, stringstepname, intsteporder, stringstepdesc, eumStepStatestepstate, objecttag) { this.Id=id; this.StepName=stepname; this.StepOrder=steporder; this.StepDesc=stepdesc; this.StepTag=tag; this.StepState=stepstate; } } }
2 步骤条实现
在项目WinControls中添加一个名为StepViewer的用户控件,具体如下图所示:
核心代码如下:
usingSystem; usingSystem.Collections.Generic; usingSystem.ComponentModel; usingSystem.Data; usingSystem.Drawing; usingSystem.Linq; usingSystem.Text; usingSystem.Threading.Tasks; usingSystem.Windows.Forms; namespaceWinControls{ publicpartialclassStepViewer : UserControl { publicStepViewer() { InitializeComponent(); this.Height=68; this.Paint+=StepViewer_Paint; } privateList<StepEntity>_dataSourceList=null; privateColor_Gray=Color.FromArgb(189, 195, 199); privateColor_DarkGray=Color.FromArgb(149, 165, 166); privateColor_Blue=Color.FromArgb(52, 152, 219); privateColor_Red=Color.FromArgb(231, 76, 60); [Browsable(true), Category("StepViewer")] publicList<StepEntity>ListDataSource { get { return_dataSourceList; } set { if (_dataSourceList!=value) { _dataSourceList=value; Invalidate(); } } } privateint_currentStep=0; publicintCurrentStep { get { return_currentStep; } set { if (_currentStep!=value) { _currentStep=value; Invalidate(); } } } privatevoidStepViewer_Paint(objectsender, PaintEventArgse) { if (this.ListDataSource!=null) { intCenterY=this.Height/2; intindex=1; intcount=ListDataSource.Count; intlineWidth=120; intStepNodeWH=28; //this.Width = 32 * count + lineWidth * (count - 1) + 6+300;//defalut pen & brushe.Graphics.SmoothingMode=System.Drawing.Drawing2D.SmoothingMode.HighQuality; Brushbrush=newSolidBrush(_Gray); Penp=newPen(brush, 1f); BrushbrushNode=newSolidBrush(_DarkGray); PenpenNode=newPen(brushNode, 1f); BrushbrushNodeCompleted=newSolidBrush(_Blue); PenpenNodeCompleted=newPen(brushNodeCompleted, 1f); intinitX=6; //stringFontnFont=newFont("微软雅黑", 12); FontstepFont=newFont("微软雅黑", 11, FontStyle.Bold); intNodeNameWidth=0; foreach (variteminListDataSource) { //roundRectanglerec=newRectangle(initX, CenterY-StepNodeWH/2, StepNodeWH, StepNodeWH); if (CurrentStep==item.StepOrder) { if (item.StepState==eumStepState.OutTime) { e.Graphics.DrawEllipse(newPen(_Red, 1f), rec); e.Graphics.FillEllipse(newSolidBrush(_Red), rec); } else { e.Graphics.DrawEllipse(penNodeCompleted, rec); e.Graphics.FillEllipse(brushNodeCompleted, rec); } //白色字体SizeFfTitle=e.Graphics.MeasureString(index.ToString(), stepFont); PointpTitle=newPoint(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); //nodeNameSizeFsNode=e.Graphics.MeasureString(item.StepName, nFont); PointpNode=newPoint(initX+StepNodeWH, CenterY- (int)Math.Round(sNode.Height/2) +2); e.Graphics.DrawString(item.StepName, newFont(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); } } elseif (item.StepOrder<CurrentStep) { //completede.Graphics.DrawEllipse(penNodeCompleted, rec); //imageRectangleFrecRF=newRectangleF(rec.X+6, rec.Y+6, rec.Width-12, rec.Height-12); e.Graphics.DrawImage(Properties.Resources.check_lightblue, recRF); //nodeNameSizeFsNode=e.Graphics.MeasureString(item.StepName, nFont); PointpNode=newPoint(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); //SizeFfTitle=e.Graphics.MeasureString(index.ToString(), stepFont); PointpTitle=newPoint(initX+StepNodeWH/2- (int)Math.Round(fTitle.Width) /2, CenterY- (int)Math.Round(fTitle.Height/2)); e.Graphics.DrawString(index.ToString(), stepFont, brush, pTitle); //nodeNameSizeFsNode=e.Graphics.MeasureString(item.StepName, nFont); PointpNode=newPoint(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) { //linee.Graphics.DrawLine(p, initX+StepNodeWH+NodeNameWidth, CenterY, initX+StepNodeWH+NodeNameWidth+lineWidth, CenterY); } } //描述信息if (item.StepDesc!="") { PointpNode=newPoint(initX+StepNodeWH, CenterY+10); e.Graphics.DrawString(item.StepDesc, newFont(nFont.FontFamily, 10), brush, pNode); } index++; //8 is space widthinitX=initX+lineWidth+StepNodeWH+NodeNameWidth+8; } } } } }
其中,首先定义了一组颜色,代码如下:
privateColor_Gray=Color.FromArgb(189, 195, 199); privateColor_DarkGray=Color.FromArgb(149, 165, 166); privateColor_Blue=Color.FromArgb(52, 152, 219); privateColor_Red=Color.FromArgb(231, 76, 60);
其次,由于步骤条的节点有多个,是一个列表,因此这里用private List<StepEntity> _dataSourceList = null;进行定义一个数据源。[Browsable(true), Category("StepViewer")]则表示ListDataSource属性在控件的属性列表中可见。在赋值后会调用 Invalidate()方法进行UI重绘。
再次,此控件初始化时,执行如下代码:
publicStepViewer() { InitializeComponent(); this.Height=68; this.Paint+=StepViewer_Paint; }
即限定控件的高度为68,同时绑定绘制事件Paint,实现绘制的方法为 StepViewer_Paint,这是控件的核心,其中使用了 e.Graphics下的API可以绘制圆形,线条和图片,以及文本信息。
3 步骤条效果
将控件添加到Form1窗口上并在初始化方法中维护数据源信息,核心代码如下:
usingSystem; usingSystem.Collections.Generic; usingSystem.ComponentModel; usingSystem.Data; usingSystem.Drawing; usingSystem.Linq; usingSystem.Text; usingSystem.Threading.Tasks; usingSystem.Windows.Forms; namespaceWinControls{ publicpartialclassForm1 : Form { publicForm1() { InitializeComponent(); this.BackColor=Color.White; } privatevoidForm1_Load(objectsender, EventArgse) { List<StepEntity>list=newList<StepEntity>(); list.Add(newStepEntity("1", "新开单", 1, "这里是该步骤的描述信息", eumStepState.Completed, null)); list.Add(newStepEntity("2", "主管审批", 2, "这里是该步骤的描述信息", eumStepState.Waiting, null)); list.Add(newStepEntity("3", "总经理审批", 3, "这里是该步骤的描述信息", eumStepState.OutTime, null)); list.Add(newStepEntity("2", "完成", 4, "这里是该步骤的描述信息", eumStepState.Waiting, null)); this.stepViewer1.CurrentStep=3; this.stepViewer1.ListDataSource=list; } privatevoidbutton1_Click(objectsender, EventArgse) { this.stepViewer1.CurrentStep--; } privatevoidbutton2_Click(objectsender, EventArgse) { this.stepViewer1.CurrentStep++; } } }
执行项目,Form1界面具体如下图所示: