创建Visual Basic .NET 控件
2008-12-31 15:49:13   来源:   评论:0 点击:

 我从来没有真正想过要当一名 C++ 程序员,因为我太懒了,不能那么辛苦地工作。但我必须承认,我过去常常嫉妒那些 C++ 程序员,嫉妒他们编写可视控件的能力。   Visual Basic® 6.0 及其早期版本中的控件 ...
 我从来没有真正想过要当一名 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 窗体控件,它将继承 S  ystem.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 On
Imports System.ComponentModel

  第 3 步:实现&&属性和&&事件

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

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

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

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

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

  前两行中的两个变量可用于存储 Status 和 BorderWidth &&属性的&&属性值,还为这些&&属性设置了默认值。保存 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 Set
End 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 Set
End Property

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

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

Public

Sub ResetStatus()
Me.Status = TrafficLightStatus.statusGreen
End Sub

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

Public Function ShouldSerializeStatus() As Boolean
If mStatus = TrafficLightStatus.statusGreen Then
Return False
Else
Return True
End If
End 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 方法绘制彩色的圆。

 

 

 大多数绘图方法都要求使用 Pen 或 Brush 对象。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

  首先使用基类绘制,它通常使用控件的背景颜色绘制背景。然后,从&&事件参数中获取控件的 Graphics 对象。

  接下来,用一个&&函数画出三个圆。有关该&&函数的内容稍后介绍。请注意,我们必须向该&&函数传递一个 Graphics 对象的引用,同时还要指示要画的圆(红、黄、绿)。

  然后是绘制轮廓的代码。声明一个具有适当位置和大小的矩形,然后传递给 Graphics 对象的 DrawRectangle 方法。

  最后,图形对象激活其 Dispose 方法。使用 GDI+ 时,最好在完成图形对象后立即释放它们。这有助于清除/Article/czxt/Index.html">操作系统绘图时所用的资源。如果要在 Windows® 98 或 Windows Me 中使用控件,管理图形资源就更加重要,因为这些/Article/czxt/Index.html">操作系统处理这种资源的能力较差。

 

 

  下面是绘制圆的&&函数:

Private Sub DrawLight(ByVal LightToDraw As TrafficLightStatus, _
ByVal grfGraphics As Graphics)

Dim nCircleX As Integer
Dim nCircleY As Integer
Dim nCircleDiameter As Integer
Dim nCircleColor As Color

' 找到所有圆的 X 坐标和直径
nCircleX = CInt(Me.Size.Width * 0.02)
nCircleDiameter = CInt(Me.Size.Width * 0.96)
Select Case LightToDraw
Case TrafficLightStatus.statusRed
If LightToDraw = Me.Status Then
nCircleColor = Color.OrangeRed
Else
nCircleColor = Color.Maroon
End If
nCircleY = CInt(Me.Size.Height * 0.01)
Case TrafficLightStatus.statusYellow
If LightToDraw = Me.Status Then
nCircleColor = Color.Yellow
Else
nCircleColor = Color.Tan
End If
nCircleY = CInt(Me.Size.Height * 0.34)
Case TrafficLightStatus.statusGreen
If LightToDraw = Me.Status Then
nCircleColor = Color.LimeGreen
Else
nCircleColor = Color.ForestGreen
End If
nCircleY = CInt(Me.Size.Height * 0.67)

End Select
Dim bshBrush As System.Drawing.Brush
If LightToDraw = Me.Status Then

bshBrush = New SolidBrush(nCircleColor)
Else
bshBrush = New SolidBrush(Color.FromArgb(60, nCircleColor))
End If

' 绘制代表红绿灯的圆
grfGraphics.FillEllipse(bshBrush, nCircleX, nCircleY, nCircleDiameter, nCircleDiameter)

' 释放笔刷
bshBrush.Dispose()

