学院首页>网络编程>ASP.NET>从头创建 Visual Basic .NET 控件

从头创建 Visual Basic .NET 控件

作者:Billy Hollis 来源:微软中国 添加时间:2006-5-21 10:45:19

从 MSDN Code Center 下载 VBNET UserControls.exe 示例文件(英文)。请注意,程序员的注释在示例程序中是英文的,而在本文中被翻译成中文,以便更好地解释该程序。

摘要:本月 Billy Hollis 将向您介绍如何从头创建可呈现其特有界面的可视控件。

我从来没有真正想过要当一名 C++ 程序员,因为我太懒了,不能那么辛苦地工作。但我必须承认,我过去常常嫉妒那些 C++ 程序员,嫉妒他们编写可视控件的能力。

Visual Basic® 6.0 及其早期版本中的控件仅限于“复合”控件(由其他控件组成的控件),这种控件称为 UserControl。在 Visual Basic 6.0 中编写能够在屏幕上呈现其特有可视外观的控件几乎是不可能的。

现在好了,可以使用功能强大的 Visual Basic .NET 编写各种类型的可视控件了!不仅可以编写复合的 UserControl,还能继承现有的控件(如 TextBox)并扩展其新功能。更重要的是,还可以从头编写能够呈现其特有界面的可视控件。

在本文中,我将从头创建一个完整的可视控件,以说明 Visual Basic .NET 的后一种功能。该控件是一个“红绿灯”- 一个包含三个圆(分别代表红、黄、绿三个灯)的矩形。图 1 显示各个灯亮时该控件的外观,控件的背景颜色设置为系统颜色 ControlDark

图 1:带有三个 TrafficLight 控件的窗体,每个控件亮不同的灯。

我们称它为 TrafficLight 控件,它可以通过代码或让用户单击灯来改变亮起的灯。

因为 TrafficLight 是一个可视的 Windows 窗体控件,它将继承 System.Windows.Forms 命名空间中的 Control 类。这样,它将具有很多预定义的属性、方法和事件,包括控制其外观的属性,如 ForeColorBackColorSizeLocation;还包括事件,如 MouseOverClick。您可以查看 .NET 文档,获得 Control 类成员的完整列表。

红绿灯也需要具有特殊的属性和事件,如下所示:

Status 属性确定亮起哪种颜色的灯。必须为以下三个枚举值之一:
  • StatusRed:红灯亮
  • StatusYellow:黄灯亮
  • StatusGreen:绿灯亮
BorderWidth 属性红绿灯周围边框的宽度。
StatusChanged 事件当通过代码或由用户单击不同的灯改变 Status 属性的值时,触发该事件。

由于这些成员不属于 Control 基类,所以我们需要包括完整的代码以处理它们。我们还需要绘制边框和三个相应颜色的灯的代码,以便在屏幕上绘制红绿灯。最后,我们需要处理用户单击圆以更改亮起灯的操作,并在更改亮起的灯时更改 Status 属性。

为了使本示例尽可能接近实际应用环境,我们还将包括能够确保在 Visual Studio® .NET IDE 中更好地使用控件的代码。我们为工具箱设置适当的图标,并包括能够使属性更好地与各属性窗口集成的逻辑。

现在让我们开始吧。

第 1 步:创建类型正确的项目

要创建一个保存 Windows 窗体控件的库,需要在 Visual Basic.NET 中启动一个新项目,选择 Windows Control Library(Windows 控件库)项目类型,然后将项目命名为 MyControls

所创建的项目实际上可以保存多个 Windows 窗体控件,每个控件都属于其各自的类,但我们只需在其中创建一个控件。

第 2 步:更改基类

在控件库中创建的类自动命名为 UserControl1,默认情况下,从 UserControl 类继承。如果我们要创建复合控件,那非常容易,只需将其他控件从工具箱中拖到设计表面上即可。

