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

BUILDING USER-DRAWN CONTROLS 413

The Changed Events

The UserControl object exposes many of the events you need to program the control, like the key and mouse events. In addition, you can raise custom events. The .NET Windows controls raise an event every time a property value is changed. If you examine the list of events exposed by the Label3D control, you’ll see the FontChanged and SizeChanged events. These events are provided by the UserControl object. As a control developer, you should expose similar events for your custom properties. This isn’t very difficult to do, but you must follow a few steps.

Declare an event handler for each of the Changed events:

Private mOnAlignmentChanged As EventHandler

Private mOnEffectChanged As EventHandler

Private mOnCaptionChanged As EventHandler

Then declare the actual events and their handlers:

Public Event AlignmentChanged(ByVal sender As Object, ByVal ev As EventArgs) Public Event EffectChanged(ByVal sender As Object, ByVal ev As EventArgs) Public Event CaptionChanged(ByVal sender As Object, ByVal ev As EventArgs)

And finally invoke the event handlers from within the appropriate OnEventName method:

Protected Overridable Sub OnAlignmentChanged(ByVal E As EventArgs)

Invalidate()

If Not (mOnAlignmentChanged Is Nothing) Then mOnAlignmentChanged.Invoke(Me, E)

End Sub

Protected Overridable Sub OnEffectChanged(ByVal E As EventArgs)

Invalidate()

If Not (mOnEffectChanged Is Nothing) Then mOnEffectChanged.Invoke(Me, E)

End Sub

Protected Overridable Sub OnCaptionChanged(ByVal E As EventArgs)

Invalidate()

If Not (mOnCaptionChanged Is Nothing) Then mOnCaptionChanged.Invoke(Me, E)

End Sub

As you can see, the OnpropertyChanged events call the Invalidate method to redraw the control when a property’s value is changed. As a result, you can now remove the call to the Invalidate method from the Property Set procedures. If you switch to the test form, you will see that the custom control exposes the AlignmentChanged, EffectChanged, and CaptionChanged events. The OnCaptionChanged method is executed automatically every time the Caption property changes value, and it fires the CaptionChanged event. Normally, this event isn’t programmed.

Raising Events

The UserControl object raises the usual events you’d expect to see in the editor’s window. When you select the custom control in the Objects drop-down list of the editor and expand the list of events for this control, you’ll see all the events fired by UserControl. They’re the usual events, which you already know how to program. However, what good are the Key events if the custom control doesn’t handle keystrokes? Most events will go unnoticed in most applications.

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

414 Chapter 9 BUILDING CUSTOM WINDOWS CONTROLS

The situation is very different with compound controls. A compound control usually allows the user to interact with one or more of its constituent controls. Let’s return to the ColorEdit custom control. The Click event is fired when the user clicks any area of the control outside the compound controls. When one of the scroll bars is clicked, no event is raised. Instead, the control adjusts the selected color. You can raise an event from within your control, if you want to. For example, you can raise an event to notify the application that the red scroll bar control has changed value, or that another color was selected in the ComboBox control with the named colors. Of course, these events are of questionable value, because the motivation for building a custom control is to hide as many of the low-level details as possible.

To demonstrate how to raise custom events, let’s say you want to raise an event when the user clicks the Label control where the selected color is displayed. Let’s call this event ColorClick. To raise a custom event, you must declare it in your control and call the RaiseEvent method to raise it. Note that the same event may be raised from many different places in the control’s code.

To declare the ColorClick event, enter the following statement in the control’s code. This line can appear anywhere, but placing it after the private variables that store the property values is customary.

Public Event ColorClick(ByVal sender As Object, ByVal e As EventArgs)

To raise the ColorClick event when the user clicks the Label control, insert the following statement in the Label control’s Click event handler:

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

ByVal e As System.EventArgs) Handles Label1.Click

RaiseEvent ColorClick(Me, e)

End Sub