End Sub

  这是整个控件中唯一的一个复杂图形。在 GDI+ 中,在要绘制椭圆的矩形中指定左上角的 X 坐标和 Y 坐标,然后指定矩形的高度和宽度即可绘制一个椭圆。我们分别将 X 坐标和 Y 坐标称为 nCircleX 和 nCircleY。因为我们要绘制一个圆,因此矩形的高度等于宽度,用变量 nCircleDiameter 来控制该值。

  将 nCircleX 设置为刚好放到控件内(控件的宽度乘以 0.02)。nCircleY 取决于要绘制哪个灯,可以设置成靠近控件的顶部(红灯)、大约向下三分之一(黄灯)或大约向下三分之二(绿灯)。直径 nCircleDiameter 设置为等于控件宽度的 96%。

  要绘制实心椭圆,还需完成一件事,即确定要使用的颜色。颜色取决于正在绘制哪个灯以及正在绘制的灯是否亮起。亮起的灯的颜色要比熄灭的灯的颜色亮。

  创建绘图要使用的笔刷时需要使用这些颜色。如果正在绘制的灯是亮起的,即使用该颜色。如果绘制的灯是熄灭的,则要使用不同的方法实例化笔刷。下面是熄灭的灯所使用笔刷的代码行:

  bshBrush = New SolidBrush(Color.FromArgb(60, nCircleColor))

  这并不是 .NET 中较好的方法名,但 FromArgB 方法的作用是创建笔刷,并通过将笔刷与背景颜色相结合来淡化颜色。第一个参数使用的数字介于 0 至 255 之间,数字越小,背景颜色渗透越深。我们使用的值为 60,它将大大降低处于熄灭状态的灯的颜色。您可以尝试对该参数使用不同的值(或将它设置成可设置&&属性),以获得不同的效果。

  最后,Graphics 对象的 DrawEllipse 方法绘制出该圆,&&函数结束。记住,该&&函数需要调用三次以绘制三个不同的圆。

 

 

 第 5 步:使控件响应用户

 

  要允许用户更改灯的颜色,必须检测到用户的鼠标单击操作。有经验的 Visual Basic 开发人员都知道,可以使用多种方法实现这一目的。我们使用最简单的一种方法,即检测 MouseUp &&事件。下面是检测用户单击并更改 Status &&属性以与之匹配的代码:

Private Sub TrafficLight_MouseUp(ByVal sender As Object, _
ByVal e As System.Windows.Forms.MouseEventArgs) _
Handles MyBase.MouseUp
Dim nMidPointX As Integer = CInt(Me.Size.Width * 0.5)
Dim nCircleRadius As Integer = nMidPointX
If Distance(e.X, e.Y, nMidPointX, CInt(Me.Size.Height / 6)) _
< nCircleRadius Then
Me.Status = TrafficLightStatus.statusRed
Exit Sub
End If
If Distance(e.X, e.Y, nMidPointX, CInt(Me.Size.Height / 2)) _
< nCircleRadius Then
Me.Status = TrafficLightStatus.statusYellow
Exit Sub
End If
If Distance(e.X, e.Y, nMidPointX, CInt((5 * Me.Size.Height) / 6)) _
< nCircleRadius Then
Me.Status = TrafficLightStatus.statusGreen
End If

End Sub

Private Function Distance(ByVal X1 As Integer, _
ByVal Y1 As Integer, _
ByVal X2 As Integer, _
ByVal y2 As Integer) As Integer
Return CInt(System.Math.Sqrt((X1 - X2) ^ 2 + (Y1 - y2) ^ 2))
End Function

  &&事件处理非常简单。检查鼠标单击的位置和每个圆心之间的距离。(请注意,圆心分别位于控件下方 1/6、1/2 和 5/6 的位置。如果不太明白,可以在纸上画出来看看。)如果计算出的距离小于圆的半径,则更改 Status &&属性。

  距离由 Distance &&函数使用您可能在代数课中学过的公式计算。请注意,平方根&&函数是从 System.Math 命名空间中获得的,数学&&函数通常都保存在该命名空间中。

  第 6 步:清理

  为了使控件顺利地运作,我们还需要执行一些其他操作。例如,大小改变时需要重新绘制控件。而且,为了不改变控件的比例,我们需要检测影响大小的&&属性发生更改的时间,然后强制宽度等于高度的三分之一。下面是完成这两项任务的&&事件处理程序:

