65.9K
CodeProject 正在变化。 阅读更多。
Home

旋转仪表

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.21/5 (17投票s)

2005 年 3 月 31 日

6分钟阅读

viewsIcon

106992

downloadIcon

5710

一篇关于旋转仪表盘式自定义控件的文章。

Sample Image

目录

  1. 目录
  2. 引言
  3. 背景
  4. 绘制仪表盘
  5. 设计器集成
  6. 使用代码
  7. 关注点

简介

本项目提供了一个简单的仪表盘式 UserControl,可以集成到您的项目中。它可以旋转 360 度,并允许程序员设置一个“红区”。我还将提供一些关于 Forms Designer 集成的基本信息,这极大地增加了自定义控件的可用性。

背景

您应该具备 .NET 工具和 Windows Forms 的基本使用经验;然而,您不需要太多,因为这是一个相当简单的控件。尽管如此,它也可以为任何需要图形化显示最大值的数据的窗体增添美观的外观。

我将把那个红色的指针称为“垂直箭头”,将指向仪表盘边缘数字的黄色线条称为“射线”。数字就是数字。如果您在阅读解释时忘记了数字的意思,请随时参考此段。

绘制仪表盘

我所有的绘图都在 OnPaint 事件中完成,该事件在控件无效并刷新时(包括控件首次加载时)被调用。OnPaint 如下所示:

protected override void OnPaint(PaintEventArgs e)

protected 表示该方法只能在类内部或其派生类中访问。 override 表示该方法重写了基类(在本例中是 System.Windows.Forms.UserControl)具有相同保护级别、返回类型和参数的成员。PaintEventArgs 参数指定了要绘制的图形对象和区域。

OnPaint 方法内部,我为仪表盘建立了一个中心点,然后创建了一个名为 gpGraphicsPath,向其中添加了一个椭圆,然后将 gp 设置为控件的绘图区域。