Raising a custom event from within a control is as simple as raising an event from within a class. The RaiseEvent statement in the Label’s Click event handler maps the Click event of the Label control to the ColorClick event of the custom control. If you switch to the test form and examine the list of events of the Label3D control on the form, you’ll see that the new event was added.

The ColorClick event doesn’t convey much information. You could use it to display a context menu with a few common color names and let the user select one. In a real application, you could convey a lot of information to the developer using your control through custom events. As you can see, the arguments passed to the application by the ColorClick event are the same as the arguments passed to the Label control’s Click event.

When raising custom events, it’s likely that you’ll want to pass additional information to the developer. Let’s say you want to pass the Label control’s color to the application through the second argument of the ColorClick event. The EventArgs type doesn’t provide a Color property, so we must build a new type that inherits all the members of the EventArgs type and adds a property, the Color property. You can probably guess that we’ll create a custom class that inherits from the EventArgs class and adds the Color member.

Enter the statements of Listing 9.10 at the end of the file (after the existing End Class statement).

Listing 9.10: Declaring a Custom Event Type

Public Class ColorEvent

Inherits EventArgs

Public Shared color As Color

End Class

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

BUILDING USER-DRAWN CONTROLS 415

Then, declare the following event in the control’s code:

Public Event ColorClick(ByVal sender As Object, ByVal e As ColorEvent)

And finally raise the ColorClick event from within the Label’s Click event handler (Listing 9.11).

Listing 9.11: Raising a Custom Event

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

ByVal e As System.EventArgs) Handles Label1.Click Dim ev As ColorEvent

ev.color = Label1.BackColor RaiseEvent ColorClick(Me, ev)

End Sub

Using the Custom Control in Other Projects

By adding a test project to the Label3D custom control project, we were able to design and test the control in the same environment. A great help indeed, but the custom control can’t be used in other projects. If you start another instance of Visual Studio and attempt to add your custom control to the toolbox, you won’t see the Label3D entry in the Toolbox.

To add your custom component in another project, open the Customize Toolbox dialog box, then click the .NET Framework Components tab. Be sure to carry out the steps described here while the .NET Framework Components tab is visible. If the COM Components tab is visible instead, you can perform the same steps but you’ll end up with an error message (because the custom component is not a COM component).

Click the Browse button on the dialog box and locate the FlexLabel.dll file. It’s in the Bin folder under the FlexLabel project’s folder. The Label3D control will be added to the list of .NET Framework components, as shown in Figure 9.6. Check the box in front of the control’s name, then click the OK button to close the dialog box and add Label3D to the Toolbox. Now you can use this control in your new project.

Figure 9.6

Adding the Label3D control to another project’s Toolbox

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

416 Chapter 9 BUILDING CUSTOM WINDOWS CONTROLS

VB.NET at Work: The Alarm Control

This example demonstrates a custom control that contains all three types of members—properties, methods, and events—and raises events based on a timer, rather than some user action. It’s a simple alarm that can be set to go off at a certain time, and when it times out, it triggers a TimeOut event. Moreover, while the timer is ticking, the control updates a display, showing the time elapsed since the timer started (the property CountDown must be False) or the time left before the alarm goes off (the property CountDown must be True). Figure 9.7 shows the test form for the Alarm control. The first instance of the Alarm control counts down the time left before the alarm goes off, and the second counts the time since it was started.

Figure 9.7

The test form for the Alarm custom control

The Alarm Control’s Interface

The Alarm control has two custom properties, AlarmTime and CountDown. AlarmTime is the time when the alarm goes off, expressed in AM/PM format. CountDown is a True/False property that determines what’s displayed on the control. If CountDown is True, the alarm displays the time remaining. If you set the alarm to go off at 8:00 P.M. and you start the timer at 7:46 P.M., the control displays 0:14.00, then 0:13.59, and so on until the alarm goes off 14 minutes later. If CountDown is False, the control starts counting at 00:00.00 and counts until the AlarmTime is reached. The Alarm control takes into consideration the date as well and can be set to go off in more than 24 hours. However, it was designed to count a relatively small number of hours. If you set it to go off in a week, the number of hours left until the TimeOut event won’t be displayed nicely on the control (you have to make the control wider so that it can fit more digits), but the code will work for any setting of the AlarmTime property.

