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

滑动刻度盘

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.97/5 (38投票s)

2008年10月9日

CPOL

4分钟阅读

viewsIcon

98080

downloadIcon

8043

一个无尽的、移动的图形刻度仪表,具有线性的数据表示。

引言

滑动刻度是一个非常简单但性能快速、功能强大的 .NET 用户控件,它被设计成一个具有线性数据表示的图形刻度仪表。该仪表由一个静态指针(指针)和一个可移动的线性刻度组成。静态指针指向移动刻度的当前位置,并通过刻度上的图形位置显示输入值。滑动刻度是无尽的,可以在某一时刻表示无限的值(类型 double)。

背景

在 CodeProject 上,已经有很多非常有用的、外观精美的仪表。我想费心再添加一个。

这个项目是用 C#、SharpDevelop 和 .NET 2.0 开发的,使用了 GDI+。

滑动刻度继承自 System.Windows.Forms.UserControl,并重写了 OnPaint 方法以及添加了一些新属性。

刻度的当前位置。
ScaleRange 刻度的可见范围。
LargeTicksCount 主刻度的数量。
SmallTickCount 次刻度的数量。
ShadowEnabled 是否启用阴影?
ShadowColor 组件的阴影颜色。
NeedleColor 刻度指针的颜色。

在代码中,使用了父控件的以下属性:

BackColor 组件的背景颜色。
BorderStyle 指示控件是否应带有边框。
ForeColor 用于显示文本和刻度的前景色。

滑动刻度由四层组成。

  1. 背景(带背景色或透明)和边框。
  2. 线性刻度(带有一定数量的小刻度和/或主刻度及其值)。
  3. 阴影(用于 3D 外观,可禁用)。
  4. 指针。

完整的逻辑位于 OnPaint 方法中。有一个小的、不变的部分是为纯粹主义者准备的,它被包含在一个有符号区域内,可以移出 OnPaint。其他一切都在几个简单的步骤中完成:

  1. 计算第一个可能显示的主刻度及其值的位置。
  2. 迭代所有其他主刻度,并绘制它们及其值数字。
  3. 迭代所有次刻度(如果存在),并绘制它们。
  4. 绘制阴影(如果已启用),使用 LinearGradientBrush
  5. 将指针绘制在所有其他元素之上,使用其定义的颜色。
protected override void OnPaint(PaintEventArgs e)
{
    base.OnPaint(e);
        
    // Draw simple text, don't waste time with luxus render:
    e.Graphics.TextRenderingHint = TextRenderingHint.SingleBitPerPixelGridFit;
            
    #region Can be moved outside OnPaint ...
            
    // Calculate help variables
    int W = this.ClientRectangle.Width;
    int H = this.ClientRectangle.Height;

    int Wm = W / 2;
    int Hm = H / 2;
            
    // Calculate distances between ticks
    double largeTicksDistance = scaleRange / largeTicksCount;
    double smallTicksDistance = largeTicksDistance / (smallTicksCount+1);
            
    // Calculate number of pixel between small ticks
    float smallTicksPixels = (float)(W/scaleRange*smallTicksDistance);
            
    #endregion Can be moved outside OnPaint
            
    // Calculate first large tick value and position
    double tickValue = Math.Floor((curValue-scaleRange/2) / 
                       largeTicksDistance) * largeTicksDistance;
    float  tickPosition = (float)Math.Floor(Wm - W/scaleRange*(curValue-tickValue));
            
    // Create drawing resources
    Pen pen = new Pen(ForeColor);
    Brush brush = new SolidBrush(ForeColor);
            
    // For all large ticks
    for (int L=0; L<=largeTicksCount; L++)
    {
        // Draw large tick
        e.Graphics.DrawLine(pen, tickPosition-0,0, tickPosition-0,15);
        e.Graphics.DrawLine(pen, tickPosition-1,0, tickPosition-1,15);

        // Draw large tick numerical value
        StringFormat sf = new StringFormat();
        sf.Alignment = StringAlignment.Center;
        e.Graphics.DrawString(Math.Round(tickValue,2).ToString(),
                              Font, brush,
                              new PointF(tickPosition, Hm),sf);
                
        // For all small ticks
        for (int S=1; S<=smallTicksCount; S++)
        {
            // Update tick value and position
            tickValue += smallTicksDistance;
            tickPosition += smallTicksPixels;
                    
            // Draw small tick
            e.Graphics.DrawLine(pen, tickPosition,0,tickPosition,10);
        }
                
        // Update tick value and position
        tickValue += smallTicksDistance;
        tickPosition += smallTicksPixels;
    }
            
    // Dispose drawing resources
    brush.Dispose();
    pen.Dispose();
            
            
    if (ShadowEnabled)
    {
        LinearGradientBrush LGBrush = null;
                
        // Draw left side shadow
        LGBrush = new LinearGradientBrush(new Rectangle(0, 0, Wm, H),
                                          Color.FromArgb(255, ShadowColor),
                                          Color.FromArgb(0, BackColor),
                                                  000, true);
        e.Graphics.FillRectangle(LGBrush, new Rectangle(0,0, Wm, H));
        
        // Draw right side shadow
        LGBrush = new LinearGradientBrush(new Rectangle(Wm+1, 0, Wm, H),
                                          Color.FromArgb(255, ShadowColor),
                                          Color.FromArgb(0, BackColor),
                                          180, true);
        e.Graphics.FillRectangle(LGBrush, new Rectangle(Wm+1, 0, Wm, H));
          
        LGBrush.Dispose();
    }

    // Draw scale needle
    e.Graphics.DrawLine(new Pen(NeedleColor), Wm-0,0, Wm-0,H);
    e.Graphics.DrawLine(new Pen(NeedleColor), Wm-1,0, Wm-1,H);
}

