1,效果。
2,绘制技巧。
1,流动块的实质是使用Pen的自定义DashStyle绘制的线,并使用线的偏移值呈现出流动的效果。
Pen barPen = new Pen(BarColor, BarHeight);
barPen.DashStyle = DashStyle.Custom;
barPen.DashOffset = startOffset;
barPen.DashPattern = new float[] { BarLength, GapLength };
graphicsPath.StartFigure();
g.DrawPath(barPen, graphicsPath);
2,使用GraphicsPath,绘制包含弧的多段线。
GraphicsPath graphicsPath = new GraphicsPath();
//特别需要注意角度旋转方向,如果角度旋转方向选错就形成闭环
graphicsPath.AddArc(new Rectangle(this.Height / 2, this.Height / 2 * (-1) - 1, this.Height, this.Height), 180.0f, -90.0f);
3,对于扇形区域使用渐变色时需要使用PathGradientBrush画刷而不是 LinearGradientBrush, LinearGradientBrush画刷无法实现从圆心到边缘的颜色渐变。
void PaintRectangle(Graphics g, Rectangle rec, PointF[] points, LinearGradientMode linearGradientMode = LinearGradientMode.Vertical)
{
//画刷效果呈现线型分布,注意与PathGradientBrush的区别
LinearGradientBrush brush = new LinearGradientBrush(rec, PipeEdgeColor, PipeCenterColor, linearGradientMode);
brush.InterpolationColors = new ColorBlend() { Colors = new Color[] { PipeEdgeColor, PipeCenterColor, PipeEdgeColor }, Positions = new float[] { 0, 0.5f, 1 } };
brush.InterpolationColors = new ColorBlend() { Colors = new Color[] { PipeEdgeColor, PipeCenterColor, PipeEdgeColor }, Positions = new float[] { 0, 0.5f, 1 } };
g.FillRectangle(brush, rec);
g.DrawLine(new Pen(BorderColor, BorderWidth), points[0], points[1]);
g.DrawLine(new Pen(BorderColor, BorderWidth), points[2], points[3]);
}
3,使用自定义的流动管道控件(FlowControl)
引用控件
属性窗口设置自定义的相关属性
设置后效果
4,代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Drawing.Drawing2D;
namespace CustomControl
{
public partial class FlowControl : UserControl
{
private float startOffset = 0.0f;
private Timer mytimer = new Timer();
Graphics g;
public FlowControl()
{
InitializeComponent();
设置控件样式
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
this.SetStyle(ControlStyles.DoubleBuffer, true);
this.SetStyle(ControlStyles.ResizeRedraw, true);
this.SetStyle(ControlStyles.Selectable, true);
this.SetStyle(ControlStyles.SupportsTransparentBackColor, true);
this.SetStyle(ControlStyles.UserPaint, true);
mytimer.Interval = 50;
mytimer.Tick += Mytimer_Tick;
mytimer.Start();
}
private void Mytimer_Tick(object sender, EventArgs e)
{
startOffset += moveSpeed;
if (Math.Abs(startOffset) > BarLength + GapLength)
{
startOffset = 0;
}
this.Invalidate();
}
#region Fileds
private int barHeight = 5;
[Browsable(true), Category("自定义属性"), Description("流动条宽度")]
public int BarHeight
{
get { return barHeight; }
set
{
this.barHeight = value;
base.Invalidate();
}
}
private Color barColor = Color.DodgerBlue;
[Browsable(true), Category("自定义属性"), Description("获取或设置管道控件的流动块颜色")]
public Color BarColor
{
get
{
return this.barColor;
}
set
{
this.barColor = value;
base.Invalidate();
}
}
private Color borderColor = Color.DimGray;
[Browsable(true), Category("自定义属性"), Description("获取或设置管道边线颜色")]
public Color BorderColor
{
get
{
return this.borderColor;
}
set
{
this.borderColor = value;
base.Invalidate();
}
}
private float borderWidth = 1;
[Browsable(true), Category("自定义属性"), Description("获取或设置管道壁厚度")]
public float BorderWidth
{
get
{
return this.borderWidth;
}
set
{
this.borderWidth = value;
base.Invalidate();
}
}
private Color pipeEdgeColor = Color.DimGray;
[Browsable(true), Category("自定义属性"), Description("获取或设置管道边缘颜色")]
public Color PipeEdgeColor
{
get
{
return this.pipeEdgeColor;
}
set
{
this.pipeEdgeColor = value;
base.Invalidate();
}
}
private Color pipeCenterColor = Color.LightGray;
[Browsable(true), Category("自定义属性"), Description("获取或设置管道控件的中心颜色")]
public Color PipeCenterColor
{
get
{
return this.pipeCenterColor;
}
set
{
this.pipeCenterColor = value;
base.Invalidate();
}
}
private PipeTurnDirection pipeTurnLeft = PipeTurnDirection.None;
[Browsable(true), Category("自定义属性"), Description("左管道的转向类型")]
public PipeTurnDirection PipeTurnLeft
{
get
{
return this.pipeTurnLeft;
}
set
{
this.pipeTurnLeft = value;
base.Invalidate();
}
}
private PipeTurnDirection pipeTurnRight = PipeTurnDirection.None;
[Browsable(true), Category("自定义属性"), Description("右管道的转向类型")]
public PipeTurnDirection PipeTurnRight
{
get
{
return this.pipeTurnRight;
}
set
{
this.pipeTurnRight = value;
base.Invalidate();
}
}
private DirectionStyle pipeLineDirection = DirectionStyle.Horizontal;
[Browsable(true), Category("自定义属性"), Description("设置管道是横向的还是纵向的")]
public DirectionStyle PipeLineDirection
{
get
{
return this.pipeLineDirection;
}
set
{
this.pipeLineDirection = value;
int temp;
if (value == DirectionStyle.Horizontal)
{
temp = Height;
Height = Width;
Width = temp;
}
else
{
temp = Width;
Width = Height;
Height = temp;
}
base.Invalidate();
}
}
private bool isActive = false;
[Browsable(true), Category("自定义属性"), DefaultValue(false), Description("获取或设置管道线是否激活液体显示")]
public bool IsActive
{
get
{
return this.isActive;
}
set
{
this.isActive = value;
this.mytimer.Enabled = value;
base.Invalidate();
}
}
private float moveSpeed = 0.3f;
[Browsable(true), Category("自定义属性"), Description("管道线液体流动的速度,0为静止,正数为正向流动,负数为反向流动")]
public float MoveSpeed
{
get
{
return this.moveSpeed;
}
set
{
this.moveSpeed = value;
base.Invalidate();
}
}
private int barLength = 5;
[Browsable(true), Category("自定义属性"), Description("流动条长度")]
public int BarLength
{
get
{
return this.barLength;
}
set
{
this.barLength = value;
base.Invalidate();
}
}
private int gapLength = 5;
[Browsable(true), Category("自定义属性"), Description("间隙长度")]
public int GapLength
{
get
{
return this.gapLength;
}
set
{
this.gapLength = value;
base.Invalidate();
}
}
#endregion
protected override void OnResize(EventArgs e)
{
if (PipeLineDirection == DirectionStyle.Horizontal)
{
if (Width < 2 * Height)
{
Width = 2 * Height;
}
}
else
{
if (Height < 2 * Width)
{
Height = 2 * Width;
}
}
base.OnResize(e);
}
protected override void OnPaint(PaintEventArgs e)
{
// base.OnPaint(e);
g = e.Graphics;
Rectangle rec;
Rectangle rec2;
GraphicsPath graphicsPath = new GraphicsPath();
if (PipeLineDirection == DirectionStyle.Horizontal)
{
#region 水平
switch (pipeTurnLeft)
{
case PipeTurnDirection.Up:
rec = new Rectangle(0, -Height, 2 * Height, 2 * Height);
rec2 = new Rectangle(rec.X + (int)BorderWidth / 2, rec.Y, rec.Width - (int)BorderWidth, rec.Height - (int)BorderWidth / 2);
PaintElliple(g, rec, rec2, 90, 90);
// path.AddArc(new Rectangle( Height / 2, rec.Y /2, Height , Height), 90, 90);
//特别需要注意角度旋转方向,如果角度旋转方向选错就形成闭环
graphicsPath.AddArc(new Rectangle(this.Height / 2, this.Height / 2 * (-1) - 1, this.Height, this.Height), 180.0f, -90.0f);
break;
case PipeTurnDirection.Down:
rec = new Rectangle(0, 0, 2 * Height, 2 * Height);
rec2 = new Rectangle(rec.X + (int)BorderWidth / 2, rec.Y + (int)BorderWidth / 2, rec.Width - (int)BorderWidth, rec.Height - (int)BorderWidth / 2);
PaintElliple(g, rec, rec2, 180, 90);
graphicsPath.AddArc(new Rectangle(Height / 2, Height / 2, Height, Height), 180, 90);
break;
case PipeTurnDirection.Left:
case PipeTurnDirection.Right:
case PipeTurnDirection.None:
PointF[] points = new PointF[] { new PointF(Height, BorderWidth / 2), new PointF(0, BorderWidth / 2), new PointF(0, Height - BorderWidth / 2), new PointF(Height, Height - BorderWidth / 2) };
PaintRectangle(g, new Rectangle(0, 0, Height, Height), points);
graphicsPath.AddLine(new PointF(0, Height / 2), new PointF(Height, Height / 2));
break;
default:
break;
}
switch (PipeTurnRight)
{
case PipeTurnDirection.Up:
rec = new Rectangle(Width - 2 * Height, -Height, 2 * Height, 2 * Height);
rec2 = new Rectangle(rec.X + (int)BorderWidth / 2, rec.Y, rec.Width - (int)BorderWidth, rec.Height - (int)BorderWidth / 2);
PaintElliple(g, rec, rec2, 0, 90);
graphicsPath.AddArc(new Rectangle(rec.X + Height / 2, rec.Y / 2, Height, Height), 90, -90);
break;
case PipeTurnDirection.Down:
rec = new Rectangle(Width - 2 * Height, 0, 2 * Height, 2 * Height);
rec2 = new Rectangle(rec.X, rec.Y + (int)BorderWidth / 2, rec.Width - (int)BorderWidth / 2, rec.Height - (int)BorderWidth / 2);
PaintElliple(g, rec, rec2, 270, 90);
//特别需要注意角度旋转方向,如果角度旋转方向选错就形成闭环
graphicsPath.AddArc(new Rectangle(rec.X + Height / 2, Height / 2, Height, Height), 270, 90);
break;
case PipeTurnDirection.Left:
case PipeTurnDirection.Right:
case PipeTurnDirection.None:
PointF[] points = new PointF[] { new PointF(Width - Height, BorderWidth / 2), new PointF(Width, BorderWidth / 2), new PointF(Width, Height - BorderWidth / 2), new PointF(Width - Height, Height - BorderWidth / 2) };
PaintRectangle(g, new Rectangle(Width - Height, 0, Height, Height), points);
graphicsPath.AddLine(Width - Height, Height / 2, Width, Height / 2);
break;
default:
break;
}
if (Width > Height * 2)
{
PointF[] points = new PointF[] { new PointF(Height, BorderWidth / 2), new PointF(Width - Height, BorderWidth / 2), new PointF(Height, Height - BorderWidth / 2), new PointF(Width - Height, Height - BorderWidth / 2) };
PaintRectangle(g, new Rectangle(Height, 0, Width - 2 * Height, Height), points);
// graphicsPath.AddLine(Height, Height / 2,Width- Height , Height / 2);
}
#endregion
}
else
{
//垂直
switch (pipeTurnLeft)
{
case PipeTurnDirection.Left:
rec = new Rectangle(-Width, 0, 2 * Width, 2 * Width);
rec2 = new Rectangle(rec.X, rec.Y + (int)BorderWidth / 2, rec.Width - (int)BorderWidth / 2, rec.Height - (int)BorderWidth);
PaintElliple(g, rec, rec2, 270, 90);
// path.AddArc(new Rectangle( Height / 2, rec.Y /2, Height , Height), 90, 90);
//特别需要注意角度旋转方向,如果角度旋转方向选错就形成闭环
graphicsPath.AddArc(new Rectangle(-this.Width / 2, this.Width / 2, this.Width, this.Width), 270.0f, 90.0f);
break;
case PipeTurnDirection.Right:
rec = new Rectangle(0, 0, 2 * Width, 2 * Width);
rec2 = new Rectangle(rec.X + (int)BorderWidth / 2, rec.Y + (int)BorderWidth / 2, rec.Width - (int)BorderWidth, rec.Height - (int)BorderWidth / 2);
PaintElliple(g, rec, rec2, 180, 90);
graphicsPath.AddArc(new Rectangle(Width / 2, Width / 2, Width, Width), 180, 90);
break;
case PipeTurnDirection.Up:
case PipeTurnDirection.Down:
case PipeTurnDirection.None:
PointF[] points = new PointF[] { new PointF(BorderWidth / 2, 0), new PointF(BorderWidth / 2, Width), new PointF(Width - BorderWidth / 2, 0), new PointF(Width - BorderWidth / 2, Width) };
PaintRectangle(g, new Rectangle(0, 0, Width, Width), points, LinearGradientMode.Horizontal);
graphicsPath.AddLine(new PointF(Width / 2, 0), new PointF(Width / 2, Width));
break;
default:
break;
}
switch (PipeTurnRight)
{
case PipeTurnDirection.Left:
rec = new Rectangle(-Width, Height - 2 * Width, 2 * Width, 2 * Width);
rec2 = new Rectangle(rec.X, rec.Y + (int)BorderWidth / 2, rec.Width - (int)BorderWidth / 2, rec.Height - (int)BorderWidth);
PaintElliple(g, rec, rec2, 0, 90);
//特别需要注意角度旋转方向,如果角度旋转方向选错就形成闭环
graphicsPath.AddArc(new Rectangle(rec.X / 2, rec.Y + Width / 2, this.Width, this.Width), 0f, 90.0f);
break;
case PipeTurnDirection.Right:
rec = new Rectangle(0, Height - 2 * Width, 2 * Width, 2 * Width);
rec2 = new Rectangle(rec.X + (int)BorderWidth / 2, rec.Y + 1, rec.Width - (int)BorderWidth, rec.Height - (int)BorderWidth / 2);
PaintElliple(g, rec, rec2, 90, 90);
graphicsPath.AddArc(new Rectangle(Width / 2, rec.Y + Width / 2, Width, Width), 180, -90);
break;
case PipeTurnDirection.Up:
case PipeTurnDirection.Down:
case PipeTurnDirection.None:
PointF[] points = new PointF[] { new PointF(BorderWidth / 2, Height - Width), new PointF(BorderWidth / 2, Height), new PointF(Width - BorderWidth / 2, Height - Width), new PointF(Width - BorderWidth / 2, Height) };
PaintRectangle(g, new Rectangle(0, Height - Width, Width, Width), points, LinearGradientMode.Horizontal);
graphicsPath.AddLine(new PointF(Width / 2, Height - Width), new PointF(Width / 2, Height));
break;
default:
break;
}
if (Height > Width * 2)
{
PointF[] points = new PointF[] { new PointF(BorderWidth / 2, Width), new PointF(BorderWidth / 2, Height - Width), new PointF(Width - BorderWidth / 2, Width), new PointF(Width - BorderWidth / 2, Height - Width) };
PaintRectangle(g, new Rectangle(0, Width, Width, Height - 2 * Width), points, LinearGradientMode.Horizontal);
// graphicsPath.AddLine(Height, Height / 2,Width- Height , Height / 2);
}
}
if (IsActive)
{
//绘制流动条
Pen barPen = new Pen(BarColor, BarHeight);
barPen.DashStyle = DashStyle.Custom;
barPen.DashOffset = startOffset;
barPen.DashPattern = new float[] { BarLength, GapLength };
graphicsPath.StartFigure();
g.DrawPath(barPen, graphicsPath);
}
}
//绘制椭圆与圆弧
void PaintElliple(Graphics g, Rectangle rec, Rectangle recArc, float startAngle, float sweepAngle)
{
GraphicsPath graphicsPath = new GraphicsPath();
graphicsPath.AddEllipse(rec);
//画刷效果呈现由中心向四方层叠
PathGradientBrush brush = new PathGradientBrush(graphicsPath);
brush.CenterPoint = new PointF(rec.X + rec.Width / 2, rec.Y + rec.Height / 2);
brush.InterpolationColors = new ColorBlend() { Colors = new Color[] { PipeEdgeColor, PipeCenterColor, PipeEdgeColor }, Positions = new float[] { 0, 0.5f, 1 } };
g.FillPie(brush, rec, startAngle, sweepAngle);
g.DrawArc(new Pen(BorderColor, BorderWidth), recArc, startAngle, sweepAngle);
}
void PaintRectangle(Graphics g, Rectangle rec, PointF[] points, LinearGradientMode linearGradientMode = LinearGradientMode.Vertical)
{
//画刷效果呈现线型分布,注意与PathGradientBrush的区别
LinearGradientBrush brush = new LinearGradientBrush(rec, PipeEdgeColor, PipeCenterColor, linearGradientMode);
brush.InterpolationColors = new ColorBlend() { Colors = new Color[] { PipeEdgeColor, PipeCenterColor, PipeEdgeColor }, Positions = new float[] { 0, 0.5f, 1 } };
brush.InterpolationColors = new ColorBlend() { Colors = new Color[] { PipeEdgeColor, PipeCenterColor, PipeEdgeColor }, Positions = new float[] { 0, 0.5f, 1 } };
g.FillRectangle(brush, rec);
g.DrawLine(new Pen(BorderColor, BorderWidth), points[0], points[1]);
g.DrawLine(new Pen(BorderColor, BorderWidth), points[2], points[3]);
}
}
/// <summary>
/// 管道左、右的转向类型
/// </summary>
public enum PipeTurnDirection
{
Up = 1,
Down,
Left,
Right,
None
}
/// <summary>
/// 管道的样式,水平还是数值
/// </summary>
public enum DirectionStyle
{
Horizontal = 1,
Vertical
}
}