Files

737 lines
25 KiB
C#
Raw Permalink Normal View History

2026-08-04 18:36:40 +08:00
using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace JinYuan.ControlLib
{
public partial class ProcessEllipse : UserControl
{
private bool _isRefreshPending = false;
private SolidBrush _backEllipseBrush;
private SolidBrush _coreEllipseBrush;
private SolidBrush _valueBrush;
private SolidBrush _foreColorBrush;
private Pen _coreEllipsePen;
private Pen _valueArcPen;
public ProcessEllipse()
{
InitializeComponent();
SetStyle(ControlStyles.AllPaintingInWmPaint |
ControlStyles.DoubleBuffer |
ControlStyles.ResizeRedraw |
ControlStyles.Selectable |
ControlStyles.SupportsTransparentBackColor |
ControlStyles.UserPaint |
ControlStyles.OptimizedDoubleBuffer, true);
UpdateStyles();
ForeColor = Color.White;
Font = new Font("Arial Unicode MS", 20);
InitDrawingObjects();
}
#region 减少闪烁
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x02000000;
return cp;
}
}
#endregion
[Description("值改变事件"), Category("自定义")]
public event EventHandler ValueChanged;
#region 私有字段
private Color m_backEllipseColor = Color.FromArgb(22, 160, 133);
private Color m_coreEllipseColor = Color.FromArgb(180, 180, 180);
private Color m_valueColor = Color.FromArgb(255, 77, 59);
private bool m_isShowCoreEllipseBorder = true;
private ShowType m_showType = ShowType.PPM;
private ValueType m_valueType = ValueType.Percent;
private ValueFloatOrInt m_valueFloatOrInt = ValueFloatOrInt.Int;
private int m_valueWidth = 30;
private int m_valueMargin = 5;
private int m_maxValue = 100;
private int m_intValue = 0;
private float m_floatValue = 0.00f;
private Font m_font = new Font("Arial Unicode MS", 20);
Color m_foreColor = Color.White;
private ShowStyleType m_showStyleType = ShowStyleType.Ring;
#endregion
#region 公共属性
/// <summary>
/// 圆背景色
/// </summary>
[Description("圆背景色"), Category("自定义")]
public Color BackEllipseColor
{
get { return m_backEllipseColor; }
set
{
m_backEllipseColor = value;
//ControlRefreshExtension.RefreshWithMonitor(this);
if (m_backEllipseColor == value) return; // 判重:值不变不刷新
m_backEllipseColor = value;
_backEllipseBrush?.Dispose();
_backEllipseBrush = new SolidBrush(value);
ScheduleRefresh(); // 延迟刷新
}
}
/// <summary>
/// 内圆颜色,ShowType=Ring 有效
/// </summary>
[Description("内圆颜色,ShowType=Ring 有效"), Category("自定义")]
public Color CoreEllipseColor
{
get { return m_coreEllipseColor; }
set
{
m_coreEllipseColor = value;
if (m_coreEllipseColor == value) return;
m_coreEllipseColor = value;
_coreEllipseBrush?.Dispose();
_coreEllipseBrush = new SolidBrush(value);
ScheduleRefresh();
}
}
[Description("值圆颜色"), Category("自定义")]
public Color ValueColor
{
get { return m_valueColor; }
set
{
if (m_valueColor == value) return;
m_valueColor = value;
_valueBrush?.Dispose();
_valueBrush = new SolidBrush(value);
_coreEllipsePen?.Dispose();
_coreEllipsePen = new Pen(value, 2);
_valueArcPen?.Dispose();
_valueArcPen = new Pen(value, m_valueWidth - m_valueMargin * 2);
ScheduleRefresh();
}
}
/// <summary>
/// 内圆是否显示边框,ShowType=Ring 有效
/// </summary>
[Description("内圆是否显示边框,ShowType=Ring 有效"), Category("自定义")]
public bool IsShowCoreEllipseBorder
{
get { return m_isShowCoreEllipseBorder; }
set
{
if (m_isShowCoreEllipseBorder == value) return;
m_isShowCoreEllipseBorder = value;
ScheduleRefresh();
}
}
/// <summary>
/// 值文字类型
/// </summary>
[Description("显示类型"), Category("自定义")]
public ShowType ShowType
{
get { return m_showType; }
set
{
if (m_showType == value) return;
m_showType = value;
UpdataMaxValue();
ScheduleRefresh();
}
}
/// <summary>
/// 值文字类型
/// </summary>
[Description("值文字类型,Percent-百分比;Absolute-数值"), Category("自定义")]
public ValueType ValueType
{
get { return m_valueType; }
set
{
if (m_valueType == value) return;
m_valueType = value;
ScheduleRefresh();
}
}
/// <summary>
/// 值文字类型
/// </summary>
[Description("值类型"), Category("自定义")]
public ValueFloatOrInt ValueFloatOrInt
{
get { return m_valueFloatOrInt; }
set
{
if (m_valueFloatOrInt == value) return;
m_valueFloatOrInt = value;
ScheduleRefresh();
}
}
/// <summary>
/// 外圆值宽度
/// </summary>
[Description("外圆值宽度,ShowType=Ring 有效"), Category("自定义")]
public int ValueWidth
{
get { return m_valueWidth; }
set
{
if (value <= 0 || value > Math.Min(this.Width, this.Height) || m_valueWidth == value)
return;
m_valueWidth = value;
_valueArcPen?.Dispose();
_valueArcPen = new Pen(m_valueColor, value - m_valueMargin * 2);
ScheduleRefresh();
}
}
/// <summary>
/// 外圆值间距
/// </summary>
[Description("外圆值间距"), Category("自定义")]
public int ValueMargin
{
get { return m_valueMargin; }
set
{
if (value < 0 || m_valueMargin >= m_valueWidth || m_valueMargin == value)
return;
m_valueMargin = value;
_valueArcPen?.Dispose();
_valueArcPen = new Pen(m_valueColor, m_valueWidth - value * 2);
ScheduleRefresh();
}
}
/// <summary>
/// 最大值
/// </summary>
[Description("最大值"), Category("自定义")]
public int MaxValue
{
get { return m_maxValue; }
set
{
if (m_maxValue == value) return;
m_maxValue = value;
ScheduleRefresh();
}
}
/// <summary>
/// 当前值
/// </summary>
[Description("当前值整数,ValueFloatOrInt=Int 有效"), Category("自定义")]
public int IntValue
{
get { return m_intValue; }
set
{
if (m_maxValue < value || value < 0 || m_intValue == value)
return;
m_intValue = value;
ValueChanged?.Invoke(this, null); // 简化事件调用
ScheduleRefresh();
}
}
/// <summary>
/// 当前值
/// </summary>
[Description("当前值浮点数,ValueFloatOrInt=Float 有效"), Category("自定义")]
public float FloatValue
{
get { return m_floatValue; }
set
{
if (m_maxValue < value || value < 0 || Math.Abs(m_floatValue - value) < 0.001f)
return;
m_floatValue = value;
ValueChanged?.Invoke(this, null);
ScheduleRefresh();
}
}
[Description("文字字体"), Category("自定义")]
public override Font Font
{
get
{
return m_font;
}
set
{
if (m_font == value) return;
m_font?.Dispose(); // 释放旧字体
m_font = value;
ScheduleRefresh();
}
}
[Description("文字颜色"), Category("自定义")]
public override Color ForeColor
{
get
{
return m_foreColor;
}
set
{
if (m_foreColor == value) return;
m_foreColor = value;
_foreColorBrush?.Dispose();
_foreColorBrush = new SolidBrush(value);
ScheduleRefresh();
}
}
[Description("显示样式"), Category("自定义")]
public ShowStyleType ShowStyleType
{
get { return m_showStyleType; }
set
{
if (m_showStyleType == value) return;
m_showStyleType = value;
ScheduleRefresh();
}
}
#endregion
///// <summary>
///// 更新最大值
///// </summary>
//private void UpdataMaxValue()
//{
// switch (m_showType)
// {
// case ShowType.PPM:
// this.m_maxValue = 100;
// break;
// case ShowType.稼动率:
// this.m_maxValue = 100;
// break;
// }
//}
//protected override void OnPaint(PaintEventArgs e)
//{
// base.OnPaint(e);
// var g = e.Graphics;
// g.SmoothingMode = SmoothingMode.AntiAlias; //使绘图质量最高,即消除锯齿
// g.InterpolationMode = InterpolationMode.HighQualityBicubic;
// g.CompositingQuality = CompositingQuality.HighQuality;
// int intWidth = Math.Min(this.Size.Width, this.Size.Height);
// //底圆
// g.FillEllipse(new SolidBrush(m_backEllipseColor), new Rectangle(new Point(0, 0), new Size(intWidth, intWidth)));
// if (m_showStyleType == ShowStyleType.Ring)
// {
// //中心圆
// int intCore = intWidth - m_valueWidth * 2;
// g.FillEllipse(new SolidBrush(m_coreEllipseColor), new Rectangle(new Point(m_valueWidth, m_valueWidth), new Size(intCore, intCore)));
// //中心圆边框
// if (m_isShowCoreEllipseBorder)
// {
// g.DrawEllipse(new Pen(m_valueColor, 2), new Rectangle(new Point(m_valueWidth + 1, m_valueWidth + 1), new Size(intCore - 1, intCore - 1)));
// }
// if (m_valueFloatOrInt == ValueFloatOrInt.Int)
// {
// if (m_intValue >= 0 && m_maxValue > 0)
// {
// int fltPercent = (int)m_intValue / (int)m_maxValue;
// if (fltPercent > 1)
// {
// fltPercent = 1;
// }
// g.DrawArc(new Pen(m_valueColor, m_valueWidth - m_valueMargin * 2), new RectangleF(new Point(m_valueWidth / 2 + m_valueMargin / 4, m_valueWidth / 2 + m_valueMargin / 4), new SizeF(intWidth - m_valueWidth - m_valueMargin / 2 + (m_valueMargin == 0 ? 0 : 1), intWidth - m_valueWidth - m_valueMargin / 2 + (m_valueMargin == 0 ? 0 : 1))), -90, fltPercent * 360);
// string strValueText = m_valueType == ValueType.Percent ? fltPercent.ToString("0%") : m_intValue.ToString();
// System.Drawing.SizeF _txtSize = g.MeasureString(strValueText, this.Font);
// g.DrawString(strValueText, this.Font, new SolidBrush(this.ForeColor), new PointF((intWidth - _txtSize.Width) / 2 + 1, (intWidth - _txtSize.Height) / 2 + 1));
// }
// }
// else
// {
// if (m_floatValue >= 0 && m_maxValue > 0)
// {
// float fltPercent = (float)m_floatValue / (float)m_maxValue;
// if (fltPercent > 1f)
// {
// fltPercent = 1f;
// }
// g.DrawArc(new Pen(m_valueColor, m_valueWidth - m_valueMargin * 2), new RectangleF(new Point(m_valueWidth / 2 + m_valueMargin / 4, m_valueWidth / 2 + m_valueMargin / 4), new SizeF(intWidth - m_valueWidth - m_valueMargin / 2 + (m_valueMargin == 0 ? 0 : 1), intWidth - m_valueWidth - m_valueMargin / 2 + (m_valueMargin == 0 ? 0 : 1))), -90, fltPercent * 360);
// string strValueText = m_valueType == ValueType.Percent ? fltPercent.ToString("0%") : m_floatValue.ToString("f2");
// System.Drawing.SizeF _txtSize = g.MeasureString(strValueText, this.Font);
// g.DrawString(strValueText, this.Font, new SolidBrush(this.ForeColor), new PointF((intWidth - _txtSize.Width) / 2 + 1, (intWidth - _txtSize.Height) / 2 + 1));
// }
// }
// }
// else
// {
// if (m_valueFloatOrInt == ValueFloatOrInt.Int)
// {
// if (m_intValue >= 0 && m_maxValue > 0)
// {
// int fltPercent = (int)m_intValue / (int)m_maxValue;
// if (fltPercent > 1)
// {
// fltPercent = 1;
// }
// g.FillPie(new SolidBrush(m_valueColor), new Rectangle(m_valueMargin, m_valueMargin, intWidth - m_valueMargin * 2, intWidth - m_valueMargin * 2), -90, fltPercent * 360);
// string strValueText = m_valueType == ValueType.Percent ? fltPercent.ToString("0%") : m_intValue.ToString();
// System.Drawing.SizeF _txtSize = g.MeasureString(strValueText, this.Font);
// g.DrawString(strValueText, this.Font, new SolidBrush(this.ForeColor), new PointF((intWidth - _txtSize.Width) / 2 + 1, (intWidth - _txtSize.Height) / 2 + 1));
// }
// }
// else
// {
// if (m_floatValue >= 0 && m_maxValue > 0)
// {
// float fltPercent = (float)m_floatValue / (float)m_maxValue;
// if (fltPercent > 1f)
// {
// fltPercent = 1f;
// }
// g.FillPie(new SolidBrush(m_valueColor), new Rectangle(m_valueMargin, m_valueMargin, intWidth - m_valueMargin * 2, intWidth - m_valueMargin * 2), -90, fltPercent * 360);
// string strValueText = m_valueType == ValueType.Percent ? fltPercent.ToString("0%") : m_floatValue.ToString("f2");
// System.Drawing.SizeF _txtSize = g.MeasureString(strValueText, this.Font);
// g.DrawString(strValueText, this.Font, new SolidBrush(this.ForeColor), new PointF((intWidth - _txtSize.Width) / 2 + 1, (intWidth - _txtSize.Height) / 2 + 1));
// }
// }
// }
#region 核心优化方法
/// <summary>
/// 初始化绘图对象
/// </summary>
private void InitDrawingObjects()
{
_backEllipseBrush = new SolidBrush(m_backEllipseColor);
_coreEllipseBrush = new SolidBrush(m_coreEllipseColor);
_valueBrush = new SolidBrush(m_valueColor);
_foreColorBrush = new SolidBrush(m_foreColor);
_coreEllipsePen = new Pen(m_valueColor, 2);
_valueArcPen = new Pen(m_valueColor, m_valueWidth - m_valueMargin * 2);
}
/// <summary>
/// 延迟刷新
/// </summary>
private void ScheduleRefresh()
{
if (_isRefreshPending) return;
_isRefreshPending = true;
// 先判断控件句柄是否已创建
if (!this.IsHandleCreated)
{
// 句柄未创建时:
// 1. 注册句柄创建完成的事件,创建后再刷新
this.HandleCreated += ProcessEllipse_HandleCreated;
// 2. 直接调用Invalidate(句柄未创建时Invalidate无副作用)
this.Invalidate();
_isRefreshPending = false;
return;
}
// 句柄已创建时,使用BeginInvoke延迟刷新
try
{
this.BeginInvoke(new Action(() =>
{
// 双重校验:避免跨线程/句柄释放导致的异常
if (this.IsDisposed || !this.IsHandleCreated)
{
_isRefreshPending = false;
return;
}
this.Invalidate();
_isRefreshPending = false;
}));
}
catch (ObjectDisposedException)
{
// 控件已释放时,重置标识
_isRefreshPending = false;
}
}
/// <summary>
/// 句柄创建完成后触发的刷新逻辑
/// </summary>
private void ProcessEllipse_HandleCreated(object sender, EventArgs e)
{
// 取消事件注册,避免重复触发
this.HandleCreated -= ProcessEllipse_HandleCreated;
// 句柄创建完成后,执行一次刷新
if (!_isRefreshPending)
{
this.Invalidate();
}
}
/// <summary>
/// 更新最大值
/// </summary>
private void UpdataMaxValue()
{
switch (m_showType)
{
case ShowType.PPM:
case ShowType.稼动率:
m_maxValue = 100;
break;
}
}
#endregion
#region 重写OnPaint(复用绘图对象 + 优化逻辑)
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
var g = e.Graphics;
// 优化绘图质量(只设置一次,避免重复赋值)
g.SmoothingMode = SmoothingMode.AntiAlias;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.CompositingQuality = CompositingQuality.HighQuality;
g.Clear(BackColor); // 统一清背景,减少闪烁
int intWidth = Math.Min(Size.Width, Size.Height);
if (intWidth == 0) return; // 避免除以0
// 绘制底圆(复用画刷)
g.FillEllipse(_backEllipseBrush, new Rectangle(0, 0, intWidth, intWidth));
if (m_showStyleType == ShowStyleType.Ring)
{
// 绘制中心圆(复用画刷)
int intCore = intWidth - m_valueWidth * 2;
g.FillEllipse(_coreEllipseBrush, new Rectangle(m_valueWidth, m_valueWidth, intCore, intCore));
// 绘制中心圆边框(复用画笔)
if (m_isShowCoreEllipseBorder)
{
g.DrawEllipse(_coreEllipsePen, new Rectangle(m_valueWidth + 1, m_valueWidth + 1, intCore - 1, intCore - 1));
}
// 绘制数值圆弧/文字
DrawValueContent(g, intWidth);
}
else
{
// 绘制扇形样式
DrawSectorContent(g, intWidth);
}
}
/// <summary>
/// 绘制圆环样式的数值内容(复用逻辑)
/// </summary>
private void DrawValueContent(Graphics g, int intWidth)
{
float percent = 0f;
string strValueText = string.Empty;
if (m_valueFloatOrInt == ValueFloatOrInt.Int)
{
if (m_intValue >= 0 && m_maxValue > 0)
{
percent = (float)m_intValue / m_maxValue;
percent = Math.Min(percent, 1f);
strValueText = m_valueType == ValueType.Percent ? percent.ToString("0%") : m_intValue.ToString();
}
}
else
{
if (m_floatValue >= 0 && m_maxValue > 0)
{
percent = m_floatValue / m_maxValue;
percent = Math.Min(percent, 1f);
strValueText = m_valueType == ValueType.Percent ? percent.ToString("0%") : m_floatValue.ToString("f2");
}
}
// 绘制圆弧(复用画笔)
if (percent > 0)
{
var arcRect = new RectangleF(
m_valueWidth / 2 + m_valueMargin / 4,
m_valueWidth / 2 + m_valueMargin / 4,
intWidth - m_valueWidth - m_valueMargin / 2 + (m_valueMargin == 0 ? 0 : 1),
intWidth - m_valueWidth - m_valueMargin / 2 + (m_valueMargin == 0 ? 0 : 1));
g.DrawArc(_valueArcPen, arcRect, -90, percent * 360);
}
// 绘制文字(复用画刷)
if (!string.IsNullOrEmpty(strValueText))
{
var txtSize = g.MeasureString(strValueText, Font);
g.DrawString(strValueText, Font, _foreColorBrush,
(intWidth - txtSize.Width) / 2 + 1,
(intWidth - txtSize.Height) / 2 + 1);
}
}
/// <summary>
/// 绘制扇形样式的数值内容(复用逻辑)
/// </summary>
private void DrawSectorContent(Graphics g, int intWidth)
{
float percent = 0f;
string strValueText = string.Empty;
if (m_valueFloatOrInt == ValueFloatOrInt.Int)
{
if (m_intValue >= 0 && m_maxValue > 0)
{
percent = (float)m_intValue / m_maxValue;
percent = Math.Min(percent, 1f);
strValueText = m_valueType == ValueType.Percent ? percent.ToString("0%") : m_intValue.ToString();
}
}
else
{
if (m_floatValue >= 0 && m_maxValue > 0)
{
percent = m_floatValue / m_maxValue;
percent = Math.Min(percent, 1f);
strValueText = m_valueType == ValueType.Percent ? percent.ToString("0%") : m_floatValue.ToString("f2");
}
}
// 绘制扇形(复用画刷)
if (percent > 0)
{
var pieRect = new Rectangle(m_valueMargin, m_valueMargin,
intWidth - m_valueMargin * 2, intWidth - m_valueMargin * 2);
g.FillPie(_valueBrush, pieRect, -90, percent * 360);
}
// 绘制文字(复用画刷)
if (!string.IsNullOrEmpty(strValueText))
{
var txtSize = g.MeasureString(strValueText, Font);
g.DrawString(strValueText, Font, _foreColorBrush,
(intWidth - txtSize.Width) / 2 + 1,
(intWidth - txtSize.Height) / 2 + 1);
}
}
#endregion
#region 释放资源(避免内存泄漏)
protected override void Dispose(bool disposing)
{
if (disposing)
{
// 释放缓存的绘图对象
_backEllipseBrush?.Dispose();
_coreEllipseBrush?.Dispose();
_valueBrush?.Dispose();
_foreColorBrush?.Dispose();
_coreEllipsePen?.Dispose();
_valueArcPen?.Dispose();
m_font?.Dispose(); // 释放字体
components?.Dispose();
}
base.Dispose(disposing);
}
#endregion
}
public enum ShowType
{
/// <summary>
/// PPM
/// </summary>
PPM,
/// <summary>
/// 数值
/// </summary>
稼动率,
/// <summary>
///
/// </summary>
生产数
}
public enum ValueType
{
/// <summary>
/// 百分比
/// </summary>
Percent,
/// <summary>
/// 数值
/// </summary>
Absolute
}
public enum ValueFloatOrInt
{
/// <summary>
/// 整数
/// </summary>
Int,
/// <summary>
/// 浮点数
/// </summary>
Float
}
public enum ShowStyleType
{
/// <summary>
/// 圆环
/// </summary>
Ring,
/// <summary>
/// 扇形
/// </summary>
Sector
}
}