快速高效的仪表
本文将向您展示如何使用 .NET 2.0 和 GDI+ 编写一个高性能控件。
引言
本代码演示了如何使用 C# 和 .NET 2.0 构建一个快速高效的控件。
我曾经用 C++、ATL 和 GDI 写过一个类似的 ActiveX 控件,想知道是否可以使用 .NET 和 GDI+ 编写高性能代码。我需要它用于另一个项目。所以,我编写了这个小控件来展示它确实可行。
代码工作原理
代码包含一个 C# 应用程序和一个自定义控件。自定义控件才是真正有趣的部分。
从 Control 派生
我们从 `Control` 派生,因为它不像 `UserControl` 那样会提供我们实际上不需要的所有属性。
public partial class AGauge : Control
处理属性
隐藏、重写不需要的属性
嗯,设计器中仍然会显示一些不必要的属性。在 C# 中,您可以使用 `new` 关键字来摆脱它们(在 VB 中是 shadows)。
public new Boolean AllowDrop, AutoSize, ForeColor, ImeMode
重写有用的属性
对于您想要使用但具有不同行为的属性,您可以使用 `override` 关键字(如果可重写),以告知程序调用此重写的属性,而不是基类的实现,在我们的例子中是 `Control` 中的实现。
public override System.Drawing.Color BackColor..
public override System.Drawing.Font Font..
public override System.Windows.Forms.ImageLayout BackgroundImageLayout..
自定义属性
为了能够进一步在设计器中自定义控件,我们需要添加一些我们自己的属性。例如:
[System.ComponentModel.Browsable(true),
System.ComponentModel.Category("AGauge"),
System.ComponentModel.Description("The value.")]
public Single Value..
`Browsable` 特性告诉设计器是否在工具箱中显示该属性。`Category` 特性告诉设计器,如果选择了分类视图,该属性应该显示在哪个类别下,而 `Description` 特性为该属性添加了一个设计器可以在工具箱中显示的描述。
事件和委托
事件可以携带附加信息,这些信息将被发送到“监听”该事件的程序,例如,窗体的该事件的处理程序。
自定义事件参数
我们希望事件能够携带指针所在的范围编号(如果它从一个范围变到另一个范围)。为了向事件添加一些数据,我们从标准的事件参数派生并添加一个变量,该变量在构造函数中初始化。这将保存随事件发送的额外信息。
public class ValueInRangeChangedEventArgs : EventArgs
{
public Int32 valueInRange;
public ValueInRangeChangedEventArgs(Int32 valueInRange)
{
this.valueInRange = valueInRange;
}
}
事件委托
“监听”我们事件的事件处理程序必须是能够“理解”我们事件的类型。通过委托声明,我们定义了这种类型。
public delegate void ValueInRangeChangedDelegate(Object sender,
ValueInRangeChangedEventArgs e);
以及事件
[Description("This event is raised if the value falls into a defined range.")]
public event ValueInRangeChangedDelegate ValueInRangeChanged;
事件的类型是我们通过 `delegate` 语句定义的类型。`Description` 特性使设计器能够为该事件在工具箱中显示描述。
构造函数
构造函数在创建控件时被调用,例如,在它显示在设计器中之前。在这里,我们将控件的样式设置为启用双缓冲。这实际上不是必需的,因为我们将进行自己的双缓冲,但这样做并没有坏处。
public AGauge()
{
InitializeComponent();
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
}
重写成员函数
我们需要重写一些成员函数。
首先,我们重写 `OnPaintBackground` 来确保每次刷新控件时背景都不会被重绘,这会消耗过多的 CPU,即使启用了双缓冲。一个缺点是我们必须自己处理背景图像的绘制,但这并不是一个太大的问题。
protected override void OnPaintBackground(PaintEventArgs pevent)
{
}
如果控件被调整大小,我们需要刷新它。所以我们重写 `OnResize`。
protected override void OnResize(EventArgs e)
{
drawGaugeBackground = true;
Refresh();
}
全局变量“`drawGaugeBackground`”被设置为 `true`,以告知控件完全重绘自身。`Refresh` 强制控件重绘,或者如果你愿意称之为 `OnPaint`,在底层,会发送一个 Windows 消息,但这是另一回事了。
最后,我们需要重写 `OnPaint` 来向用户显示一些输出。
这就是我们的控件真正做的事情,它向用户显示输出。它不像滚动条那样处理用户输入。滚动条会重写 `OnMouseMove`、`OnMouseDown`、`OnKeyPressed` 等。`OnPaint` 是我们控件的核心。
protected override void OnPaint(PaintEventArgs pe)
`OnPaint`,每次控件重绘时都会被调用,例如,如果仪表的值改变了,它会决定是完全重绘自身还是仅使用高效的 `DrawImage` 函数绘制背景部分。如果背景没有改变,它只需要绘制指针,从而避免每次都调用耗时的 GDI+ 函数。背景改变,例如,如果颜色等属性发生变化,或者控件被调整大小时。
结论
因此,通过使用双缓冲和位块传输(`DrawImage`),确实可以利用 GDI+ 编写快速高效的控件。
如果您比 C# 更喜欢 VB,您可以在 SourceForge 上搜索“`SpeedyHMI`”,我写的这个项目包含了用 VB 编写的这个仪表。
下载、构建、运行,尽情享受吧!