但是,由于我们要从头创建自己的控件,因此需要做一些更改。将控件类的名称从 UserControl1 更改为 TrafficLight。然后,将以下行:

    Inherits System.Windows.Forms.UserControl

更改为:

    Inherits System.Windows.Forms.Control

这样,使最一般的 Control 类成为基类。您会发现,不再显示可视设计表面,而是替换为组件设计表面。

为保持代码的一致性,也要将代码文件名从 UserControl1.vb 更改为 TrafficLight.vb。可以在 Solution Explorer(解决方案资源管理器)中进行更改:右键单击代码文件的名称,并选择 Rename(重命名)。

还需要在类模块的顶部添加几行代码。将 Option Strict 设置为 On,并导入包含我们将来要用到的某些属性的命名空间。下面是要放到代码最上面的两行:

Option Strict OnImports System.ComponentModel

第 3 步:实现属性和事件

要实现 Status 属性,首先要为可能的属性值创建枚举。将以下几行插入以 Inherits 开始的行下面:

Public Enum TrafficLightStatus    statusRed = 1    statusYellow = 2    statusGreen = 3End Enum

此枚举是公开的,也就是说使用该控件的窗体可以访问它。

在这些行下面添加以下三行:

Dim mStatus As TrafficLightStatus = TrafficLightStatus.statusGreenDim msngBorderWidth As Single = 1.0!Public Event StatusChanged(ByVal NewStatus As TrafficLightStatus)

前两行中的两个变量可用于存储 StatusBorderWidth 属性的属性值,还为这些属性设置了默认值。保存 BorderWidth 的变量必须为 Single 类型,因为它是绘制边框所用的图形语句需要的类型。默认值中的惊叹号也表明它是 Single 类型。此集合中的最后一行声明了 StatusChanged 事件。

现在,我们为 BorderWidth 属性编写代码。在标记为 Windows Form Designer Generated Code(Windows 窗体设计器生成的代码)的代码区域下插入以下行:

<DefaultValue(1.0!), _Description("红绿灯周围边框的宽度")> _Public Property BorderWidth() As Single    Get        Return msngBorderWidth    End Get    Set(ByVal Value As Single)        If msngBorderWidth <> Value Then            msngBorderWidth = Value            Me.Invalidate()        End If    End SetEnd Property

前两行包括使该属性更好地使用 IDE 的属性。DefaultValue 特性允许在 Properties(属性)窗口中将属性值重置为默认值(操作步骤稍后介绍)。Description 特性提供选中该属性时在 Properties(属性)窗口底部显示的文本。

DefaultValue 特性还有一个技巧。如果将 TrafficLight 控件放到窗体上,并保留 BorderWidth 属性的默认值,那么窗体设计器将不生成设置属性值的代码行。这使它与其他 Windows 窗体控件没有什么区别。如果您查看典型控件(如 TextBox)的设计器生成的代码,您会发现只包括设置为非默认值的属性的代码行。我们赋予 TrafficLight 控件同样的能力。

Property Get 简单明了。Property Set 子句包括可视控件属性中常见的逻辑。设置属性时,重要的是在新属性值更改控件的外观时要能够重新绘制控件。因此,Set 子句负责确定传递的新值是否与属性中现有的值不相同。如果相同,则不执行操作。如果不同,则接受新值,然后访问控件的 Invalidate 方法。此方法表明,控件的可视区域已过期,控件需要重新绘制。

Status 属性的处理有些不同,因为它是枚举值。DefaultValue 特性没有为枚举属性提供自动重置能力。在这种情况下,DefaultValue 也无法告诉设计器何时停止设置属性值的代码。因此,Status 属性的实现中不需要 DefaultValue 特性。下面是 Status 属性的代码:

<Description("红绿灯的状态(颜色)")> _Public Property Status() As TrafficLightStatus    Get        Status = mStatus    End Get    Set(ByVal Value As TrafficLightStatus)        If mStatus <> Value Then            mStatus = Value            RaiseEvent StatusChanged(mStatus)            Me.Invalidate()        End If    End SetEnd Property