using(GraphicsPath gp = new GraphicsPath())
{
    gp.AddEllipse(0,0,2*center,2*center);
    this.Region = new Region(gp);

请注意,该对象的长度和高度是 x 和 y 轴上到中心距离的两倍。这使我们得到一个圆形而不是椭圆形。我不知道为什么没有 DrawCircle 方法,但只需再写几行代码就可以确保您的椭圆形是圆形的。

在此之后,我们定义了一个要使用的 Brush,检查我们是否至少有一个数字和一条射线供垂直箭头指向,然后开始绘制带编号的射线。我们还需要一个 Matrix 对象,以便在围绕中心旋转时可以旋转射线。

using(Brush brush  = new SolidBrush(Color.Yellow))
{
    if (numNumbers == 0)
    numNumbers = 1;
    for (int i = 0; i < 360; i += 360 / numNumbers)
    {
        Matrix matrix = new Matrix();
        matrix.RotateAt(i + this.angle, centerPt);
        g.Transform = matrix;
        if(i >= redZone)
            g.DrawLine(Pens.Red,center, center,center, center * 3/10);
        else
            g.DrawLine(Pens.Yellow, center, center, center, center * 3/10);
        g.DrawString(((int)(i * divisor)).ToString(), 
            this.Font,brush, center - 6, center * 5 / 100,
            StringFormat.GenericTypographic);
    }
}

for 循环根据圆周 360 度和前面提到的中心点为每条射线提供了角度。您会注意到,如果角度 i 大于 redZone,我们就会用红色而不是黄色绘制射线和数字。顺便说一句,Matrix 是一个很酷的小数学工具,可以让您对 Graphics 对象进行精妙的更改,例如 g(在此从 OnPaintPaintEventArgs 参数派生)。我们将当前角度添加到角度 i 以获得绘制该特定射线/数字组合所需的总旋转量。如果您不知道 Matrix 是什么,但仍然想画颠倒的图形或让它们跳动或围绕一个点旋转,您将不得不学习一些东西。在我们绘制完每条射线(用正确的颜色)后,我们在其末端绘制数字。这里的 divisor 只是一个 float 值,我通过将我想要显示的数字最大值除以 360 得到的。当我将其乘以当前角度 i 时,我得到要提供给 gDrawString 方法的数字值,它不出所料地需要一个 string。C# 中的一切似乎都有一个 ToString() 方法,尽管结果对我们人类来说可能很奇怪。值 center - 6center * 5 / 100 告诉 DrawString 在 x 轴上从中心左侧六个像素开始绘制,在从顶部向下 1/20 的位置绘制。然后,通过 Matrix 的魔力,数字会围绕中心以正确的位置旋转。

在我绘制完仪表盘背景后,我轻松地绘制了固定不动的箭头。

using(GraphicsPath gp2 = new GraphicsPath())
{
    using( Pen pen = new Pen(arrowColor, 12))
    {
        Matrix matrix = new Matrix();
        matrix.RotateAt(0, centerPt);
        g.Transform = matrix;
        pen.EndCap = LineCap.ArrowAnchor;
        g.DrawLine(pen, center, center, center, center / 8);
        g.DrawLine(pen, center, center, (center * 9)/10, center);
        g.DrawLine(pen, center, center, (center * 11)/10, center);
        g.DrawLine(pen, center, center, center, (center * 11)/10);
    }
}

在这里,我们首先快速旋转一下,将指针回到垂直位置——否则指针会指向最后绘制的射线(比垂直位置偏一点),并尴尬地与仪表盘一起旋转——然后开始绘制带有尖锐、倒钩线端的粗线条。using 指令,正如 MSDN 所说,“定义一个作用域,在该作用域结束时将处置一个对象”。比尔·盖茨,请别告我。比尔在这里的意思是,Pen penGraphicsPath gp2 将在我们超出各自的花括号后立即被清理,这可以防止垃圾对象在堆或堆栈上累积(无论 .NET 在哪里创建 new 对象)。

设计器集成

您还会注意到这个东西有一些奇特的访问器和修改器。

[
CategoryAttribute("Appearance"),
DescriptionAttribute("Initial angle of arm")
]
public float Angle
{
    get{return angle;}
    set
    {
        angle = (360f - value);
        if (angle < 0f)
            angle = 0f;
        if(this.angle < 360f - this.RedZone)
            RedZoneHit();
        this.Refresh();
    }
}

这允许您像访问任何其他控件的属性一样访问仪表盘的属性。

private CompassCard.CompassCard card = new CompassCard.CompassCard();
card.Angle = 345;

它还让 Visual Studio .NET 中的属性窗口看到您想要提供给设计器的属性,以便客户端作者可以赋予仪表盘他们期望的外观和行为。

您会注意到,我在访问器/修改器中触发了我的 RedZoneHit()(实际上我调用了一个触发事件的方法),而不是在 OnPaint() 中重绘仪表盘时触发。这是因为,正如我第一版的细心读者指出的那样,一旦箭头进入红色区域,任何触发 OnPaint() 的操作都会触发 RedZoneEvent,导致它一遍又一遍地无谓地触发。当然,我的新方法仍然意味着当您尝试将箭头移出红色区域时会再次触发事件,但我认为,当仪表盘处于红色区域时,每次移动时都能得到通知是好事。一旦您将其移出,事件就会停止触发,一切就绪。

访问器/修改器前面还有一个看起来奇怪的语句。

[
CategoryAttribute("Appearance"),
DescriptionAttribute("Initial angle of gauge")
]

这告诉设计器,此属性应放在控件属性表中的“外观”类别下,其描述为“仪表盘的初始角度”, whatever that means。实际上,这意味着仪表盘旋转的初始角度,例如,如果您想让指针指向 34 度而不是 0 度。

CompassCard 类的定义开头,还可以找到另一个 .NET 的特殊之处。

public delegate void HitRedZone(object sender, EventArgs e);
[DefaultEventAttribute("RedZoneEvent")]
public class CompassCard : System.Windows.Forms.UserControl
{
    public event HitRedZone RedZoneEvent;

这样,Visual Studio .NET 就知道默认事件(当您在窗体上双击控件时触发的事件)是 RedZoneEvent。这为客户端作者带来了一些便利。

使用代码

如果您处理 RedZoneEvent 事件,请记住,无论何时旋转仪表盘,它都会触发,即使您将其移出红色区域。除此之外,它应该很容易使用。

CompassCard 向其属性对话框添加了以下属性:

  • Angle:仪表盘的起始旋转角度。
  • Range:仪表盘所代表的最大值。
  • RedZone:红色区域的起始点;进入该区域会触发事件。
  • ArrowColor:垂直箭头的颜色。
  • NumNumbers:仪表盘上显示的值的数量。必须大于一。

所有这些都出现在属性窗口的“外观”部分。

CompassCard 还添加了一个您可以处理的事件 RedZoneEvent,它接受参数 object sender, EventArgs e,并在您进入红色区域时通知您。

趣味点

这是一款相当有趣的编程小作品。由于我不是一个经验丰富的 .NET 开发人员,仅仅是让指针画在仪表盘上方,而不显示任何径向叶片,就很有意思。

© . All rights reserved.