使用代码

该控件具有一套很好的默认属性值,因此一旦将其包含在您自己的解决方案中,就可以直接从工具箱中选择它,将其拖放到窗体上,并立即使用。否则,只需要配置其他几个列出的属性。您只需要一行代码即可使其工作。

slidingScale1.Value = your value here!

包含的归档文件包含组件的完整源代码和已编译的二进制文件,以及一个简单的测试应用程序。

可能的增强功能

这个控件有很多改进的可能性。如果有人想做,这里有一些想法:

由于使用了 LinearGradientBrush 着色,它具有三维效果,滑动刻度看起来已经像一个真正的仪表。我个人希望在刻度盘上绘制数字以及自定义刻度线长度方面能有更大的灵活性。

为了将仪表设置为能像真实仪表一样工作,我们必须关注两个重要方面:

  1. 刻度应该是有限的和/或圆形的。
    • 真正的刻度不是无尽的丝带,而是有实际尺寸的。
    • 限制刻度并不是一个困难的问题。我们可以通过添加两个新属性 MinValueMaxValue,并控制现有 Value 属性的更改来实现。只接受定义范围内的值。
    • 一个圆形的刻度实现起来并不那么简单。对于这个功能,我们需要几个新属性和相当多的计算。
  2. 刻度不应立即响应值的变化。
    • 真正的刻度具有惯性,不可能在短时间内从一端跳到另一端。值的变化必须实现为刻度自行移动,并以小步长从旧位置平滑地滑动到新位置。它应该立即开始从当前位置滑动/旋转,并在一段时间后到达最终位置。
    • 可以通过以下方式实现这种行为:当需要更改当前值时,首先将新值设置为期望值,并启用一个定时器,该定时器以小步长递增(递减)当前值,直到它达到期望值。
    • 简单来说,我们应该制作一个动画滑动的刻度。

这些是一些其他期望的功能:

  • 垂直刻度表示。
  • 带有标记的公差限(OTL、UTL 等)的刻度。
  • 带有不同颜色区域的刻度。

此处显示的滑动刻度在测量技术方面是一个“指示器”。我们可以将其变成一个“控制器”。因此,我们应该重写 OnMouseDownOnMouseMove 方法来处理鼠标事件,并实现一个 ChangeValue 方法来宣布值的变化。

结论

这是该控件的初步版本,还有许多功能需要实现。我将尽快尝试涵盖这些。

任何建议/评论/反馈都受欢迎并备受赞赏。

如果您喜欢,请投票,如果您在商业上使用它,请在下面的讨论区描述您的成功故事。

历史

  • 2008.10.08 - 发布 1.0 版本。
  • 2008.10.16 - 发布 2.0 版本。添加了新属性和垂直方向。
© . All rights reserved.