The Alarm control has two methods for starting and stopping the alarm: StartTimer starts the timer, and StopTimer stops it.

Finally, the Alarm control fires the TimeOut event, which notifies the application that the alarm has gone off (which happens when the time reaches AlarmTime). The application can use this event to trigger another action or simply to notify the user.

Testing the Alarm Control

The Alarm control’s test form is shown earlier, in Figure 9.7. It contains two instances of the control, and you set their CountDown property at design time. The AlarmTime property of both controls is set to the same value, which is 15 minutes ahead of the current time. Listing 9.12 shows the code behind the Start Timers button of the test form.

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

BUILDING USER-DRAWN CONTROLS 417

Listing 9.12: Setting Up the Two Alarm Controls on the Test Form

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

ByVal e As System.EventArgs) Handles Button1.Click CtrlAlarm1.CountDown = True

CtrlAlarm2.CountDown = False CtrlAlarm1.AlarmTime = Now.AddSeconds(10) CtrlAlarm2.AlarmTime = Now.AddSeconds(20) CtrlAlarm1.StartTimer() CtrlAlarm2.StartTimer()

TextBox1.Text = “Current date and time: “ & vbCrLf & Now & vbCrLf TextBox1.Text = TextBox1.Text + “Alarm1” & vbCrLf

TextBox1.Text = TextBox1.Text & “

set for “ & _

CtrlAlarm1.AlarmTime.ToShortDateString

TextBox1.Text = TextBox1.Text & “

“ & _

CtrlAlarm1.AlarmTime.ToLongTimeString & vbCrLf

TextBox1.Text = TextBox1.Text & “

and counting down” & vbCrLf

TextBox1.Text = TextBox1.Text & vbCrLf & “Alarm2” & vbCrLf

TextBox1.Text = TextBox1.Text & “

set for “ & _

CtrlAlarm2.AlarmTime.ToShortDateString

TextBox1.Text = TextBox1.Text & “

“ & _

CtrlAlarm2.AlarmTime.ToLongTimeString & vbCrLf

TextBox1.Text = TextBox1.Text & “

and counting up” & vbCrLf

End Sub

The last group of statements that manipulate the TextBox control display the time each control was started and the alarm time of the two controls. Then, you can watch the alarms count the time until they go off. To start the two alarms, the code calls the control’s StartTimer method. The information printed on the TextBox will help you verify that the controls work properly, especially if you edit the code.

Both controls will fire the TimeOut event when the alarm time is reached, and I’ve inserted two very simple handlers for these events (there’s a similar event handler for the second control):

Private Sub CtrlAlarm1_TimeOut() Handles CtrlAlarm1.TimeOut

Beep()

MsgBox(“Alarm1 is off!”)

End Sub

Designing the Alarm’s User Interface

Your first step is to design the control’s interface. Unlike the Timer control of Visual Basic, the Alarm control has a visible interface and uses two constituent controls: a Timer control (which is used to update the display every second as well as figure out whether the alarm must go off or not) and a Label control, where it displays the time.

To design the control’s interface, follow these steps:

1.Place a Label control on the UserControl form, and set its Font property to a font and size that looks nice for our purposes. We will not expose the Label’s Font as a property of the

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

418 Chapter 9 BUILDING CUSTOM WINDOWS CONTROLS

control, so that developers using this control can’t change it. If you want developers to be able to change the control’s font, you must insert additional code to adjust the dimensions of the Label so that all the digits will be visible.

2.Set the Label’s Dock property to Fill, so that it takes up all the space provided for the control.

3.Add a Timer control to the UserControl object—it will appear in the components tray at the bottom of the Designer’s window.

The control’s visible interface is quite trivial, thanks to the constituent controls. Let’s look at the members of the Alarm control (Figure 9.8).