看起来与 BorderWidth 属性的实现类似,只有一点不同:当 Status 属性发生改变时,除了强制重新绘制控件外,还会触发 StatusChanged 事件。

要在 Properties(属性)窗口中处理属性的自动重置,我们需要使用一种特殊的方法。由于我们的属性命名为 Status,因此必须将重置方法命名为 ResetStatus。重置方法只是恢复属性的默认值。以下是其代码:

Public Sub ResetStatus()    Me.Status = TrafficLightStatus.statusGreenEnd Sub

为了提示设计器何时需要包括一行代码以便设置 Status 属性,我们需要包括一个名为 ShouldSerializeStatus 的方法。当属性需要一行代码时,此方法返回布尔值 True,否则,则返回 False。以下是其代码:

Public Function ShouldSerializeStatus() As Boolean    If mStatus = TrafficLightStatus.statusGreen Then        Return False    Else        Return True    End IfEnd Function

第 4 步:绘制控件的外观

要使控件具有一个可视的外观,我们需要在 Paint 事件中放置逻辑。然后,每次控件需要刷新其可视外观时,就会运行该逻辑。

Windows 窗体中的 Paint 逻辑使用 .NET 中 GDI+ 部分中的类。这些类基本上包括了 Windows API 图形功能。由于适合 .NET,所以比 API 更易于使用。但是,有关它们的工作原理,需要理解以下几点。

在 Windows API 中,图形操作需要一个窗口句柄,有时称为 hWnd。在 GDI+ 中,它由 Graphics 对象取代,该对象不仅代表了绘图区域,还提供在该区域执行的操作(方法)。

例如,Graphics 对象具有以下方法,可用来绘制各种屏幕元素:

  • DrawCurve
  • DrawEllipse
  • DrawLine
  • DrawPolygon
  • DrawRectangle
  • DrawString
  • FillEllipse
  • FillPolygon

这些都是很容易理解的,只是可用方法的示例。一些更复杂的方法还允许旋转对象。我们将使用 DrawRectangle 方法绘制边框,使用 FillEllipse 方法绘制彩色的圆。

大多数绘图方法都要求使用 PenBrush 对象。Pen 对象用于绘制直线并确定直线的颜色和粗细。Brush 对象用于填充区域、确定填充区域所使用的颜色,以及一些特殊效果(例如,用位图填充区域)。我们将使用特殊的 Brush 效果使当前没有亮起的灯的颜色变暗。

下面是处理控件的 Paint 事件的代码:

Protected Overrides Sub OnPaint(ByVal pe As _                         System.Windows.Forms.PaintEventArgs)    MyBase.OnPaint(pe)    Dim grfGraphics As System.Drawing.Graphics    grfGraphics = pe.Graphics    ' 首先绘制三个代表灯的圆。    ' 一个亮起,其余两个熄灭。    DrawLight(TrafficLightStatus.statusGreen, grfGraphics)    DrawLight(TrafficLightStatus.statusYellow, grfGraphics)    DrawLight(TrafficLightStatus.statusRed, grfGraphics)    ' 现在绘制红绿灯周围的轮廓    ' 用画笔绘制轮廓,将它涂成黑色。    Dim penDrawingPen As New _        System.Drawing.Pen(System.Drawing.Color.Black, msngBorderWidth)    ' 在控件上绘制红绿灯的轮廓。     ' 首先定义要绘制的矩形。    Dim rectBorder As System.Drawing.Rectangle    rectBorder.X = 1    rectBorder.Y = 1    rectBorder.Height = Me.Height - 2    rectBorder.Width = Me.Width - 2    grfGraphics.DrawRectangle(penDrawingPen, rectBorder)    ' 释放图形对象    penDrawingPen.Dispose()    grfGraphics.Dispose()End Sub

下一页>>
站内搜索