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 公共属性 /// /// 圆背景色 /// [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(); // 延迟刷新 } } /// /// 内圆颜色,ShowType=Ring 有效 /// [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(); } } /// /// 内圆是否显示边框,ShowType=Ring 有效 /// [Description("内圆是否显示边框,ShowType=Ring 有效"), Category("自定义")] public bool IsShowCoreEllipseBorder { get { return m_isShowCoreEllipseBorder; } set { if (m_isShowCoreEllipseBorder == value) return; m_isShowCoreEllipseBorder = value; ScheduleRefresh(); } } /// /// 值文字类型 /// [Description("显示类型"), Category("自定义")] public ShowType ShowType { get { return m_showType; } set { if (m_showType == value) return; m_showType = value; UpdataMaxValue(); ScheduleRefresh(); } } /// /// 值文字类型 /// [Description("值文字类型,Percent-百分比;Absolute-数值"), Category("自定义")] public ValueType ValueType { get { return m_valueType; } set { if (m_valueType == value) return; m_valueType = value; ScheduleRefresh(); } } /// /// 值文字类型 /// [Description("值类型"), Category("自定义")] public ValueFloatOrInt ValueFloatOrInt { get { return m_valueFloatOrInt; } set { if (m_valueFloatOrInt == value) return; m_valueFloatOrInt = value; ScheduleRefresh(); } } /// /// 外圆值宽度 /// [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(); } } /// /// 外圆值间距 /// [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(); } } /// /// 最大值 /// [Description("最大值"), Category("自定义")] public int MaxValue { get { return m_maxValue; } set { if (m_maxValue == value) return; m_maxValue = value; ScheduleRefresh(); } } /// /// 当前值 /// [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(); } } /// /// 当前值 /// [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 ///// ///// 更新最大值 ///// //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 核心优化方法 /// /// 初始化绘图对象 /// 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); } /// /// 延迟刷新 /// 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; } } /// /// 句柄创建完成后触发的刷新逻辑 /// private void ProcessEllipse_HandleCreated(object sender, EventArgs e) { // 取消事件注册,避免重复触发 this.HandleCreated -= ProcessEllipse_HandleCreated; // 句柄创建完成后,执行一次刷新 if (!_isRefreshPending) { this.Invalidate(); } } /// /// 更新最大值 /// 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); } } /// /// 绘制圆环样式的数值内容(复用逻辑) /// 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); } } /// /// 绘制扇形样式的数值内容(复用逻辑) /// 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 { /// /// PPM /// PPM, /// /// 数值 /// 稼动率, /// /// /// 生产数 } public enum ValueType { /// /// 百分比 /// Percent, /// /// 数值 /// Absolute } public enum ValueFloatOrInt { /// /// 整数 /// Int, /// /// 浮点数 /// Float } public enum ShowStyleType { /// /// 圆环 /// Ring, /// /// 扇形 /// Sector } }