前提
入行已经7,8年了,一直想做一套漂亮点的自定义控件,于是就有了本系列文章。
官网:https://www.hzhcontrols.cn
GitHub:https://github.com/kwwwvagaa/NetWinformControl
如果觉得写的还行,请点个 star 支持一下吧
麻烦博客下方点个【推荐】,谢谢
NuGet
Install-Package HZH_Controls
目录
c#Winform自定义控件-目录_c#winform自定义控件-有图标的按钮-CSDN博客
用处及效果
准备工作
GDI+画的,不会的可以先百度了解下
开始
添加一个类UCRadarChart ,继承 UserControl
添加一些控制属性
1 /// <summary>
2 /// The split count
3 /// </summary>
4 private int splitCount = 5;
5 /// <summary>
6 /// Gets or sets the split count.
7 /// </summary>
8 /// <value>The split count.</value>
9 [Browsable(true)]
10 [Category("自定义")]
11 [Description("获取或设置分隔份数")]
12 public int SplitCount
13 {
14 get { return splitCount; }
15 set
16 {
17 splitCount = value;
18 Invalidate();
19 }
20 }
21
22 /// <summary>
23 /// The split odd color
24 /// </summary>
25 private Color splitOddColor = Color.White;
26 /// <summary>
27 /// 分隔奇数栏背景色
28 /// </summary>
29 /// <value>The color of the split odd.</value>
30 [Browsable(true)]
31 [Category("自定义")]
32 [Description("获取或设置分隔奇数栏背景色")]
33 public Color SplitOddColor
34 {
35 get { return splitOddColor; }
36 set
37 {
38 splitOddColor = value;
39 Invalidate();
40 }
41 }
42 /// <summary>
43 /// The split even color
44 /// </summary>
45 private Color splitEvenColor = Color.FromArgb(232, 232, 232);
46 /// <summary>
47 /// 分隔偶数栏背景色
48 /// </summary>
49 /// <value>The color of the split even.</value>
50 [Browsable(true)]
51 [Category("自定义")]
52 [Description("获取或设置分隔偶数栏背景色")]
53 public Color SplitEvenColor
54 {
55 get { return splitEvenColor; }
56 set { splitEvenColor = value; }
57 }
58
59 /// <summary>
60 /// The line color
61 /// </summary>
62 private Color lineColor = Color.FromArgb(153, 153, 153);
63 /// <summary>
64 /// Gets or sets the color of the line.
65 /// </summary>
66 /// <value>The color of the line.</value>
67 [Browsable(true)]
68 [Category("自定义")]
69 [Description("获取或设置线条色")]
70 public Color LineColor
71 {
72 get { return lineColor; }
73 set
74 {
75 lineColor = value;
76 Invalidate();
77 }
78 }
79
80 /// <summary>
81 /// The radar positions
82 /// </summary>
83 private RadarPosition[] radarPositions;
84 /// <summary>
85 /// 节点列表,至少需要3个
86 /// </summary>
87 /// <value>The radar positions.</value>
88 [Browsable(true)]
89 [Category("自定义")]
90 [Description("获取或设置节点,至少需要3个")]
91 public RadarPosition[] RadarPositions
92 {
93 get { return radarPositions; }
94 set
95 {
96 radarPositions = value;
97 Invalidate();
98 }
99 }
100
101 /// <summary>
102 /// The title
103 /// </summary>
104 private string title;
105 /// <summary>
106 /// 标题
107 /// </summary>
108 /// <value>The title.</value>
109 [Browsable(true)]
110 [Category("自定义")]
111 [Description("获取或设置标题")]
112 public string Title
113 {
114 get { return title; }
115 set
116 {
117 title = value;
118 ResetTitleSize();
119 Invalidate();
120 }
121 }
122
123 /// <summary>
124 /// The title font
125 /// </summary>
126 private Font titleFont = new Font("微软雅黑", 12);
127 /// <summary>
128 /// Gets or sets the title font.
129 /// </summary>
130 /// <value>The title font.</value>
131 [Browsable(true)]
132 [Category("自定义")]
133 [Description("获取或设置标题字体")]
134 public Font TitleFont
135 {
136 get { return titleFont; }
137 set
138 {
139 titleFont = value;
140 ResetTitleSize();
141 Invalidate();
142 }
143 }
144
145 /// <summary>
146 /// The title color
147 /// </summary>
148 private Color titleColor = Color.Black;
149 /// <summary>
150 /// Gets or sets the color of the title.
151 /// </summary>
152 /// <value>The color of the title.</value>
153 [Browsable(true)]
154 [Category("自定义")]
155 [Description("获取或设置标题文本颜色")]
156 public Color TitleColor
157 {
158 get { return titleColor; }
159 set
160 {
161 titleColor = value;
162 Invalidate();
163 }
164 }
165
166 /// <summary>
167 /// The lines
168 /// </summary>
169 private RadarLine[] lines;
170 /// <summary>
171 /// Gets or sets the lines.
172 /// </summary>
173 /// <value>The lines.</value>
174 [Browsable(true)]
175 [Category("自定义")]
176 [Description("获取或设置值线条,Values长度必须与RadarPositions长度一致,否则无法显示")]
177 public RadarLine[] Lines
178 {
179 get { return lines; }
180 set
181 {
182 lines = value;
183 Invalidate();
184 }
185 }
186
187
188 /// <summary>
189 /// The title size
190 /// </summary>
191 SizeF titleSize = SizeF.Empty;
192 /// <summary>
193 /// The m rect working
194 /// </summary>
195 private RectangleF m_rectWorking = Rectangle.Empty;
196 /// <summary>
197 /// The line value type size
198 /// </summary>
199 SizeF lineValueTypeSize = SizeF.Empty;
200 /// <summary>
201 /// The int line value COM count
202 /// </summary>
203 int intLineValueComCount = 0;
204 /// <summary>
205 /// The int line value row count
206 /// </summary>
207 int intLineValueRowCount = 0;
属性改变时处理工作区域
1 /// <summary>
2 /// Handles the SizeChanged event of the UCRadarChart control.
3 /// </summary>
4 /// <param name="sender">The source of the event.</param>
5 /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
6 void UCRadarChart_SizeChanged(object sender, EventArgs e)
7 {
8 ResetWorkingRect();
9 }
10
11 /// <summary>
12 /// Resets the working rect.
13 /// </summary>
14 private void ResetWorkingRect()
15 {
16 if (lines != null && lines.Length > 0)
17 {
18 using (Graphics g = this.CreateGraphics())
19 {
20 foreach (var item in lines)
21 {
22 var s = g.MeasureString(item.Name, Font);
23 if (s.Width > lineValueTypeSize.Width)
24 lineValueTypeSize = s;
25 }
26 }
27 }
28 var lineTypePanelHeight = 0f;
29 if (lineValueTypeSize != SizeF.Empty)
30 {
31 intLineValueComCount = (int)(this.Width / (lineValueTypeSize.Width + 25));
32
33 intLineValueRowCount = lines.Length / intLineValueComCount;
34 if (lines.Length % intLineValueComCount != 0)
35 {
36 intLineValueRowCount++;
37 }
38 lineTypePanelHeight = (lineValueTypeSize.Height + 10) * intLineValueRowCount;
39 }
40 var min = Math.Min(this.Width, this.Height - titleSize.Height - lineTypePanelHeight);
41 var rectWorking = new RectangleF((this.Width - min) / 2 + 10, titleSize.Height + lineTypePanelHeight + 10, min - 10, min - 10);
42 //处理文字
43 float fltSplitAngle = 360F / radarPositions.Length;
44 float fltRadiusWidth = rectWorking.Width / 2;
45 float minX = rectWorking.Left;
46 float maxX = rectWorking.Right;
47 float minY = rectWorking.Top;
48 float maxY = rectWorking.Bottom;
49 using (Graphics g = this.CreateGraphics())
50 {
51 PointF centrePoint = new PointF(rectWorking.Left + rectWorking.Width / 2, rectWorking.Top + rectWorking.Height / 2);
52 for (int i = 0; i < radarPositions.Length; i++)
53 {
54 float fltAngle = 270 + fltSplitAngle * i;
55 fltAngle = fltAngle % 360;
56 PointF _point = GetPointByAngle(centrePoint, fltAngle, fltRadiusWidth);
57 var _txtSize = g.MeasureString(radarPositions[i].Text, Font);
58 if (_point.X < centrePoint.X)//左
59 {
60 if (_point.X - _txtSize.Width < minX)
61 {
62 minX = rectWorking.Left + _txtSize.Width;
63 }
64 }
65 else//右
66 {
67 if (_point.X + _txtSize.Width > maxX)
68 {
69 maxX = rectWorking.Right - _txtSize.Width;
70 }
71 }
72 if (_point.Y < centrePoint.Y)//上
73 {
74 if (_point.Y - _txtSize.Height < minY)
75 {
76 minY = rectWorking.Top + _txtSize.Height;
77 }
78 }
79 else//下
80 {
81 if (_point.Y + _txtSize.Height > maxY)
82 {
83 maxY = rectWorking.Bottom - _txtSize.Height;
84 }
85 }
86 }
87 }
88
89 min = Math.Min(maxX - minX, maxY - minY);
90 m_rectWorking = new RectangleF(minX, minY, min, min);
91 }
重绘
1 protected override void OnPaint(PaintEventArgs e)
2 {
3 base.OnPaint(e);
4 var g = e.Graphics;
5 g.SetGDIHigh();
6
7 if (!string.IsNullOrEmpty(title))
8 {
9 g.DrawString(title, titleFont, new SolidBrush(titleColor), new RectangleF(m_rectWorking.Left + (m_rectWorking.Width - titleSize.Width) / 2, m_rectWorking.Top - titleSize.Height - 10 - (intLineValueRowCount * (10 + lineValueTypeSize.Height)), titleSize.Width, titleSize.Height));
10 }
11
12 if (radarPositions.Length <= 2)
13 {
14 g.DrawString("至少需要3个顶点", Font, new SolidBrush(Color.Black), m_rectWorking, new StringFormat() { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center });
15 return;
16 }
17
18 var y = m_rectWorking.Top - 20 - (intLineValueRowCount * (10 + lineValueTypeSize.Height));
19
20 for (int i = 0; i < intLineValueRowCount; i++)
21 {
22 var x = 0f;
23 int intCount = intLineValueComCount;
24 if (i == intLineValueRowCount - 1)
25 {
26 intCount = lines.Length % intLineValueComCount;
27
28 }
29 x = m_rectWorking.Left + (m_rectWorking.Width - intCount * (lineValueTypeSize.Width + 25)) / 2;
30
31 for (int j = 0; j < intCount; j++)
32 {
33 g.FillRectangle(new SolidBrush(lines[i * intLineValueComCount + j].LineColor.Value), new RectangleF(x + (lineValueTypeSize.Width + 25)*j, y + lineValueTypeSize.Height * i, 15, lineValueTypeSize.Height));
34 g.DrawString(lines[i * intLineValueComCount + j].Name, Font, new SolidBrush(lines[i * intLineValueComCount + j].LineColor.Value), new PointF(x + (lineValueTypeSize.Width + 25) * j + 20, y + lineValueTypeSize.Height * i));
35 }
36 }
37
38 float fltSplitAngle = 360F / radarPositions.Length;
39 float fltRadiusWidth = m_rectWorking.Width / 2;
40 float fltSplitRadiusWidth = fltRadiusWidth / splitCount;
41 PointF centrePoint = new PointF(m_rectWorking.Left + m_rectWorking.Width / 2, m_rectWorking.Top + m_rectWorking.Height / 2);
42
43 List<List<PointF>> lstRingPoints = new List<List<PointF>>(splitCount);
44 //分割点
45 for (int i = 0; i < radarPositions.Length; i++)
46 {
47 float fltAngle = 270 + fltSplitAngle * i;
48 fltAngle = fltAngle % 360;
49 for (int j = 0; j < splitCount; j++)
50 {
51 if (i == 0)
52 {
53 lstRingPoints.Add(new List<PointF>());
54 }
55 PointF _point = GetPointByAngle(centrePoint, fltAngle, fltSplitRadiusWidth * (splitCount - j));
56 lstRingPoints[j].Add(_point);
57 }
58 }
59
60 for (int i = 0; i < lstRingPoints.Count; i++)
61 {
62 var ring = lstRingPoints[i];
63 GraphicsPath path = new GraphicsPath();
64 path.AddLines(ring.ToArray());
65 if ((lstRingPoints.Count - i) % 2 == 0)
66 {
67 g.FillPath(new SolidBrush(splitEvenColor), path);
68 }
69 else
70 {
71 g.FillPath(new SolidBrush(splitOddColor), path);
72 }
73 }
74
75 //画环
76 foreach (var ring in lstRingPoints)
77 {
78 ring.Add(ring[0]);
79 g.DrawLines(new Pen(new SolidBrush(lineColor)), ring.ToArray());
80 }
81 //分割线
82 foreach (var item in lstRingPoints[0])
83 {
84 g.DrawLine(new Pen(new SolidBrush(lineColor)), centrePoint, item);
85 }
86
87 //值
88 for (int i = 0; i < lines.Length; i++)
89 {
90 var line = lines[i];
91 if (line.Values.Length != radarPositions.Length)//如果数据长度和节点长度不一致则不绘制
92 continue;
93 if (line.LineColor == null || line.LineColor == Color.Empty || line.LineColor == Color.Transparent)
94 line.LineColor = ControlHelper.Colors[i + 13];
95 List<PointF> ps = new List<PointF>();
96 for (int j = 0; j < radarPositions.Length; j++)
97 {
98 float fltAngle = 270 + fltSplitAngle * j;
99 fltAngle = fltAngle % 360;
100 PointF _point = GetPointByAngle(centrePoint, fltAngle, fltRadiusWidth * (float)(line.Values[j] / radarPositions[i].MaxValue));
101 ps.Add(_point);
102 }
103 ps.Add(ps[0]);
104 if (line.FillColor != null && line.FillColor != Color.Empty && line.FillColor != Color.Transparent)
105 {
106 GraphicsPath path = new GraphicsPath();
107 path.AddLines(ps.ToArray());
108 g.FillPath(new SolidBrush(line.FillColor.Value), path);
109 }
110 g.DrawLines(new Pen(new SolidBrush(line.LineColor.Value), 2), ps.ToArray());
111
112 for (int j = 0; j < radarPositions.Length; j++)
113 {
114 var item = ps[j];
115 g.FillEllipse(new SolidBrush(Color.White), new RectangleF(item.X - 3, item.Y - 3, 6, 6));
116 g.DrawEllipse(new Pen(new SolidBrush(line.LineColor.Value)), new RectangleF(item.X - 3, item.Y - 3, 6, 6));
117 if (line.ShowValueText)
118 {
119 var valueSize = g.MeasureString(line.Values[j].ToString("0.##"), Font);
120 g.DrawString(line.Values[j].ToString("0.##"), Font, new SolidBrush(line.LineColor.Value), new PointF(item.X - valueSize.Width / 2, item.Y - valueSize.Height - 5));
121 }
122 }
123 }
124
125 //文本
126
127 for (int i = 0; i < radarPositions.Length; i++)
128 {
129 PointF point = lstRingPoints[0][i];
130 var txtSize = g.MeasureString(radarPositions[i].Text, Font);
131
132 if (point.X == centrePoint.X)
133 {
134 if (point.Y > centrePoint.Y)
135 {
136 g.DrawString(radarPositions[i].Text, Font, new SolidBrush(ForeColor), new PointF(point.X - txtSize.Width / 2, point.Y + 10));
137 }
138 else
139 {
140 g.DrawString(radarPositions[i].Text, Font, new SolidBrush(ForeColor), new PointF(point.X - txtSize.Width / 2, point.Y - 10 - txtSize.Height));
141 }
142 }
143 else if (point.Y == centrePoint.Y)
144 {
145 if (point.X < centrePoint.X)
146 g.DrawString(radarPositions[i].Text, Font, new SolidBrush(ForeColor), new PointF(point.X - 10 - txtSize.Width, point.Y - txtSize.Height / 2));
147 else
148 g.DrawString(radarPositions[i].Text, Font, new SolidBrush(ForeColor), new PointF(point.X + 10, point.Y - txtSize.Height / 2));
149 }
150 else if (point.X < centrePoint.X)//左
151 {
152 if (point.Y < centrePoint.Y)//左上
153 {
154 g.DrawString(radarPositions[i].Text, Font, new SolidBrush(ForeColor), new PointF(point.X - 10 - txtSize.Width, point.Y - 10 + txtSize.Height / 2));
155 }
156 else//左下
157 {
158 g.DrawString(radarPositions[i].Text, Font, new SolidBrush(ForeColor), new PointF(point.X - 10 - txtSize.Width, point.Y + 10 - txtSize.Height / 2));
159 }
160 }
161 else
162 {
163 if (point.Y < centrePoint.Y)//右上
164 {
165 g.DrawString(radarPositions[i].Text, Font, new SolidBrush(ForeColor), new PointF(point.X + 10, point.Y - 10 + txtSize.Height / 2));
166 }
167 else//右下
168 {
169 g.DrawString(radarPositions[i].Text, Font, new SolidBrush(ForeColor), new PointF(point.X + 10, point.Y + 10 - txtSize.Height / 2));
170 }
171 }
172 }
173
174 }
辅助函数
1 #region 根据中心点、角度、半径计算圆边坐标点 English:Calculating the coordinate points of circular edge according to the center point, angle and radius
2 /// <summary>
3 /// 功能描述:根据中心点、角度、半径计算圆边坐标点 English:Calculating the coordinate points of circular edge according to the center point, angle and radius
4 /// 作 者:HZH
5 /// 创建日期:2019-09-25 09:46:32
6 /// 任务编号:POS
7 /// </summary>
8 /// <param name="centrePoint">centrePoint</param>
9 /// <param name="fltAngle">fltAngle</param>
10 /// <param name="fltRadiusWidth">fltRadiusWidth</param>
11 /// <returns>返回值</returns>
12 private PointF GetPointByAngle(PointF centrePoint, float fltAngle, float fltRadiusWidth)
13 {
14 PointF p = centrePoint;
15 if (fltAngle == 0)
16 {
17 p.X += fltRadiusWidth;
18 }
19 else if (fltAngle == 90)
20 {
21 p.Y += fltRadiusWidth;
22 }
23 else if (fltAngle == 180)
24 {
25 p.X -= fltRadiusWidth;
26 }
27 else if (fltAngle == 270)
28 {
29 p.Y -= fltRadiusWidth;
30 }
31 else if (fltAngle > 0 && fltAngle < 90)
32 {
33 p.Y += (float)Math.Sin(Math.PI * (fltAngle / 180.00F)) * fltRadiusWidth;
34 p.X += (float)Math.Cos(Math.PI * (fltAngle / 180.00F)) * fltRadiusWidth;
35 }
36 else if (fltAngle > 90 && fltAngle < 180)
37 {
38 p.Y += (float)Math.Sin(Math.PI * ((180 - fltAngle) / 180.00F)) * fltRadiusWidth;
39 p.X -= (float)Math.Cos(Math.PI * ((180 - fltAngle) / 180.00F)) * fltRadiusWidth;
40 }
41 else if (fltAngle > 180 && fltAngle < 270)
42 {
43 p.Y -= (float)Math.Sin(Math.PI * ((fltAngle - 180) / 180.00F)) * fltRadiusWidth;
44 p.X -= (float)Math.Cos(Math.PI * ((fltAngle - 180) / 180.00F)) * fltRadiusWidth;
45 }
46 else if (fltAngle > 270 && fltAngle < 360)
47 {
48 p.Y -= (float)Math.Sin(Math.PI * ((360 - fltAngle) / 180.00F)) * fltRadiusWidth;
49 p.X += (float)Math.Cos(Math.PI * ((360 - fltAngle) / 180.00F)) * fltRadiusWidth;
50 }
51 return p;
52 }
53 #endregion
54
55 /// <summary>
56 /// Resets the size of the title.
57 /// </summary>
58 private void ResetTitleSize()
59 {
60 if (!string.IsNullOrEmpty(title))
61 {
62 using (Graphics g = this.CreateGraphics())
63 {
64 titleSize = g.MeasureString(title, titleFont);
65 }
66 }
67 else
68 {
69 titleSize = SizeF.Empty;
70 }
71 titleSize.Height += 20;
72 ResetWorkingRect();
73 }
完整代码
// ***********************************************************************
// Assembly : HZH_Controls
// Created : 2019-09-25
//
// ***********************************************************************
// <copyright file="UCRadarChart.cs">
// Copyright by Huang Zhenghui(黄正辉) All, QQ group:568015492 QQ:623128629 Email:623128629@qq.com
// </copyright>
//
// Blog: https://www.cnblogs.com/bfyx
// GitHub:https://github.com/kwwwvagaa/NetWinformControl
// gitee:https://gitee.com/kwwwvagaa/net_winform_custom_control.git
//
// If you use this code, please keep this note.
// ***********************************************************************
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.ComponentModel;
namespace HZH_Controls.Controls
{
/// <summary>
/// Class UCRadarChart.
/// Implements the <see cref="System.Windows.Forms.UserControl" />
/// </summary>
/// <seealso cref="System.Windows.Forms.UserControl" />
public class UCRadarChart : UserControl
{
/// <summary>
/// The split count
/// </summary>
private int splitCount = 5;
/// <summary>
/// Gets or sets the split count.
/// </summary>
/// <value>The split count.</value>
[Browsable(true)]
[Category("自定义")]
[Description("获取或设置分隔份数")]
public int SplitCount
{
get { return splitCount; }
set
{
splitCount = value;
Invalidate();
}
}
/// <summary>
/// The split odd color
/// </summary>
private Color splitOddColor = Color.White;
/// <summary>
/// 分隔奇数栏背景色
/// </summary>
/// <value>The color of the split odd.</value>
[Browsable(true)]
[Category("自定义")]
[Description("获取或设置分隔奇数栏背景色")]
public Color SplitOddColor
{
get { return splitOddColor; }
set
{
splitOddColor = value;
Invalidate();
}
}
/// <summary>
/// The split even color
/// </summary>
private Color splitEvenColor = Color.FromArgb(232, 232, 232);
/// <summary>
/// 分隔偶数栏背景色
/// </summary>
/// <value>The color of the split even.</value>
[Browsable(true)]
[Category("自定义")]
[Description("获取或设置分隔偶数栏背景色")]
public Color SplitEvenColor
{
get { return splitEvenColor; }
set { splitEvenColor = value; }
}
/// <summary>
/// The line color
/// </summary>
private Color lineColor = Color.FromArgb(153, 153, 153);
/// <summary>
/// Gets or sets the color of the line.
/// </summary>
/// <value>The color of the line.</value>
[Browsable(true)]
[Category("自定义")]
[Description("获取或设置线条色")]
public Color LineColor
{
get { return lineColor; }
set
{
lineColor = value;
Invalidate();
}
}
/// <summary>
/// The radar positions
/// </summary>
private RadarPosition[] radarPositions;
/// <summary>
/// 节点列表,至少需要3个
/// </summary>
/// <value>The radar positions.</value>
[Browsable(true)]
[Category("自定义")]
[Description("获取或设置节点,至少需要3个")]
public RadarPosition[] RadarPositions
{
get { return radarPositions; }
set
{
radarPositions = value;
Invalidate();
}
}
/// <summary>
/// The title
/// </summary>
private string title;
/// <summary>
/// 标题
/// </summary>
/// <value>The title.</value>
[Browsable(true)]
[Category("自定义")]
[Description("获取或设置标题")]
public string Title
{
get { return title; }
set
{
title = value;
ResetTitleSize();
Invalidate();
}
}
/// <summary>
/// The title font
/// </summary>
private Font titleFont = new Font("微软雅黑", 12);
/// <summary>
/// Gets or sets the title font.
/// </summary>
/// <value>The title font.</value>
[Browsable(true)]
[Category("自定义")]
[Description("获取或设置标题字体")]
public Font TitleFont
{
get { return titleFont; }
set
{
titleFont = value;
ResetTitleSize();
Invalidate();
}
}
/// <summary>
/// The title color
/// </summary>
private Color titleColor = Color.Black;
/// <summary>
/// Gets or sets the color of the title.
/// </summary>
/// <value>The color of the title.</value>
[Browsable(true)]
[Category("自定义")]
[Description("获取或设置标题文本颜色")]
public Color TitleColor
{
get { return titleColor; }
set
{
titleColor = value;
Invalidate();
}
}
/// <summary>
/// The lines
/// </summary>
private RadarLine[] lines;
/// <summary>
/// Gets or sets the lines.
/// </summary>
/// <value>The lines.</value>
[Browsable(true)]
[Category("自定义")]
[Description("获取或设置值线条,Values长度必须与RadarPositions长度一致,否则无法显示")]
public RadarLine[] Lines
{
get { return lines; }
set
{
lines = value;
Invalidate();
}
}
/// <summary>
/// The title size
/// </summary>
SizeF titleSize = SizeF.Empty;
/// <summary>
/// The m rect working
/// </summary>
private RectangleF m_rectWorking = Rectangle.Empty;
/// <summary>
/// The line value type size
/// </summary>
SizeF lineValueTypeSize = SizeF.Empty;
/// <summary>
/// The int line value COM count
/// </summary>
int intLineValueComCount = 0;
/// <summary>
/// The int line value row count
/// </summary>
int intLineValueRowCount = 0;
/// <summary>
/// Initializes a new instance of the <see cref="UCRadarChart"/> class.
/// </summary>
public UCRadarChart()
{
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);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None;
this.SizeChanged += UCRadarChart_SizeChanged;
Size = new System.Drawing.Size(150, 150);
radarPositions = new RadarPosition[0];
if (ControlHelper.IsDesignMode())
{
radarPositions = new RadarPosition[6];
for (int i = 0; i < 6; i++)
{
radarPositions[i] = new RadarPosition
{
Text = "Item" + (i + 1),
MaxValue = 100
};
}
}
lines = new RadarLine[0];
if (ControlHelper.IsDesignMode())
{
Random r = new Random();
lines = new RadarLine[2];
for (int i = 0; i < 2; i++)
{
lines[i] = new RadarLine()
{
Name = "line" + i
};
lines[i].Values = new double[radarPositions.Length];
for (int j = 0; j < radarPositions.Length; j++)
{
lines[i].Values[j] = r.Next(20, (int)radarPositions[j].MaxValue);
}
}
}
}
/// <summary>
/// Handles the SizeChanged event of the UCRadarChart control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
void UCRadarChart_SizeChanged(object sender, EventArgs e)
{
ResetWorkingRect();
}
/// <summary>
/// Resets the working rect.
/// </summary>
private void ResetWorkingRect()
{
if (lines != null && lines.Length > 0)
{
using (Graphics g = this.CreateGraphics())
{
foreach (var item in lines)
{
var s = g.MeasureString(item.Name, Font);
if (s.Width > lineValueTypeSize.Width)
lineValueTypeSize = s;
}
}
}
var lineTypePanelHeight = 0f;
if (lineValueTypeSize != SizeF.Empty)
{
intLineValueComCount = (int)(this.Width / (lineValueTypeSize.Width + 25));
intLineValueRowCount = lines.Length / intLineValueComCount;
if (lines.Length % intLineValueComCount != 0)
{
intLineValueRowCount++;
}
lineTypePanelHeight = (lineValueTypeSize.Height + 10) * intLineValueRowCount;
}
var min = Math.Min(this.Width, this.Height - titleSize.Height - lineTypePanelHeight);
var rectWorking = new RectangleF((this.Width - min) / 2 + 10, titleSize.Height + lineTypePanelHeight + 10, min - 10, min - 10);
//处理文字
float fltSplitAngle = 360F / radarPositions.Length;
float fltRadiusWidth = rectWorking.Width / 2;
float minX = rectWorking.Left;
float maxX = rectWorking.Right;
float minY = rectWorking.Top;
float maxY = rectWorking.Bottom;
using (Graphics g = this.CreateGraphics())
{
PointF centrePoint = new PointF(rectWorking.Left + rectWorking.Width / 2, rectWorking.Top + rectWorking.Height / 2);
for (int i = 0; i < radarPositions.Length; i++)
{
float fltAngle = 270 + fltSplitAngle * i;
fltAngle = fltAngle % 360;
PointF _point = GetPointByAngle(centrePoint, fltAngle, fltRadiusWidth);
var _txtSize = g.MeasureString(radarPositions[i].Text, Font);
if (_point.X < centrePoint.X)//左
{
if (_point.X - _txtSize.Width < minX)
{
minX = rectWorking.Left + _txtSize.Width;
}
}
else//右
{
if (_point.X + _txtSize.Width > maxX)
{
maxX = rectWorking.Right - _txtSize.Width;
}
}
if (_point.Y < centrePoint.Y)//上
{
if (_point.Y - _txtSize.Height < minY)
{
minY = rectWorking.Top + _txtSize.Height;
}
}
else//下
{
if (_point.Y + _txtSize.Height > maxY)
{
maxY = rectWorking.Bottom - _txtSize.Height;
}
}
}
}
min = Math.Min(maxX - minX, maxY - minY);
m_rectWorking = new RectangleF(minX, minY, min, min);
}
/// <summary>
/// 引发 <see cref="E:System.Windows.Forms.Control.Paint" /> 事件。
/// </summary>
/// <param name="e">包含事件数据的 <see cref="T:System.Windows.Forms.PaintEventArgs" />。</param>
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
var g = e.Graphics;
g.SetGDIHigh();
if (!string.IsNullOrEmpty(title))
{
g.DrawString(title, titleFont, new SolidBrush(titleColor), new RectangleF(m_rectWorking.Left + (m_rectWorking.Width - titleSize.Width) / 2, m_rectWorking.Top - titleSize.Height - 10 - (intLineValueRowCount * (10 + lineValueTypeSize.Height)), titleSize.Width, titleSize.Height));
}
if (radarPositions.Length <= 2)
{
g.DrawString("至少需要3个顶点", Font, new SolidBrush(Color.Black), m_rectWorking, new StringFormat() { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center });
return;
}
var y = m_rectWorking.Top - 20 - (intLineValueRowCount * (10 + lineValueTypeSize.Height));
for (int i = 0; i < intLineValueRowCount; i++)
{
var x = 0f;
int intCount = intLineValueComCount;
if (i == intLineValueRowCount - 1)
{
intCount = lines.Length % intLineValueComCount;
}
x = m_rectWorking.Left + (m_rectWorking.Width - intCount * (lineValueTypeSize.Width + 25)) / 2;
for (int j = 0; j < intCount; j++)
{
g.FillRectangle(new SolidBrush(lines[i * intLineValueComCount + j].LineColor.Value), new RectangleF(x + (lineValueTypeSize.Width + 25)*j, y + lineValueTypeSize.Height * i, 15, lineValueTypeSize.Height));
g.DrawString(lines[i * intLineValueComCount + j].Name, Font, new SolidBrush(lines[i * intLineValueComCount + j].LineColor.Value), new PointF(x + (lineValueTypeSize.Width + 25) * j + 20, y + lineValueTypeSize.Height * i));
}
}
float fltSplitAngle = 360F / radarPositions.Length;
float fltRadiusWidth = m_rectWorking.Width / 2;
float fltSplitRadiusWidth = fltRadiusWidth / splitCount;
PointF centrePoint = new PointF(m_rectWorking.Left + m_rectWorking.Width / 2, m_rectWorking.Top + m_rectWorking.Height / 2);
List<List<PointF>> lstRingPoints = new List<List<PointF>>(splitCount);
//分割点
for (int i = 0; i < radarPositions.Length; i++)
{
float fltAngle = 270 + fltSplitAngle * i;
fltAngle = fltAngle % 360;
for (int j = 0; j < splitCount; j++)
{
if (i == 0)
{
lstRingPoints.Add(new List<PointF>());
}
PointF _point = GetPointByAngle(centrePoint, fltAngle, fltSplitRadiusWidth * (splitCount - j));
lstRingPoints[j].Add(_point);
}
}
for (int i = 0; i < lstRingPoints.Count; i++)
{
var ring = lstRingPoints[i];
GraphicsPath path = new GraphicsPath();
path.AddLines(ring.ToArray());
if ((lstRingPoints.Count - i) % 2 == 0)
{
g.FillPath(new SolidBrush(splitEvenColor), path);
}
else
{
g.FillPath(new SolidBrush(splitOddColor), path);
}
}
//画环
foreach (var ring in lstRingPoints)
{
ring.Add(ring[0]);
g.DrawLines(new Pen(new SolidBrush(lineColor)), ring.ToArray());
}
//分割线
foreach (var item in lstRingPoints[0])
{
g.DrawLine(new Pen(new SolidBrush(lineColor)), centrePoint, item);
}
//值
for (int i = 0; i < lines.Length; i++)
{
var line = lines[i];
if (line.Values.Length != radarPositions.Length)//如果数据长度和节点长度不一致则不绘制
continue;
if (line.LineColor == null || line.LineColor == Color.Empty || line.LineColor == Color.Transparent)
line.LineColor = ControlHelper.Colors[i + 13];
List<PointF> ps = new List<PointF>();
for (int j = 0; j < radarPositions.Length; j++)
{
float fltAngle = 270 + fltSplitAngle * j;
fltAngle = fltAngle % 360;
PointF _point = GetPointByAngle(centrePoint, fltAngle, fltRadiusWidth * (float)(line.Values[j] / radarPositions[i].MaxValue));
ps.Add(_point);
}
ps.Add(ps[0]);
if (line.FillColor != null && line.FillColor != Color.Empty && line.FillColor != Color.Transparent)
{
GraphicsPath path = new GraphicsPath();
path.AddLines(ps.ToArray());
g.FillPath(new SolidBrush(line.FillColor.Value), path);
}
g.DrawLines(new Pen(new SolidBrush(line.LineColor.Value), 2), ps.ToArray());
for (int j = 0; j < radarPositions.Length; j++)
{
var item = ps[j];
g.FillEllipse(new SolidBrush(Color.White), new RectangleF(item.X - 3, item.Y - 3, 6, 6));
g.DrawEllipse(new Pen(new SolidBrush(line.LineColor.Value)), new RectangleF(item.X - 3, item.Y - 3, 6, 6));
if (line.ShowValueText)
{
var valueSize = g.MeasureString(line.Values[j].ToString("0.##"), Font);
g.DrawString(line.Values[j].ToString("0.##"), Font, new SolidBrush(line.LineColor.Value), new PointF(item.X - valueSize.Width / 2, item.Y - valueSize.Height - 5));
}
}
}
//文本
for (int i = 0; i < radarPositions.Length; i++)
{
PointF point = lstRingPoints[0][i];
var txtSize = g.MeasureString(radarPositions[i].Text, Font);
if (point.X == centrePoint.X)
{
if (point.Y > centrePoint.Y)
{
g.DrawString(radarPositions[i].Text, Font, new SolidBrush(ForeColor), new PointF(point.X - txtSize.Width / 2, point.Y + 10));
}
else
{
g.DrawString(radarPositions[i].Text, Font, new SolidBrush(ForeColor), new PointF(point.X - txtSize.Width / 2, point.Y - 10 - txtSize.Height));
}
}
else if (point.Y == centrePoint.Y)
{
if (point.X < centrePoint.X)
g.DrawString(radarPositions[i].Text, Font, new SolidBrush(ForeColor), new PointF(point.X - 10 - txtSize.Width, point.Y - txtSize.Height / 2));
else
g.DrawString(radarPositions[i].Text, Font, new SolidBrush(ForeColor), new PointF(point.X + 10, point.Y - txtSize.Height / 2));
}
else if (point.X < centrePoint.X)//左
{
if (point.Y < centrePoint.Y)//左上
{
g.DrawString(radarPositions[i].Text, Font, new SolidBrush(ForeColor), new PointF(point.X - 10 - txtSize.Width, point.Y - 10 + txtSize.Height / 2));
}
else//左下
{
g.DrawString(radarPositions[i].Text, Font, new SolidBrush(ForeColor), new PointF(point.X - 10 - txtSize.Width, point.Y + 10 - txtSize.Height / 2));
}
}
else
{
if (point.Y < centrePoint.Y)//右上
{
g.DrawString(radarPositions[i].Text, Font, new SolidBrush(ForeColor), new PointF(point.X + 10, point.Y - 10 + txtSize.Height / 2));
}
else//右下
{
g.DrawString(radarPositions[i].Text, Font, new SolidBrush(ForeColor), new PointF(point.X + 10, point.Y + 10 - txtSize.Height / 2));
}
}
}
}
#region 根据中心点、角度、半径计算圆边坐标点 English:Calculating the coordinate points of circular edge according to the center point, angle and radius
/// <summary>
/// 功能描述:根据中心点、角度、半径计算圆边坐标点 English:Calculating the coordinate points of circular edge according to the center point, angle and radius
/// 作 者:HZH
/// 创建日期:2019-09-25 09:46:32
/// 任务编号:POS
/// </summary>
/// <param name="centrePoint">centrePoint</param>
/// <param name="fltAngle">fltAngle</param>
/// <param name="fltRadiusWidth">fltRadiusWidth</param>
/// <returns>返回值</returns>
private PointF GetPointByAngle(PointF centrePoint, float fltAngle, float fltRadiusWidth)
{
PointF p = centrePoint;
if (fltAngle == 0)
{
p.X += fltRadiusWidth;
}
else if (fltAngle == 90)
{
p.Y += fltRadiusWidth;
}
else if (fltAngle == 180)
{
p.X -= fltRadiusWidth;
}
else if (fltAngle == 270)
{
p.Y -= fltRadiusWidth;
}
else if (fltAngle > 0 && fltAngle < 90)
{
p.Y += (float)Math.Sin(Math.PI * (fltAngle / 180.00F)) * fltRadiusWidth;
p.X += (float)Math.Cos(Math.PI * (fltAngle / 180.00F)) * fltRadiusWidth;
}
else if (fltAngle > 90 && fltAngle < 180)
{
p.Y += (float)Math.Sin(Math.PI * ((180 - fltAngle) / 180.00F)) * fltRadiusWidth;
p.X -= (float)Math.Cos(Math.PI * ((180 - fltAngle) / 180.00F)) * fltRadiusWidth;
}
else if (fltAngle > 180 && fltAngle < 270)
{
p.Y -= (float)Math.Sin(Math.PI * ((fltAngle - 180) / 180.00F)) * fltRadiusWidth;
p.X -= (float)Math.Cos(Math.PI * ((fltAngle - 180) / 180.00F)) * fltRadiusWidth;
}
else if (fltAngle > 270 && fltAngle < 360)
{
p.Y -= (float)Math.Sin(Math.PI * ((360 - fltAngle) / 180.00F)) * fltRadiusWidth;
p.X += (float)Math.Cos(Math.PI * ((360 - fltAngle) / 180.00F)) * fltRadiusWidth;
}
return p;
}
#endregion
/// <summary>
/// Resets the size of the title.
/// </summary>
private void ResetTitleSize()
{
if (!string.IsNullOrEmpty(title))
{
using (Graphics g = this.CreateGraphics())
{
titleSize = g.MeasureString(title, titleFont);
}
}
else
{
titleSize = SizeF.Empty;
}
titleSize.Height += 20;
ResetWorkingRect();
}
}
}