Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
(ebook) Visual Studio .NET Mastering Visual Basic.pdf
Скачиваний:
120
Добавлен:
17.08.2013
Размер:
15.38 Mб
Скачать

BUILDING USER-DRAWN CONTROLS 405

Building User-Drawn Controls

This is the most complicated, but also the most flexible, type of control. A user-drawn control consists of a UserControl object with no constituent controls. You are responsible for updating the control’s visible area with the appropriate code, which must appear in the control’s OnPaint method. This method is called right before the OnPaint event is fired, and if you override it, you can take control of the repaint process.

To demonstrate the design of user-drawn controls, we’ll develop the Label3D control, which is an enhanced Label control and is shown in Figure 9.5. It provides all the members of the Label control plus a few highly desirable new features, such as the ability to align the text in all possible ways on the control, as well as in three-dimensional type. The new custom control is called Label3D, and its project on the CD is the FlexLabel project. It contains the Label3D project (which is a Windows Control Library project) and the usual test project (which is a Windows Application project).

Figure 9.5

The Label3D control is an enhanced Label control.

At this point, you’re probably thinking about the code that aligns the text and renders it as carved or raised. A good idea is to start with a Windows project, which displays a string on a form and aligns it in all possible ways. A control is an application packaged in a way that allows it to be displayed on a form instead of on the Desktop. As far as the functionality is concerned, in most cases it can be implemented on a regular form.

Designing a Windows form with the same functionality is fairly straightforward. You haven’t seen the drawing methods yet, but this control doesn’t involve any advanced drawing techniques. All we need is a method to render strings on the control. To achieve the 3D effect, you must display the same string twice, first in white and then in black on top of the white. The two strings must be displaced slightly, and the direction of the displacement determines the effect (whether the text will appear as raised or carved). The amount of displacement determines the depth of the effect. Use a displacement of one pixel for a light effect and a displacement of two pixels for a heavy one.

VB.NET at Work: The Label3D Control

The first step in designing a user-drawn custom control is to design the control’s interface: what it will look like when placed on a form (its visible interface) and how developers can access this functionality through its members (the programmatic interface). Sure, you’ve heard the same advice over and over, and many of you still start coding an application without spending much time designing it. In the real world, especially if you are not a member of programming team, people design as they code (or the other way around).

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

406 Chapter 9 BUILDING CUSTOM WINDOWS CONTROLS

The situation is quite different with Windows controls. Your custom control must provide properties, which will be displayed automatically in the Properties window. The developer should be able to adjust every aspect of the control’s appearance by manipulating the settings of these properties. In addition, developers expect to see the standard properties shared by most controls (such as the background color, the text’s font, and so on). You must carefully design the methods so that they expose all the functionality of the control that should be accessed from within the application’s code, and the methods shouldn’t overlap. Finally, you must provide the events necessary for the control to react to external events. Don’t start coding a custom control unless you have formulated a very clear idea of what the control will do and how it will be used by developers at design time.

The Label3D Control’s Specifications

The Label3D control displays a caption like the standard Label control, so it must provide the Caption and Font properties, which let the developer determine the text and its appearance. The UserControl object exposes these two properties, so we need not implement them in our code. In addition, the Label3D can align its caption both vertically and horizontally. This functionality will be exposed by the Alignment property, whose settings are shown in Table 9.1.

Table 9.1: The Settings of the Alignment Property (The Align Enumeration)

Value

TopLeft

TopMiddle

TopRight

CenterLeft

CenterMiddle

CenterRight

BottomLeft

BottomMiddle

BottomRight

The (self-explanatory) values in Table 9.1 are the names that will appear in the drop-down list of the Alignment property in the Properties window. As you have noticed, properties with a limited number of settings display a drop-down list there. This list contains descriptive names (instead of numeric values), and the developer can select only a valid setting. The Alignment property’s settings will be the members of a custom enumeration.

Similarly, the text effect is manipulated through the Effect property, whose settings are shown in Table 9.2. There are basically two types of effects, raised and carved text, and two variations on each effect (normal and heavy).

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

BUILDING USER-DRAWN CONTROLS 407