Figure 9.8

The Alarm control at design time

Implementing the Control’s Members

Now we are ready to implement the control’s properties, methods, and event. Let’s start with the properties. First, declare the private variables that will hold the property values:

Private startTime As Date

Private Running As Boolean

Private m_CountDown As Boolean

Private m_AlarmTime As Date

As you have guessed, m_CountDown and m_AlarmTime are the two private variables that will hold the values of the CountDown and AlarmTime properties. The Running variable is True while the alarm is running and is declared outside any procedure so that all procedures can access its value. The startTime variable is set to the time the alarm starts counting and is used when the control is not counting down (you’ll see how it’s used shortly).

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

BUILDING USER-DRAWN CONTROLS 419

The procedures for implementing the control’s properties are quite simple; they’re detailed in Listing 9.13.

Listing 9.13: The Alarm Control’s Properties

Public Property CountDown() As Boolean

Get

CountDown = m_CountDown

End Get

Set(ByVal vNewValue As Boolean)

m_CountDown = vNewValue

End Set

End Property

Public Property AlarmTime() As Date

Get

AlarmTime = m_AlarmTime

End Get

Set(ByVal vNewValue As Date)

If IsDate(vNewValue) Then m_AlarmTime = vNewValue

End Set

End Property

The AlarmTime property may include a date part. If you specify a date only, the program assumes that the time is 00:00:00 (midnight). In the Properties window, VB will display a date value for the AlarmTime property. Type the desired time after the date in the format “hh:mm:ss”. Notice that because AlarmTime is of the Date type, a DateTimePicker control will be automatically displayed on the Properties window to help you set the property’s value visually.

Now we can add the code for the two methods. The StartTimer method (Listing 9.14) sets the Timer control’s Enabled property to True, so that it will start firing Tick events.

Listing 9.14: The StartTimer Method

Public Sub StartTimer()

If Not Running Then Timer1.Enabled = True Running = True startTime = Now

End If End Sub

This method doesn’t do anything if the alarm is already running. The StopTimer method is shown in Listing 9.15.

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

420 Chapter 9 BUILDING CUSTOM WINDOWS CONTROLS

Listing 9.15: The StopTimer Method

Public Sub StopTimer()

If Running Then

Timer1.Enabled = False

Running = False

End If

End Sub

As with the StartTimer method, the alarm stops only if it’s running. If that’s the case, the code disables the Timer control and sets the Running variable to False.

Next declare the TimeOut event with the following statement, which must appear outside any procedure.

Public Event TimeOut()

The TimeOut event doesn’t pass any information to the caller; it simply notifies the application that the current instance of the Alarm control has timed off. To raise the event, you must insert the appropriate code in the Timer’s Tick event handler. In the same event handler, which is invoked every second, we must also update the display. If the control is counting down, we create a TimeSpan object with the difference between the current time and the AlarmTime property. If the control is counting up, we create another TimeSpan object with the difference between the time the control was started and the current time (the time elapsed since the alarm was started). Listing 9.16 is the code of the Timer control’s Tick event hander.

Listing 9.16: The Timer’s Tick Event Handler

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

ByVal e As System.EventArgs) Handles Timer1.Tick

Dim TimeDiff As TimeSpan

TimeDiff = m_AlarmTime.Subtract(Now)

If TimeDiff.Seconds < 0 Then

StopNow = True

Timer1.Enabled = False

Label1.Text = “*****”

RaiseEvent TimeOut

Exit Sub

End If

If Not m_CountDown Then

the following statement calculates the difference

between current time and alarm time and adds 1 second TimeDiff = Now.TimeOfDay.Subtract(startTime.TimeOfDay). _

Add(New TimeSpan(0, 0, 1))

End If

Label1.Text = Format(TimeDiff.TotalHours, “00”) & “:” & _

Format(TimeDiff.Minutes, “00”) & “:” & _

Format(TimeDiff.Seconds, “00”)

End Sub

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com