编辑
2025-02-13
C# 应用
00
请注意,本文编写于 82 天前,最后修改于 82 天前,其中某些信息可能已经过时。

目录

1. 基础类定义
2. 主要绘制方法
2.1 OnPaint 重写
2.2 流动样式
3. 动画控制
4. 完整代码
总结

在工业自动化、流程监控等领域,管道控件是一个常见的可视化元素。本文将详细介绍如何使用C# GDI+ 实现一个专业的管道控件,包括圆角管道的绘制、流动动画效果和方向指示箭头。

1. 基础类定义

首先,我们创建一个继承自 Control 的自定义控件类:

C#
private Timer animationTimer; private float flowOffset = 0; private const float FLOW_SPEED = 2.0f; // 自定义属性 private Color pipeColor = Color.DodgerBlue; private Color flowColor = Color.White; private bool isHorizontal = true; private int pipeWidth = 40; private FlowStyle flowStyle = FlowStyle.Diagonal; private FlowDirection flowDirection = FlowDirection.RightToLeft; private readonly int patternRepeat = 3; private ArrowStyle arrowStyle = ArrowStyle.SolidTriangle; // 添加圆角属性 private int cornerRadius = 0; [Description("管道颜色")] public Color PipeColor { get => pipeColor; set { pipeColor = value; Invalidate(); } } [Description("流动效果颜色")] public Color FlowColor { get => flowColor; set { flowColor = value; Invalidate(); } } [Description("是否横向显示")] public bool IsHorizontal { get => isHorizontal; set { isHorizontal = value; // 根据方向自动调整流向 if (isHorizontal) { flowDirection = FlowDirection.RightToLeft; } else { flowDirection = FlowDirection.TopToBottom; } Invalidate(); } } [Description("管道宽度")] public int PipeWidth { get => pipeWidth; set { pipeWidth = value; Invalidate(); } } [Description("流动样式")] public FlowStyle FlowStyle { get => flowStyle; set { flowStyle = value; Invalidate(); } } [Description("流动方向")] public FlowDirection FlowDirection { get => flowDirection; set { flowDirection = value; // 根据流向自动调整方向 isHorizontal = (value == FlowDirection.LeftToRight || value == FlowDirection.RightToLeft); Invalidate(); } } [Description("箭头样式")] public ArrowStyle ArrowStyle { get => arrowStyle; set { arrowStyle = value; Invalidate(); } } [Description("管道的圆角半径")] public int CornerRadius { get => cornerRadius; set { cornerRadius = Math.Max(0, Math.Min(value, PipeWidth)); // 限制圆角半径不超过管道宽度 Invalidate(); } } public PipelineControl() { SetStyle(ControlStyles.SupportsTransparentBackColor | ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true); BackColor = Color.Transparent; // 初始化动画计时器 animationTimer = new Timer(); animationTimer.Interval = 50; animationTimer.Tick += AnimationTimer_Tick; animationTimer.Start(); }

2. 主要绘制方法

2.1 OnPaint 重写