Table 9.2: The Settings of the Effect Property (The Effect3D Enumeration)

Value

None

Carved

CarvedHeavy

Raised

RaisedHeavy

Like the Alignment property, the Effect property has a small number of valid settings, which will be identified in the Properties window with descriptive names. These names are the members of another custom enumeration.

In addition to the custom properties, the Label3D control should also expose the standard properties of a Label control, such as Tag, BackColor, and so on. Developers expect to see standard properties in the Properties window, and you should implement them. The Label3D control doesn’t have any custom methods, but it should provide the standard methods of the Label control, such as the Move method. Similarly, although the control doesn’t raise any special events, it must support the standard events of the Label control, such as the mouse and keyboard events.

Most of the custom control’s functionality exists already, and there should be a simple technique to borrow this functionality from other controls, rather than implementing it from scratch. This is indeed the case: The UserControl object, from which all user-drawn controls inherit, exposes a large number of members.

Designing the Custom Control

Start a new project of the Windows Control Library type, name it FlexLabel, and then rename the UserControl1 object to Label3D. Open the UserControl object’s code window and change the name of the class from UserControl1 to Label3D. The first two lines in the code window should be:

Public Class Label3D

Inherits System.Windows.Forms.UserControl

All user-drawn controls inherit from the UserControl object, and you will soon see the members exposed by the UserControl object itself.

Note Every time you place a Windows control on a form, it’s named according to the UserControl object’s name and a sequence digit. The first instance of the custom control you place on a form will be named Label3D1, the next one will be named Label3D2, and so on. Obviously, it’s important to choose a meaningful name for your UserControl object.

As you will soon see, the UserControl is the “form” on which the custom control will be designed. It looks, feels, and behaves like a regular VB form, but it’s called a UserControl. UserControl objects have additional unique properties that don’t apply to a regular form, but in order to start designing new controls, think of them as regular forms.

You’ve set the scene for a new user-drawn Windows control. Start by declaring the two enumerations shown in Tables 9.1 and 9.2. Listing 9.6 shows the Enum statements for the two enumerations.

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

408 Chapter 9 BUILDING CUSTOM WINDOWS CONTROLS

Listing 9.6: The Align and Effect3D Enumerations

Public Enum Align

TopLeft

TopMiddle

TopRight

CenterLeft

CenterMiddle

CenterRight

BottomLeft

BottomMiddle

BottomRight

End Enum

Public Enum Effect3D

None

Raised

RaisedHeavy

Carved

CarvedHeavy

End Enum

The next step is to implement the Alignment and Effect properties. Each property’s type is an enumeration, and Listing 9.7 shows the implementation of the two properties.

Listing 9.7: The Alignment and Effect Properties

Private Shared mAlignment As Align

Private Shared mEffect As Effect3D

Public Property Alignment() As Align

Get

Alignment = mAlignment

End Get

Set(ByVal Value As Align)

mAlignment = Value

Invalidate()

End Set

End Property

Public Property Effect() As Effect3D

Get

Effect = mEffect

End Get

Set(ByVal Value As Effect3D)

mEffect = Value

Invalidate()

End Set

End Property

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

BUILDING USER-DRAWN CONTROLS 409

The current settings of the two properties are stored in the private variables mAlignment and mEffect. When either property is set, the Property procedure’s code calls the Invalidate method of the UserControl object to redraw the string on the control’s surface. The call to the Invalidate method is required for the control to operate properly in design mode. You can provide a method to redraw the control at runtime (although developers shouldn’t have to call a method to refresh the control every time they set a property), but this isn’t possible at design time. When a property is changed in the Properties window, the control should be able to update itself and reflect the new property setting. The Invalidate method causes the control to be redrawn, to reflect the new setting of the property. Shortly, you’ll see an even better way to automatically redraw the control every time a property is changed.

Finally, you must add one more property, the Caption property, which is the string to be rendered on the control. Declare a private variable to store the control’s caption (the mCaption variable) and enter the code from Listing 9.7 to implement the Caption property.

Listing 9.7: The Caption Property Procedure

Private mCaption As String

Property Caption() As String

Get

Caption = mCaption

