自定义控件学习(2)
自定义控件学习 仪表盘
一、现有案例学习
案例1:
实际中修改 实时值显示 属性就能让仪表盘的指针进行变动
使用到的属性:
-
外环左边颜色:左侧环用颜色显示正常
-
外环右边颜色:右侧环用颜色显示异常。比如气压过高的报警颜色
如果有三段颜色需求可以考虑把外环圆分为三段进行绘制,本例中绘制了两段
-
刻度颜色:外侧圆环的刻度颜色
-
左半部分的角度:0°-270°之间
-
内环圆所占比例
-
刻度圆所占比例
-
实时数据显示高度所占比例
-
仪表显示量程
-
内环宽度
-
外环宽度
-
实时值显示
-
中心圆半径
-
显示前缀
-
刻度字体
-
显示字体
-
是否显示实时值
-
实时值显示颜色
属性部分:
#region Fileds
private Graphics g;
private Pen p;
private SolidBrush sb;
private int width;
private int height;
private Color leftColor = Color.FromArgb(113, 152, 54);
[Browsable(true)]
[Category("自定义属性")]
[Description("外环左边颜色")]
public Color LeftColor
{
get { return leftColor; }
set
{
leftColor = value;
this.Invalidate();
}
}
private Color rightColor = Color.FromArgb(187, 187, 187);
[Browsable(true)]
[Category("自定义属性")]
[Description("外环右边颜色")]
public Color RightColor
{
get { return rightColor; }
set
{
rightColor = value;
this.Invalidate();
}
}
private Color textColor = Color.Black;
[Browsable(true)]
[Category("自定义属性")]
[Description("刻度颜色")]
public Color TextColor
{
get { return textColor; }
set
{
textColor = value;
this.Invalidate();
}
}
private float leftAngle = 168.75f;
[Browsable(true)]
[Category("自定义属性")]
[Description("左半部分的角度:0°-270°之间")]
public float LeftAngle
{
get { return leftAngle; }
set
{
if (value > 270.0f || value < 0.0f)
{
return;
}
leftAngle = value;
this.Invalidate();
}
}
private float inScale = 0.5f;
[Browsable(true)]
[Category("自定义属性")]
[Description("内环圆所占比例:0 - 1 之间")]
public float InScale
{
get { return inScale; }
set
{
if (value > 1.0f || value < 0.0f)
{
return;
}
inScale = value;
this.Invalidate();
}
}
private float outScale = 0.8f;
[Browsable(true)]
[Category("自定义属性")]
[Description("刻度圆所占比例:0 - 1 之间")]
public float OutScale
{
get { return outScale; }
set
{
if (value > 1.0f || value < 0.0f)
{
return;
}
outScale = value;
this.Invalidate();
}
}
private float textShowScale = 0.88f;
[Browsable(true)]
[Category("自定义属性")]
[Description("实时数据显示高度所占比例:0 - 1 之间,值越小越靠上")]
public float TextShowScale
{
get { return textShowScale; }
set
{
if (value > 1.0f || value < 0.0f)
{
return;
}
textShowScale = value;
this.Invalidate();
}
}
private float range = 160.0f;
[Browsable(true)]
[Category("自定义属性")]
[Description("仪表显示量程")]
public float Range
{
get { return range; }
set
{
if (value <= 0.0f)
{
return;
}
range = value;
this.Invalidate();
}
}
private int inThickness = 12;
[Browsable(true)]
[Category("自定义属性")]
[Description("内环宽度")]
public int InThickness
{
get { return inThickness; }
set
{
if (value <= 0)
{
return;
}
inThickness = value;
this.Invalidate();
}
}
private int outThickness = 5;
[Browsable(true)]
[Category("自定义属性")]
[Description("外环宽度")]
public int OutThickness
{
get { return outThickness; }
set
{
if (value <= 0)
{
return;
}
outThickness = value;
this.Invalidate();
}
}
private float currentValue = 100.0f;
[Browsable(true)]
[Category("自定义属性")]
[Description("实时值显示")]
public float CurrentValue
{
get { return currentValue; }
set
{
if (value <= 0.0f || value > range)
{
return;
}
currentValue = value;
this.Invalidate();
}
}
private float centerRadius = 12.0f;
[Browsable(true)]
[Category("自定义属性")]
[Description("中心圆半径")]
public float CenterRadius
{
get { return centerRadius; }
set
{
if (value <= 0.0f)
{
return;
}
centerRadius = value;
this.Invalidate();
}
}
private string textPrefix = "实际温度:";
[Browsable(true)]
[Category("自定义属性")]
[Description("显示前缀")]
public string TextPrefix
{
get { return textPrefix; }
set
{
textPrefix = value;
this.Invalidate();
}
}
private string unit = "℃";
[Browsable(true)]
[Category("自定义属性")]
[Description("显示前缀")]
public string Unit
{
get { return unit; }
set
{
unit = value;
this.Invalidate();
}
}
private Font scaleFont = new Font(new FontFamily("微软雅黑"), 8.0f);
[Browsable(true)]
[Category("自定义属性")]
[Description("刻度字体")]
public Font ScaleFont
{
get { return scaleFont; }
set
{
scaleFont = value;
this.Invalidate();
}
}
private Font textShowFont = new Font(new FontFamily("微软雅黑"), 10.0f);
[Browsable(true)]
[Category("自定义属性")]
[Description("显示字体")]
public Font TxtShowFont
{
get { return textShowFont; }
set
{
textShowFont = value;
this.Invalidate();
}
}
private bool isTextShow = true;
[Browsable(true)]
[Category("自定义属性")]
[Description("是否显示实时值")]
public bool IsTextShow
{
get { return isTextShow; }
set
{
isTextShow = value;
this.Invalidate();
}
}
private Color textShowColor = Color.Black;
[Browsable(true)]
[Category("自定义属性")]
[Description("实时值显示颜色")]
public Color TextShowColor
{
get { return textShowColor; }
set
{
textShowColor = value;
this.Invalidate();
}
}
#endregion
重绘部分:
#region Override
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
//设置画布属性
g = e.Graphics;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
this.width = this.Width;
this.height = this.Height;
//特殊情况处理
if (this.width <= 20 || this.height <= 20)
{
return;
}
//获取圆心位置
Point center = GetCenterPoint();
//绘制外环
g.RotateTransform(0.0f);
p = new Pen(leftColor, outThickness);
g.DrawArc(p, new Rectangle(10, 10, center.X * 2 - 20, center.Y * 2 - 20), -225.0f, leftAngle);
p = new Pen(rightColor, outThickness);
g.DrawArc(p, new Rectangle(10, 10, center.X * 2 - 20, center.Y * 2 - 20), -225.0f + leftAngle, 270.0f - leftAngle);
g.TranslateTransform(center.X, center.Y);
//画刻度
g.RotateTransform(-135.0f);
for (int i = 0; i < 9; i++)
{
if (33.75f * i > leftAngle)
{
sb = new SolidBrush(rightColor);
}
else
{
sb = new SolidBrush(leftColor);
}
g.FillRectangle(sb, new RectangleF(-2.0f, center.Y * (-1.0f) + 5.0f, 4.0f, 12.0f));
g.RotateTransform(33.75f);
}
//绘制刻度值
//33.75+270.0
g.RotateTransform(-303.75f);
//复杂一点、横着的刻度值
//宽度为60,高度为20
g.RotateTransform(135.0f);
for (int i = 0; i < 9; i++)
{
float angle = -225.0f + 33.75f * i;
double x1 = Math.Cos(angle * Math.PI / 180);
double y1 = Math.Sin(angle * Math.PI / 180);
float x = Convert.ToSingle(center.X * outScale * x1);
float y = Convert.ToSingle(center.Y * outScale * y1);
StringFormat sf = new StringFormat();
if (i == 4)
{
x = x - 30;
sf.Alignment = StringAlignment.Center;
}
else if (i > 4)
{
x = x - 60;
sf.Alignment = StringAlignment.Far;
}
else if (i < 4)
{
sf.Alignment = StringAlignment.Near;
}
RectangleF rec = new RectangleF(x, y, 60, 20);
if (range % 8 == 0)
{
g.DrawString((range / 8 * i).ToString(), scaleFont, new SolidBrush(textColor), rec, sf);
}
else
{
g.DrawString((range / 8 * i).ToString("f1"), scaleFont, new SolidBrush(textColor), rec, sf);
}
}
//画内圆
g.FillEllipse(new SolidBrush(leftColor), new RectangleF(-centerRadius, -centerRadius, centerRadius * 2, centerRadius * 2));
//画内圆实际值
p = new Pen(leftColor, inThickness);
float sweepangle = currentValue / range * 270.0f;
g.DrawArc(p, new RectangleF(center.X * inScale * (-1.0f), center.Y * inScale * (-1.0f), center.X * 2 * inScale, center.Y * 2 * inScale), -225.0f, sweepangle);
//画指针
g.RotateTransform(-135.0f);
g.RotateTransform(sweepangle);
p = new Pen(leftColor, 3.0f);
PointF endPoint = new PointF(-1.5f, (center.Y * inScale + inThickness * 0.5f) * (-1.0f));
g.DrawLine(p, new PointF(0, 0), endPoint);
//写标识数组
if (isTextShow)
{
g.RotateTransform(sweepangle * (-1.0f));
g.RotateTransform(135.0f);
StringFormat sf = new StringFormat();
sf.Alignment = StringAlignment.Center;
string val = textPrefix + currentValue.ToString() + " " + unit;
RectangleF rec = new RectangleF(center.X * (-1.0f), this.height * textShowScale - center.Y, center.X * 2, this.height * (1.0f - textShowScale));
g.DrawString(val, textShowFont, new SolidBrush(textShowColor), rec, sf);
}
}
#endregion
#region Private Methods
private Point GetCenterPoint()
{
if (this.Height > this.Width)
{
return new Point(this.Width / 2, this.Width / 2);
}
else
{
return new Point(this.Height / 2, this.Height / 2);
}
}
#endregion
思路原理:
- 找圆心:图像可能横向或者纵向放大。选择较短的边构成的正方形中心作为圆心。
- 绘制外环时候:把外环分为左半个和右半个圆环。
- 绘制刻度:使用很小的长方形来充当刻度线。实际绘制需要向左稍微偏移一点。
- 绘制刻度值:共有9个数值,其中左边4个,中间1个,右边4个。StringAlignment枚举类有3个枚举属性。左侧的使用Near,中间的使用Center,右侧的使用Far
Center | 1 | 指定文本在布局矩形中居中对齐。 |
Far | 2 | 指定文本远离布局矩形的原点位置对齐。 在左到右布局中,远端位置是右。 在右到左布局中,远端位置是左。 |
Near | 0 | 指定文本靠近布局对齐。 在左到右布局中,近端位置是左。 在右到左布局中,近端位置是右。 |
- 绘制圆内实际值与指针:先用计算出实际值占量程的比例再乘以270°,然后绘制直线指向实际值的位置。绘制直线。
- RotateTransform。该方法是更改逆时针旋转起始角度。比如旧的样式中需要顺时针旋转270度的图形。当先使用g.RotateTransform(90)后,那起始的角度就变为90度,仅仅只需要旋转180度就好了。
- TranslateTransform(x,y)。平移圆心坐标到指定的(x,y)
- 其中所涉及到的实际绘图角度,我本人也是学得一知半解,每次需要多次调整才能绘制出大致的图形。个人感触,需要现有美工绘制出图形,然后使用工具测量出相应的数据,再去图形中进行尝试。
案例2
代码逻辑基本相同:
代码完整如下:
public ZDialPlate()
{
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);
}
#region Fileds
private Graphics g;
private Pen p;
private SolidBrush sb;
private int width;
private int height;
private Color leftColor = Color.FromArgb(113, 152, 54);
[Browsable(true)]
[Category("自定义属性")]
[Description("外环左边颜色")]
public Color LeftColor
{
get { return leftColor; }
set
{
leftColor = value;
this.Invalidate();
}
}
private Color rightColor = Color.FromArgb(187, 187, 187);
[Browsable(true)]
[Category("自定义属性")]
[Description("外环右边颜色")]
public Color RightColor
{
get { return rightColor; }
set
{
rightColor = value;
this.Invalidate();
}
}
private Color textColor = Color.Black;
[Browsable(true)]
[Category("自定义属性")]
[Description("刻度颜色")]
public Color TextColor
{
get { return textColor; }
set
{
textColor = value;
this.Invalidate();
}
}
private float leftAngle = 120f;
[Browsable(true)]
[Category("自定义属性")]
[Description("左半部分的角度:0°-180°之间")]
public float LeftAngle
{
get { return leftAngle; }
set
{
if (value > 180.0f || value < 0.0f)
{
return;
}
leftAngle = value;
this.Invalidate();
}
}
private float inScale = 0.5f;
[Browsable(true)]
[Category("自定义属性")]
[Description("内环圆所占比例:0 - 1 之间")]
public float InScale
{
get { return inScale; }
set
{
if (value > 1.0f || value < 0.0f)
{
return;
}
inScale = value;
this.Invalidate();
}
}
private float outScale = 0.8f;
[Browsable(true)]
[Category("自定义属性")]
[Description("刻度圆所占比例:0 - 1 之间")]
public float OutScale
{
get { return outScale; }
set
{
if (value > 1.0f || value < 0.0f)
{
return;
}
outScale = value;
this.Invalidate();
}
}
private float textShowScale = 0.88f;
[Browsable(true)]
[Category("自定义属性")]
[Description("实时数据显示高度所占比例:0 - 1 之间,值越小越靠上")]
public float TextShowScale
{
get { return textShowScale; }
set
{
if (value > 1.0f || value < 0.0f)
{
return;
}
textShowScale = value;
this.Invalidate();
}
}
private float range = 180.0f;
[Browsable(true)]
[Category("自定义属性")]
[Description("仪表显示量程")]
public float Range
{
get { return range; }
set
{
if (value <= 0.0f)
{
return;
}
range = value;
this.Invalidate();
}
}
private int inThickness = 12;
[Browsable(true)]
[Category("自定义属性")]
[Description("内环宽度")]
public int InThickness
{
get { return inThickness; }
set
{
if (value <= 0)
{
return;
}
inThickness = value;
this.Invalidate();
}
}
private int outThickness = 5;
[Browsable(true)]
[Category("自定义属性")]
[Description("外环宽度")]
public int OutThickness
{
get { return outThickness; }
set
{
if (value <= 0)
{
return;
}
outThickness = value;
this.Invalidate();
}
}
private float currentValue = 100.0f;
[Browsable(true)]
[Category("自定义属性")]
[Description("实时值显示")]
public float CurrentValue
{
get { return currentValue; }
set
{
if (value <= 0.0f || value > range)
{
return;
}
currentValue = value;
this.Invalidate();
}
}
private string textPrefix = "实际温度:";
[Browsable(true)]
[Category("自定义属性")]
[Description("显示前缀")]
public string TextPrefix
{
get { return textPrefix; }
set
{
textPrefix = value;
this.Invalidate();
}
}
private string unit = "℃";
[Browsable(true)]
[Category("自定义属性")]
[Description("显示前缀")]
public string Unit
{
get { return unit; }
set
{
unit = value;
this.Invalidate();
}
}
private Font scaleFont = new Font(new FontFamily("微软雅黑"), 8.0f);
[Browsable(true)]
[Category("自定义属性")]
[Description("刻度字体")]
public Font ScaleFont
{
get { return scaleFont; }
set
{
scaleFont = value;
this.Invalidate();
}
}
private Font textShowFont = new Font(new FontFamily("微软雅黑"), 10.0f);
[Browsable(true)]
[Category("自定义属性")]
[Description("显示字体")]
public Font TxtShowFont
{
get { return textShowFont; }
set
{
textShowFont = value;
this.Invalidate();
}
}
private bool isTextShow = true;
[Browsable(true)]
[Category("自定义属性")]
[Description("是否显示实时值")]
public bool IsTextShow
{
get { return isTextShow; }
set
{
isTextShow = value;
this.Invalidate();
}
}
private Color textShowColor = Color.Black;
[Browsable(true)]
[Category("自定义属性")]
[Description("实时值显示颜色")]
public Color TextShowColor
{
get { return textShowColor; }
set
{
textShowColor = value;
this.Invalidate();
}
}
#endregion
#region Override
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
g = e.Graphics;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
this.width = this.Width;
this.height = this.Height;
//特殊情况处理
if (this.width <= 20 || this.height <= 20)
{
return;
}
if (this.height < this.width * 0.5f)
{
return;
}
//画外环
p = new Pen(leftColor, outThickness);
g.DrawArc(p, new RectangleF(10, 10, this.width - 20, this.width - 20), -180.0f, leftAngle);
p = new Pen(rightColor, outThickness);
g.DrawArc(p, new RectangleF(10, 10, this.width - 20, this.width - 20), -180.0f + leftAngle, 180.0f - leftAngle);
//画刻度
g.TranslateTransform(this.width * 0.5f, this.width * 0.5f);
g.RotateTransform(-90.0f);
for (int i = 0; i < 7; i++)
{
if (30 * i > leftAngle)
{
sb = new SolidBrush(rightColor);
}
else
{
sb = new SolidBrush(leftColor);
}
g.FillRectangle(sb, new RectangleF(-3.0f, (this.width * 0.5f - 10.0f + outThickness * 0.5f + 2.0f) * (-1.0f), 6.0f, outThickness + 4.0f));
g.RotateTransform(30.0f);
}
//画刻度值
g.RotateTransform(-210.0f);
g.RotateTransform(90.0f);
for (int i = 0; i < 7; i++)
{
float angle = -180.0f + 30.0f * i;
double x1 = Math.Cos(angle * Math.PI / 180);
double y1 = Math.Sin(angle * Math.PI / 180);
float x = Convert.ToSingle(this.width * outScale * 0.5f * x1);
float y = Convert.ToSingle(this.width * outScale * 0.5f * y1);
StringFormat sf = new StringFormat();
if (i == 3)
{
x = x - 30;
sf.Alignment = StringAlignment.Center;
}
else if (i > 3)
{
x = x - 60;
sf.Alignment = StringAlignment.Far;
}
else if (i < 3)
{
sf.Alignment = StringAlignment.Near;
}
RectangleF rec = new RectangleF(x, y, 60, 20);
sb = new SolidBrush(textColor);
if (range % 6 == 0)
{
g.DrawString((range / 6 * i).ToString(), scaleFont, sb, rec, sf);
}
else
{
g.DrawString((range / 6 * i).ToString("f1"), scaleFont, sb, rec, sf);
}
}
//画实际值圆弧
p = new Pen(leftColor, inThickness);
float sweepangle = currentValue / range * 180.0f;
float xx = this.width * 0.5f * inScale * (-1.0f);
float yy = this.width * 0.5f * inScale * (-1.0f);
g.DrawArc(p, new RectangleF(xx, yy, this.width * inScale, this.width * inScale), -180.0f, sweepangle);
//绘制TextShow
if (isTextShow)
{
StringFormat sf = new StringFormat();
sf.Alignment = StringAlignment.Center;
RectangleF rec = new RectangleF(this.width * (-0.5f), this.height * textShowScale - 0.5f * this.width, this.width, this.height * (1.0f - textShowScale));
string val = textPrefix + currentValue.ToString() + " " + unit;
g.DrawString(val, textShowFont, new SolidBrush(textShowColor), rec, sf);
}
}
#endregion