C#
protected override void OnPaint(PaintEventArgs e) { e.Graphics.SmoothingMode = SmoothingMode.AntiAlias; using (var path = new GraphicsPath()) { Rectangle pipeRect; if (IsHorizontal) { pipeRect = new Rectangle(0, (Height - PipeWidth) / 2, Width, PipeWidth); } else { pipeRect = new Rectangle((Width - PipeWidth) / 2, 0, PipeWidth, Height); } if (cornerRadius > 0) { if (IsHorizontal) { // 水平管道的圆角矩形 path.AddArc(pipeRect.X, pipeRect.Y, cornerRadius * 2, pipeRect.Height, 90, 180); // 左端 path.AddArc(pipeRect.Right - cornerRadius * 2, pipeRect.Y, cornerRadius * 2, pipeRect.Height, 270, 180); // 右端 path.CloseFigure(); } else { // 垂直管道的圆角矩形 path.AddArc(pipeRect.X, pipeRect.Y, pipeRect.Width, cornerRadius * 2, 180, 180); // 上端 path.AddArc(pipeRect.X, pipeRect.Bottom - cornerRadius * 2, pipeRect.Width, cornerRadius * 2, 0, 180); // 下端 path.CloseFigure(); } } else { // 绘制管道主体 path.AddRectangle(pipeRect); } // 绘制管道主体渐变填充 using (var brush = new LinearGradientBrush(pipeRect, Color.FromArgb(200, PipeColor), Color.FromArgb(150, PipeColor), IsHorizontal ? LinearGradientMode.Vertical : LinearGradientMode.Horizontal)) { e.Graphics.FillPath(brush, path); } // 绘制流动效果 DrawFlowAnimation(e.Graphics, pipeRect); // 绘制边框 using (var pen = new Pen(Color.FromArgb(100, Color.Gray), 1)) { e.Graphics.DrawPath(pen, path); } // 绘制流向箭头 DrawFlowArrow(e.Graphics, pipeRect); } }

2.2 流动样式

C#
private void DrawDiagonalFlow(Graphics g, Rectangle pipeRect) { using (var flowBrush = new HatchBrush(HatchStyle.LightDownwardDiagonal, Color.FromArgb(50, FlowColor), Color.Transparent)) { g.FillRectangle(flowBrush, pipeRect); } } private void DrawDotsFlow(Graphics g, Rectangle pipeRect) { int dotSize = PipeWidth / 4; using (var dotBrush = new SolidBrush(Color.FromArgb(50, FlowColor))) { if (IsHorizontal) { for (int x = pipeRect.Left; x < pipeRect.Right; x += PipeWidth) { g.FillEllipse(dotBrush, x, pipeRect.Top + (pipeRect.Height - dotSize) / 2, dotSize, dotSize); } } else { for (int y = pipeRect.Top; y < pipeRect.Bottom; y += PipeWidth) { g.FillEllipse(dotBrush, pipeRect.Left + (pipeRect.Width - dotSize) / 2, y, dotSize, dotSize); } } } } private void DrawWaveFlow(Graphics g, Rectangle pipeRect) { using (var path = new GraphicsPath()) { float amplitude = PipeWidth / 4f; float wavelength = PipeWidth * 2f; if (IsHorizontal) { var points = new List<Point>(); for (float x = pipeRect.Left; x <= pipeRect.Right; x += 5) { float y = pipeRect.Top + pipeRect.Height / 2 + (float)(Math.Sin((x / wavelength) * Math.PI * 2) * amplitude); points.Add(new Point((int)x, (int)y)); } path.AddLines(points.ToArray()); } else { var points = new List<Point>(); for (float y = pipeRect.Top; y <= pipeRect.Bottom; y += 5) { float x = pipeRect.Left + pipeRect.Width / 2 + (float)(Math.Sin((y / wavelength) * Math.PI * 2) * amplitude); points.Add(new Point((int)x, (int)y)); } path.AddLines(points.ToArray()); } using (var pen = new Pen(Color.FromArgb(50, FlowColor), 2)) { g.DrawPath(pen, path); } } }

3. 动画控制

C#
private void AnimationTimer_Tick(object sender, EventArgs e) { float speed = FLOW_SPEED; if (flowDirection == FlowDirection.LeftToRight || flowDirection == FlowDirection.TopToBottom) { speed = -FLOW_SPEED; } flowOffset += speed; if (IsHorizontal) { if (speed > 0 && flowOffset >= Width) { flowOffset = -Width / patternRepeat; } else if (speed < 0 && flowOffset <= -Width) { flowOffset = Width / patternRepeat; } } else { if (speed > 0 && flowOffset >= Height) { flowOffset = -Height / patternRepeat; } else if (speed < 0 && flowOffset <= -Height) { flowOffset = Height / patternRepeat; } } Invalidate(); }

4. 完整代码

C#
using System; using System.ComponentModel; using System.Drawing.Drawing2D; using System.Reflection; using System.Windows.Forms; using Timer = System.Windows.Forms.Timer; namespace AppControls { public enum FlowStyle { [Description("斜线流动")] Diagonal, [Description("点状流动")] Dots, [Description("波浪流动")] Wave, [Description("虚线流动")] Dashed } public enum FlowDirection { [Description("向右")] RightToLeft, [Description("向左")] LeftToRight, [Description("向上")] BottomToTop, [Description("向下")] TopToBottom } public enum ArrowStyle { [Description("实心三角形")] SolidTriangle, [Description("空心三角形")] HollowTriangle, [Description("双线箭头")] DoubleLines, [Description("尖头箭头")] Sharp, [Description("无箭头")] None } public class PipelineControl : Control { private Timer animationTimer; private float flowOffset = 0; private const float FLOW_SPEED = 2.0f; // 自定义属性 private Color pipeColor = Color.DodgerBlue; private Color flowColor = Color.White; private bool isHorizontal = true; private int pipeWidth = 40; private FlowStyle flowStyle = FlowStyle.Diagonal; private FlowDirection flowDirection = FlowDirection.RightToLeft; private readonly int patternRepeat = 3; private ArrowStyle arrowStyle = ArrowStyle.SolidTriangle; // 添加圆角属性 private int cornerRadius = 0; [Description("管道颜色")] public Color PipeColor { get => pipeColor; set { pipeColor = value; Invalidate(); } } [Description("流动效果颜色")] public Color FlowColor { get => flowColor; set { flowColor = value; Invalidate(); } } [Description("是否横向显示")] public bool IsHorizontal { get => isHorizontal; set { isHorizontal = value; // 根据方向自动调整流向 if (isHorizontal) { flowDirection = FlowDirection.RightToLeft; } else { flowDirection = FlowDirection.TopToBottom; } Invalidate(); } } [Description("管道宽度")] public int PipeWidth { get => pipeWidth; set { pipeWidth = value; Invalidate(); } } [Description("流动样式")] public FlowStyle FlowStyle { get => flowStyle; set { flowStyle = value; Invalidate(); } } [Description("流动方向")] public FlowDirection FlowDirection { get => flowDirection; set { flowDirection = value; // 根据流向自动调整方向 isHorizontal = (value == FlowDirection.LeftToRight || value == FlowDirection.RightToLeft); Invalidate(); } } [Description("箭头样式")] public ArrowStyle ArrowStyle { get => arrowStyle; set { arrowStyle = value; Invalidate(); } } [Description("管道的圆角半径")] public int CornerRadius { get => cornerRadius; set { cornerRadius = Math.Max(0, Math.Min(value, PipeWidth)); // 限制圆角半径不超过管道宽度 Invalidate(); } } public PipelineControl() { SetStyle(ControlStyles.SupportsTransparentBackColor | ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true); BackColor = Color.Transparent; // 初始化动画计时器 animationTimer = new Timer(); animationTimer.Interval = 50; animationTimer.Tick += AnimationTimer_Tick; animationTimer.Start(); } private void AnimationTimer_Tick(object sender, EventArgs e) { float speed = FLOW_SPEED; if (flowDirection == FlowDirection.LeftToRight || flowDirection == FlowDirection.TopToBottom) { speed = -FLOW_SPEED; } flowOffset += speed; if (IsHorizontal) { if (speed > 0 && flowOffset >= Width) { flowOffset = -Width / patternRepeat; } else if (speed < 0 && flowOffset <= -Width) { flowOffset = Width / patternRepeat; } } else { if (speed > 0 && flowOffset >= Height) { flowOffset = -Height / patternRepeat; } else if (speed < 0 && flowOffset <= -Height) { flowOffset = Height / patternRepeat; } } Invalidate(); } protected override void OnPaint(PaintEventArgs e) { e.Graphics.SmoothingMode = SmoothingMode.AntiAlias; using (var path = new GraphicsPath()) { Rectangle pipeRect; if (IsHorizontal) { pipeRect = new Rectangle(0, (Height - PipeWidth) / 2, Width, PipeWidth); } else { pipeRect = new Rectangle((Width - PipeWidth) / 2, 0, PipeWidth, Height); } if (cornerRadius > 0) { if (IsHorizontal) { // 水平管道的圆角矩形 path.AddArc(pipeRect.X, pipeRect.Y, cornerRadius * 2, pipeRect.Height, 90, 180); // 左端 path.AddArc(pipeRect.Right - cornerRadius * 2, pipeRect.Y, cornerRadius * 2, pipeRect.Height, 270, 180); // 右端 path.CloseFigure(); } else { // 垂直管道的圆角矩形 path.AddArc(pipeRect.X, pipeRect.Y, pipeRect.Width, cornerRadius * 2, 180, 180); // 上端 path.AddArc(pipeRect.X, pipeRect.Bottom - cornerRadius * 2, pipeRect.Width, cornerRadius * 2, 0, 180); // 下端 path.CloseFigure(); } } else { // 绘制管道主体 path.AddRectangle(pipeRect); } // 绘制管道主体渐变填充 using (var brush = new LinearGradientBrush(pipeRect, Color.FromArgb(200, PipeColor), Color.FromArgb(150, PipeColor), IsHorizontal ? LinearGradientMode.Vertical : LinearGradientMode.Horizontal)) { e.Graphics.FillPath(brush, path); } // 绘制流动效果 DrawFlowAnimation(e.Graphics, pipeRect); // 绘制边框 using (var pen = new Pen(Color.FromArgb(100, Color.Gray), 1)) { e.Graphics.DrawPath(pen, path); } // 绘制流向箭头 DrawFlowArrow(e.Graphics, pipeRect); } } private void DrawFlowAnimation(Graphics g, Rectangle pipeRect) { // 创建扩展的绘制区域,以实现无缝连接 Rectangle extendedRect; if (IsHorizontal) { extendedRect = new Rectangle( -pipeRect.Width, pipeRect.Y, pipeRect.Width * (patternRepeat + 1), pipeRect.Height); } else { extendedRect = new Rectangle( pipeRect.X, -pipeRect.Height, pipeRect.Width, pipeRect.Height * (patternRepeat + 1)); } // 使用裁剪区域确保只显示控件范围内的内容 g.SetClip(pipeRect); Matrix matrix = new Matrix(); if (IsHorizontal) { matrix.Translate(flowOffset, 0); } else { matrix.Translate(0, flowOffset); } g.Transform = matrix; switch (FlowStyle) { case FlowStyle.Diagonal: DrawDiagonalFlow(g, extendedRect); break; case FlowStyle.Dots: DrawDotsFlow(g, extendedRect); break; case FlowStyle.Wave: DrawWaveFlow(g, extendedRect); break; case FlowStyle.Dashed: DrawDashedFlow(g, extendedRect); break; } g.ResetTransform(); g.ResetClip(); } private void DrawDiagonalFlow(Graphics g, Rectangle pipeRect) { using (var flowBrush = new HatchBrush(HatchStyle.LightDownwardDiagonal, Color.FromArgb(50, FlowColor), Color.Transparent)) { g.FillRectangle(flowBrush, pipeRect); } } private void DrawDotsFlow(Graphics g, Rectangle pipeRect) { int dotSize = PipeWidth / 4; using (var dotBrush = new SolidBrush(Color.FromArgb(50, FlowColor))) { if (IsHorizontal) { for (int x = pipeRect.Left; x < pipeRect.Right; x += PipeWidth) { g.FillEllipse(dotBrush, x, pipeRect.Top + (pipeRect.Height - dotSize) / 2, dotSize, dotSize); } } else { for (int y = pipeRect.Top; y < pipeRect.Bottom; y += PipeWidth) { g.FillEllipse(dotBrush, pipeRect.Left + (pipeRect.Width - dotSize) / 2, y, dotSize, dotSize); } } } } private void DrawWaveFlow(Graphics g, Rectangle pipeRect) { using (var path = new GraphicsPath()) { float amplitude = PipeWidth / 4f; float wavelength = PipeWidth * 2f; if (IsHorizontal) { var points = new List<Point>(); for (float x = pipeRect.Left; x <= pipeRect.Right; x += 5) { float y = pipeRect.Top + pipeRect.Height / 2 + (float)(Math.Sin((x / wavelength) * Math.PI * 2) * amplitude); points.Add(new Point((int)x, (int)y)); } path.AddLines(points.ToArray()); } else { var points = new List<Point>(); for (float y = pipeRect.Top; y <= pipeRect.Bottom; y += 5) { float x = pipeRect.Left + pipeRect.Width / 2 + (float)(Math.Sin((y / wavelength) * Math.PI * 2) * amplitude); points.Add(new Point((int)x, (int)y)); } path.AddLines(points.ToArray()); } using (var pen = new Pen(Color.FromArgb(50, FlowColor), 2)) { g.DrawPath(pen, path); } } } private void DrawDashedFlow(Graphics g, Rectangle pipeRect) { using (var pen = new Pen(Color.FromArgb(50, FlowColor), 2)) { pen.DashStyle = DashStyle.Dash; pen.DashPattern = new float[] { 10.0f, 10.0f }; // 设置虚线样式 if (IsHorizontal) { float centerY = pipeRect.Top + pipeRect.Height / 2; for (int x = pipeRect.Left; x < pipeRect.Right; x += 40) { g.DrawLine(pen, x, centerY, x + 20, centerY); } } else { float centerX = pipeRect.Left + pipeRect.Width / 2; for (int y = pipeRect.Top; y < pipeRect.Bottom; y += 40) { g.DrawLine(pen, centerX, y, centerX, y + 20); } } } } private void DrawFlowArrow(Graphics g, Rectangle pipeRect) { if (arrowStyle == ArrowStyle.None) return; DrawSingleArrow(g, pipeRect); } private void DrawSingleArrow(Graphics g, Rectangle pipeRect) { if (arrowStyle == ArrowStyle.None) return; int arrowSize = PipeWidth / 2; Point[] arrowPoints = new Point[3]; Point arrowCenter; // 根据方向计算箭头中心点和基本方向 switch (FlowDirection) { case FlowDirection.RightToLeft: arrowCenter = new Point(pipeRect.Right - arrowSize * 2, pipeRect.Top + pipeRect.Height / 2); CalculateArrowPoints(arrowCenter, arrowSize, 0, out arrowPoints); break; case FlowDirection.LeftToRight: arrowCenter = new Point(pipeRect.Left + arrowSize * 2, pipeRect.Top + pipeRect.Height / 2); CalculateArrowPoints(arrowCenter, arrowSize, 180, out arrowPoints); break; case FlowDirection.TopToBottom: arrowCenter = new Point(pipeRect.Left + pipeRect.Width / 2, pipeRect.Top + arrowSize * 2); CalculateArrowPoints(arrowCenter, arrowSize, 270, out arrowPoints); break; case FlowDirection.BottomToTop: arrowCenter = new Point(pipeRect.Left + pipeRect.Width / 2, pipeRect.Bottom - arrowSize * 2); CalculateArrowPoints(arrowCenter, arrowSize, 90, out arrowPoints); break; default: return; } // 根据不同样式绘制箭头 switch (ArrowStyle) { case ArrowStyle.SolidTriangle: DrawSolidTriangle(g, arrowPoints); break; case ArrowStyle.HollowTriangle: DrawHollowTriangle(g, arrowPoints); break; case ArrowStyle.DoubleLines: DrawDoubleLines(g, arrowPoints); break; case ArrowStyle.Sharp: DrawSharpArrow(g, arrowPoints); break; } } private void DrawArrowWithStyle(Graphics g, Point[] points) { switch (ArrowStyle) { case ArrowStyle.SolidTriangle: DrawSolidTriangle(g, points); break; case ArrowStyle.HollowTriangle: DrawHollowTriangle(g, points); break; case ArrowStyle.DoubleLines: DrawDoubleLines(g, points); break; case ArrowStyle.Sharp: DrawSharpArrow(g, points); break; } } private void CalculateArrowPoints(Point center, int size, float angleDegrees, out Point[] points) { points = new Point[3]; float angleRad = angleDegrees * (float)Math.PI / 180f; // 计算基本三角形的三个点 points[0] = new Point( // 箭头尖端 (int)(center.X + size * Math.Cos(angleRad)), (int)(center.Y + size * Math.Sin(angleRad)) ); points[1] = new Point( // 左翼 (int)(center.X + size * Math.Cos(angleRad + 2.618f)), (int)(center.Y + size * Math.Sin(angleRad + 2.618f)) ); points[2] = new Point( // 右翼 (int)(center.X + size * Math.Cos(angleRad - 2.618f)), (int)(center.Y + size * Math.Sin(angleRad - 2.618f)) ); } private void DrawSolidTriangle(Graphics g, Point[] points) { using (var brush = new SolidBrush(Color.FromArgb(180, PipeColor))) { g.FillPolygon(brush, points); } } private void DrawHollowTriangle(Graphics g, Point[] points) { using (var pen = new Pen(Color.FromArgb(180, PipeColor), 2)) { g.DrawPolygon(pen, points); } } private void DrawDoubleLines(Graphics g, Point[] points) { using (var pen = new Pen(Color.FromArgb(180, PipeColor), 2)) { // 计算两条平行线的点 Point center = points[0]; Point left = points[1]; Point right = points[2]; // 绘制外侧线 g.DrawLine(pen, left, center); g.DrawLine(pen, right, center); // 计算并绘制内侧线 Point innerLeft = new Point( (left.X + center.X) / 2, (left.Y + center.Y) / 2 ); Point innerRight = new Point( (right.X + center.X) / 2, (right.Y + center.Y) / 2 ); g.DrawLine(pen, innerLeft, center); g.DrawLine(pen, innerRight, center); } } private void DrawSharpArrow(Graphics g, Point[] points) { using (var pen = new Pen(Color.FromArgb(180, PipeColor), 2)) { // 计算箭头主干的终点 Point tailEnd = new Point( (points[1].X + points[2].X) / 2, (points[1].Y + points[2].Y) / 2 ); // 绘制箭头主干 g.DrawLine(pen, tailEnd, points[0]); // 绘制箭头两翼 g.DrawLine(pen, points[0], points[1]); g.DrawLine(pen, points[0], points[2]); } } private Point[] CalculateArrowHead(Point tip, float length, FlowDirection direction) { Point[] points = new Point[2]; double angle = Math.PI / 6; // 30度角 switch (direction) { case FlowDirection.RightToLeft: points[0] = new Point( (int)(tip.X - length * Math.Cos(angle)), (int)(tip.Y - length * Math.Sin(angle)) ); points[1] = new Point( (int)(tip.X - length * Math.Cos(angle)), (int)(tip.Y + length * Math.Sin(angle)) ); break; case FlowDirection.LeftToRight: points[0] = new Point( (int)(tip.X + length * Math.Cos(angle)), (int)(tip.Y - length * Math.Sin(angle)) ); points[1] = new Point( (int)(tip.X + length * Math.Cos(angle)), (int)(tip.Y + length * Math.Sin(angle)) ); break; case FlowDirection.TopToBottom: points[0] = new Point( (int)(tip.X - length * Math.Sin(angle)), (int)(tip.Y - length * Math.Cos(angle)) ); points[1] = new Point( (int)(tip.X + length * Math.Sin(angle)), (int)(tip.Y - length * Math.Cos(angle)) ); break; case FlowDirection.BottomToTop: points[0] = new Point( (int)(tip.X - length * Math.Sin(angle)), (int)(tip.Y + length * Math.Cos(angle)) ); points[1] = new Point( (int)(tip.X + length * Math.Sin(angle)), (int)(tip.Y + length * Math.Cos(angle)) ); break; } return points; } protected override void Dispose(bool disposing) { if (disposing) { animationTimer?.Stop(); animationTimer?.Dispose(); } base.Dispose(disposing); } } }

image.png

总结

本文详细介绍了如何使用C# GDI+实现一个专业的管道控件。通过合理使用GraphicsPath、贝塞尔曲线等技术,实现了圆角管道、流动动画和方向指示等功能。这个控件可以在工业自动化、流程监控等场景中使用,具有良好的可扩展性和定制性。

希望这个完整的实现能够帮助大家更好地理解GDI+绘图技术,并在实际项目中得到应用。

本文作者:rick

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!