End Get

Set(ByVal Value As String)

mCaption = Value

Invalidate()

End Set

End Property

The core of the control’s code is in the OnPaint method, which is called automatically before the control repaints itself (that is, prior to the Paint event). The same event’s code is also executed when the Invalidate method is called, and this is why we call this method every time one of the control’s properties changes value. The OnPaint method enables you to take control of the repaint process and supply your own code for repainting the control’s surface. The single characteristic of all userdrawn controls is that they override the default OnPaint method. This is where you must insert the code to draw the control’s surface—i.e., draw the specified string taking into consideration the Alignment and Effect properties. The OnPaint method’s code is shown in Listing 9.8.

Listing 9.8: Overriding the UserControl’s OnPaint Method

Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs) Dim lblFont As Font = Me.Font

Dim lblBrush As New SolidBrush(Color.Red) Dim X, Y As Integer

Dim textSize As SizeF

textSize = e.Graphics.MeasureString(mCaption, lblFont) Select Case Me.mAlignment

Case Align.BottomLeft

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

410 Chapter 9 BUILDING CUSTOM WINDOWS CONTROLS

X = 2

Y = Me.Height - textSize.Height Case Align.BottomMiddle

X = CInt((Me.Width - textSize.Width) / 2) Y = Me.Height - textSize.Height

Case Align.BottomRight

X = Me.Width - textSize.Width - 2 Y = Me.Height - textSize.Height

Case Align.CenterLeft X = 2

Y = (Me.Height - textSize.Height) / 2 Case Align.CenterMiddle

X = (Me.Width - textSize.Width) / 2 Y = (Me.Height - textSize.Height) / 2

Case Align.CenterRight

X = Me.Width - textSize.Width - 2

Y = (Me.Height - textSize.Height) / 2 Case Align.TopLeft

X = 2

Y = 2

Case Align.TopMiddle

X = (Me.Width - textSize.Width) / 2 Y = 2

Case Align.TopRight

X = Me.Width - textSize.Width - 2 Y = 2

End Select

Dim dispX, dispY As Integer Select Case mEffect

Case Effect3D.None : dispX = 0 : dispY = 0 Case Effect3D.Raised : dispX = 1 : dispY = 1

Case Effect3D.RaisedHeavy : dispX = 2 : dispY = 2 Case Effect3D.Carved : dispX = -1 : dispY = -1 Case Effect3D.CarvedHeavy : dispX = -2 : dispY = -2

End Select e.Graphics.Clear(Me.BackColor) lblBrush.Color = Color.White

e.Graphics.DrawString(mCaption, lblFont, lblBrush, X, Y) lblBrush.Color = Me.ForeColor

If Me.DesignMode Then

e.Graphics.DrawString(“DesignTime”, New Font(“Verdana”, 24, _ FontStyle.Bold), New SolidBrush(Color.FromARGB(200, 230, 200, 255)), 0, 0)

Else

e.Graphics.DrawString(“RunTime”, New Font(“Verdana”, 24, FontStyle.Bold), _ New SolidBrush(Color.FromARGB(200, 230, 200, 255)), 0, 0)

End If

e.Graphics.DrawString(mCaption, lblFont, lblBrush, X + dispX, Y + dispY) End Sub

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

BUILDING USER-DRAWN CONTROLS 411

This subroutine calls for a few explanations. The OnPaint event passes a PaintEventArgs argument (the ubiquitous e argument). This argument exposes the Graphics property, which represents the control’s surface. The Graphics object exposes all the methods you can call to create graphics on the control’s surface. The Graphics object is discussed in detail in Chapter 14, but for the purposes of this chapter all you need to know is that the MeasureString method returns the dimensions

of a string when rendered in a specific font, and the DrawString method draws the string in the specified font. The first Select Case statement calculates the coordinates of the string’s origin on the control’s surface. These coordinates are calculated for each different alignment. Then another Select Case statement sets the displacement between the two strings, so that when superimposed they produce a three-dimensional look. Finally, the code draws the value of the Caption property on the Graphics object. It draws the string in white color first, then in black. The second string is drawn dispX pixels to the left and dispY pixels below the first one to give the 3D effect.