Private Sub TrafficLight_Resize(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles MyBase.Resize
Me.Invalidate()
End Sub

Private Sub TrafficLight_Layout(ByVal sender As Object, _
ByVal e As System.Windows.Forms.LayoutEventArgs) _
Handles MyBase.Layout
Select Case e.AffectedProperty
Case "Bounds"
Me.Width = CInt(Me.Height * 0.3333)
Case Else
' 不执行任何操作
End Select
End Sub

  最后,设置控件在工具箱中使用的图标。控件已经有一个看似齿轮的默认图标,但是我们要使用 Visual Studio .NET 附带的红绿灯图标。

 

 

控件的工具箱图标是由名为 ToolboxBitmap 的类中的特性设置的。在以 Public Class 开始的行上面插入以下行:

<ToolboxBitmap("C:\Program Files\Microsoft Visual Studio
.NET\Common7\Graphics\icons\Traffic\TRFFC09.ICO")> _

注意:所有内容都应在一行中。为了便于阅读,我们在 Studio 后放置了一个回车。粘贴该代码时,要确保它们位于一行中,Studio.NET 之间只需一个空格,并删除回车。如果您已经将 Visual Studio .NET 安装到其默认位置,那么上述代码将用 Visual Studio 目录中的图标设置该特性。如果您没有将 Visual Studio .NET 安装到其默认位置,则需要相应地更改图标的路径名。

   第 7 步:生成和测试控件

  现在 TrafficLight 控件的设计就完成了。选择 Build | Build MyControls(生成 | 生成 MyControls),以创建最终的控件库。

  要测试控件,我们需要一个 Windows 窗体项目。您可以在其他解决方案中执行此操作,但在开发控件所用的解决方案中执行会更容易。从菜单中选择 File | Add Project | New Project(文件 | 添加项目 | 新项目)。选择 Windows Application(Windows 应用程序)项目类型,将项目命名为 TestTrafficLight。单击 OK(确定),启动测试所需的 Windows 应用程序。

  必须先将 TrafficLight 控件放到工具箱中,才能将其拖放到测试应用程序的空白窗体 1 中。右键单击工具箱中的 Windows 窗体选项卡,然后选择 Customize Toolbox(自定义工具箱)。选择 .NET Framework Components(.NET Framework 组件)选项卡,然后单击 Browse(浏览)按钮。浏览到您的 MyControls 项目所在的位置,然后转到该项目的 /bin 目录。选择 MyControls.dll 组件并单击 OK(确定)。现在,该对话框应如图 2 所示。

\

  图 2:在 Customize Toolbox(自定义工具箱)对话框中,TrafficLight 控件被选中。

  您可以看到 TrafficLight 控件旁边有一个复选标记。单击 OK(确定)按钮,在工具箱的 Windows Forms(Windows 窗体)选项卡上,TrafficLight 控件将出现在控件列表的底部。图 3 显示了底部为 TrafficLight 控件的工具箱。

\

图 3:工具箱底部的 TrafficLight 控件

  现在,您可以将 TrafficLight 控件拖放到 TestTrafficLight 的空白窗体 1 中。默认情况下,它被命名为 TrafficLight1。您可以调整控件的大小,重新设置控件的&&属性,包括 Status &&属性,该&&属性有一个下拉菜单,菜单中包含该&&属性的三个可能的值。请注意,调整控件的大小或更改其&&属性时,控件将在设计器中自动刷新。

 

 

 要恢复&&属性的默认值,请将 Status &&属性更改为 statusRed。然后,右键单击 Properties(&&属性)窗口中的 Status(状态)&&属性,并选择 Reset(重置),如图 4 所示。该&&属性将更改回 statusGreen。如果将 BorderWidth &&属性设置为 1 之外的其他值,也可以使用同样的方法恢复其默认值。

\

  图 4:Properties(&&属性)窗口中 Status(状态)&&属性的 Reset(重置)选项。请注意窗口底部有关 Status(状态)&&属性的说明。

  如果需要,还可以为控件插入 StatusChanged &&事件。然后,可以使用该&&事件中的以下代码行查看更改后的状态:

  MsgBox("新状态为 " & NewStatus.ToString)

  要在操作中测试该控件,您需要启动 TestTrafficLight 项目。此时,它还不是该解决方案的启动项目,因此您需要解决它。在 Solution Explorer(解决方案资源管理器)中,右键单击 Solution(解决方案)名称 - Solution Explorer(解决方案资源管理器)中的第一行。选择 Properties(&&属性),然后将 Single Startup Project(单启动项目)设置从 MyControls 更改为 TestTrafficLight,然后单击 OK(确定)。

  按 F5 键启动该项目。将显示带有 TrafficLight 控件的窗体。测试控件:按下不同的灯,查看它们是否亮起。您还可以测试 BorderWidth &&属性,尝试在代码中设置灯的 Status &&属性。

   小结

  尽管 TrafficLight 是一个简单的控件(虽然曾有开发人员要把它用到真实的项目中),但它却显示了开发复杂控件所需要的所有原理,包括:

  • 在控件中添加&&属性。
  • 使用默认值和说明,使&&属性与 Visual Studio IDE 协调。
  • Paint &&事件中插入逻辑以绘制控件。
  • 在绘图逻辑中使用 GDI+。
  • 为控件设置位图,以便在工具箱中显示。

  创建复杂控件的关键在于熟悉 GDI+ 的绘图能力。如果理解了 TrafficLight 绘制边框和彩色圆的原理,那么您就有了一个好的起点。关键是,有了 Visual Basic .NET,即使象我这么懒惰的程序员也能创建高级的 Windows 窗体。

相关热词搜索:

上一篇:VB.NET窗体操作技巧两则
下一篇:用Vb.net实现自定义界面

分享到: 收藏
频道总排行
频道本月排行