Notice the two statements that print the strings “DesignTime” and “RunTime” in a light color on the control’s background, depending on the current status of the control. They indicate whether the control is currently in design (if UserMode is True) or run time (if UserMode is False).

Testing Your New Control

To test your new control, you must first add it to the Toolbox, so that you can place it on a form. You can add a form to the current project and test the control, but you shouldn’t add more components to the control project. It’s best to add a new project to the current solution.

Add the TestProject to the current solution, rename its Form to TestForm, and open it in design mode. Place a Label3D control on the test form and the other controls shown in Figure 9.5. If the Label3D icon doesn’t appear in the Toolbox, you must build the control’s project.

Now double-click the Label3D control on the form to see its events. Your new control has its own events, and you can program them just as you would program the events of any other control. Enter the following code in the control’s Click event:

Private Sub Label3D1_Click(ByVal sender As Object, _

ByVal e As System.EventArgs) Handles Label3D1.Click MsgBox(“My properties are “ & vbCrLf & _

“Caption = “ & Label3D1.Caption & vbCrLf & _ “Alignment = “ & Label3D1.Alignment & vbCrLf & _ “Effect = “ & Label3D1.Effect)

End Sub

To run the control, press F5 and then click the control. You will see the control’s properties displayed in a message box.

The other controls on the test form (see Figure 9.5) allow you to set the appearance of the custom control at runtime. The two ComboBox controls are populated with the members of the appropriate enumeration when the form is loaded. In their SelectedIndexChanged event handler, you must set the corresponding property to the selected value, as shown in the following listing:

Private Sub AlignmentBox_SelectedIndexChanged(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _

Handles AlignmentBox.SelectedIndexChanged Label3D1.Alignment = AlignmentBox.SelectedItem

End Sub

Private Sub EffectsBox_SelectedIndexChanged(ByVal sender As System.Object, _

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

412 Chapter 9 BUILDING CUSTOM WINDOWS CONTROLS

ByVal e As System.EventArgs) _

Handles EffectsBox.SelectedIndexChanged

Label3D1.Effect = EffectsBox.SelectedItem

End Sub

The TextBox control at the bottom of the form stores the Caption property. Every time you change this string, the control is updated, because the Set procedure of the Caption property calls the Invalidate method.

Initializing a Custom Control

To initialize the control’s properties, insert the appropriate code in the New() subroutine. This subroutine is in the section marked with the following line:

#Region “ Windows Form Designer generated code “

and it contains code generated by the designer. Expand this section by clicking the plus sign in front of its name and locate the New() subroutine. Listing 9.9 shows the New() subroutine of the custom control.

Listing 9.9: The New() Subroutine of the Label3D Control

Public Sub New()

MyBase.New()

‘This call is required by the Windows Form Designer. InitializeComponent()

‘Add any initialization after the InitializeComponent() call mCaption = “Label3D”

mAlignment = Align.CenterMiddle mEffect = Effect3D.Raised

SetStyle(ControlStyles.ResizeRedraw, “True”) End Sub

I’ve only added the four last statements in this listing; the first couple of statements and the comments were inserted by the Designer. After assigning initial values to the private variables that store the control’s properties, there’s a call to the SetStyle method, which accepts several arguments. The ResizeRedraw argument determines whether the control will be redrawn when it’s resized. Normally, the Paint event isn’t fired when the control is made smaller, and when the control is enlarged, only the new area of the control is repainted. The call to the SetStyle method forces the control to be repainted every time the user resizes it on the form.

You need not initialize all the properties of the control, just the ones that should have a value the first time the control is placed on a form. When you place a TextBox control on the form, for example, its Text property is set to the control’s name and its Font property is set to Microsoft Sans Serif. The Label3D control’s default caption is “Label3D”. Many of the control’s properties are handled by the UserControl object itself, and Font is one of them. To set the initial font, locate the Font property of the UserControl and set it accordingly. The BackgroundColor and BackgroundImage properties are also handled by the UserControl object. You can specify a default background image if you want, but this property will be exposed in the Properties window, and the developer can set it at design time.

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com