Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Beginning Visual Basic 2005 Express Edition - From Novice To Professional (2006)

.pdf
Скачиваний:
387
Добавлен:
17.08.2013
Размер:
21.25 Mб
Скачать

320

C H A P T E R 1 2 E V E N T S

Figure 12-4. The online help for EventHandler

Ignoring the weird stuff in angle brackets (they are called attributes, and you really don’t need to worry about them at all while you’re reading this book), EventHandler is declared as a new type you’ve not seen before: Delegate. Other than the Delegate keyword, this looks pretty much like a standard subroutine definition (albeit an odd one with no code inside). In fact, take a look at the standard code for a button’s event handler in a form again:

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

ByVal e As System.EventArgs) Handles Button1.Click

End Sub

Look familiar? It should, because the event handler in your code has exactly the same format as this Delegate thing called EventHandler. Time to reveal all.

C H A P T E R 1 2 E V E N T S

321

If you were a C or C++ programmer, I’d tell you that a delegate is like an ultrasmart pointer to a function. You would wonder what’s so ultrasmart about it, but the “pointer to a function” bit you’d understand and smile knowingly. Because the majority of the world’s population is human, and you’re reading this book because you have a life and thus don’t want to learn C++, let me explain it another way.

A Delegate is a special kind of class that simply holds a list of functions to call. When you declare a delegate, you are defining a class that exists for no other purpose than to hold lists of functions. So far, so good. You declare a delegate, even though it’s a special kind of class, just as you would declare a method. For example, say I had a method in my code called SayHello that looked like this:

Private Sub SayHello(ByVal personsName As String)

End Sub

Then I could declare a delegate that could keep track of functions such as SayHello, like this:

Public Delegate Sub HelloFunctionDelegate(ByVal SomeValue As String)

The key thing here is that the signature of the delegate is identical to the signature of my method. Both the delegate and my method are subroutines, and both the delegate and my method take a single string parameter. You’ll recall from when you looked at method signatures a while back that the names of the parameters are not important—what is important is their type and number. As long as the delegate has the same number of parameters as the functions it’s supposed to keep track of, and the same types, we’re in good shape.

Now, because a delegate is really a class, simply declaring a delegate does nothing useful. We need to actually create a member variable to hold the delegate (class). It’s just like saying Dim myEmployee as New Employee in code. myEmployee becomes an object variable that refers to an Employee object. With delegates, though, we use the Event keyword to do the same thing:

Public Event HelloFunc As HelloFunctionDelegate

So, HelloFunc is an event that holds a Delegate of type HelloFunctionDelegate. Hold that brain together just a little while longer; all is about to become clear.

The obvious question is, “Why would we want to store a delegate, if all a delegate can do is store lists of functions to call?” Glad you asked. The reason we’d do this is that we can actually call a delegate. Let’s really let that statement sink in with a “Try It Out.”

322

C H A P T E R 1 2 E V E N T S

Try It Out: Delegates and Events

Create a new console project (we’ll keep it simple and not let graphical controls and so on get in the way). After it’s created and the code is in the editor, go ahead and add a new Employee class to it:

Module Module1

Sub Main()

End Sub

End Module

Public Class Employee

Private _employeeName As String

Public Sub HireEmployee(ByVal name As String)

End Sub

Public Sub FireEmployee()

End Sub

End Class

Now, what we’d like to happen here is for events to fire when an employee is hired and when an employee is fired. You could declare an HrActionDelegate for this. Go ahead:

Public Class Employee

Public Delegate Sub HrActionDelegate(ByVal name As String)

Private _employeeName As String

Public Sub HireEmployee(ByVal name As String)

End Sub

Public Sub FireEmployee()

End Sub

End Class

C H A P T E R 1 2 E V E N T S

323

Now that the delegate is declared, you need to let the Employee class know that it can use it as an event. Actually, you need two events: one for hiring someone, and one for firing someone. Go ahead and add the two lines:

Public Class Employee

Public Delegate Sub HrActionDelegate(ByVal name As String)

Public Event Hire As HrActionDelegate

Public Event Fire As HrActionDelegate

Private _employeeName As String

Public Sub HireEmployee(ByVal name As String)

End Sub

Public Sub FireEmployee()

End Sub

End Class

Here you’re saying that the Employee class can raise two events: Hire and Fire. In addition, these events are of type HrActionDelegate. What that means, of course, is that any event handlers someone declares to respond to those events (it’s just as if you had written a button here and said it could be “clicked”) must have the same signature as the delegate. So, that’s a subroutine that expects a single string as a parameter.

Now, you have the delegate declared and you have two events in your Employee class using it. The next step is to actually raise an event when someone gets hired or fired. Let’s code up the HireEmployee() method first:

Public Class Employee

Public Delegate Sub HrActionDelegate(ByVal name As String)

Public Event Hire As HrActionDelegate

Public Event Fire As HrActionDelegate

Private _employeeName As String

Public Sub HireEmployee(ByVal name As String)

_employeeName = name RaiseEvent Hire(_employeeName)

End Sub

324

C H A P T E R 1 2 E V E N T S

Public Sub FireEmployee()

End Sub

End Class

The first bit is straightforward enough. When the HireEmployee() method is called, you store the employee name in a member variable in the class.

The next line uses the RaiseEvent keyword to actually raise the event. What happens is .NET then calls any handlers that have been added to the event. If there are no event handlers registered with our object, then that line does nothing.

Let’s code up the FireEmployee() method now. Then you can get on with some code to see all this stuff in action:

Public Class Employee

Public Delegate Sub HrActionDelegate(ByVal name As String)

Public Event Hire As HrActionDelegate

Public Event Fire As HrActionDelegate

Private _employeeName As String

Public Sub HireEmployee(ByVal name As String) _employeeName = name

RaiseEvent Hire(_employeeName) End Sub

Public Sub FireEmployee()

RaiseEvent Fire(_employeeName)

End Sub

End Class

This is pretty much the same thing. When FireEmployee() is called, RaiseEvent is used to “raise” the Fire event, passing across the name of the person that got fired.

Let’s drop back into the main Module1 code now and put all this to use. First, you need two event handlers, just as if you were creating handlers for Button clicks:

Module Module1

Sub Main()

C H A P T E R 1 2 E V E N T S

325

End Sub

Private Sub NewEmployee(ByVal name As String)

Console.WriteLine("We just hired {0}", name)

End Sub

Private Sub RetireEmployee(ByVal name As String)

Console.WriteLine("{0} just took early retirement", name)

End Sub

End Module

Notice that both the handlers have exactly the same syntax as your delegate; they are subroutines and take a single string as a parameter.

All that remains then is to create a couple of Employee objects and connect the handlers:

Module Module1

Sub Main()

Dim pete As New Employee()

Dim fred As New Employee()

AddHandler pete.Hire, AddressOf NewEmployee

AddHandler pete.Fire, AddressOf RetireEmployee

AddHandler fred.Hire, AddressOf NewEmployee

AddHandler fred.Fire, AddressOf RetireEmployee

End Sub

Private Sub NewEmployee(ByVal name As String)

Console.WriteLine("We just hired {0}", name)

End Sub

Private Sub RetireEmployee(ByVal name As String)

Console.WriteLine("{0} just took early retirement", name)

End Sub

End Module

First, two Employee objects, pete and fred, are created. Next you connect the handlers. To do this you simply use AddHandler, which takes two parameters, as you can see. The first is the object and event you are hooking up (IntelliSense helps you here, showing you a list of events on the object that you can hook into).

326

C H A P T E R 1 2 E V E N T S

The second is the AddressOf routine you want to use as your event handler. Again, the subroutine you specify here must have exactly the same signature as the delegate. It’s worth also noting that you are not limited to adding just one event handler to an event. AddHandler can be used to add as many subroutines to handle the event as you like. When the event is raised, all routines you pass through AddHandler get called. You are effectively adding your event handlers to a list of functions that Employee needs to call when employees get hired or fired.

So the final step is to hire and fire some people:

Module Module1

Sub Main()

Dim pete As New Employee()

Dim fred As New Employee()

AddHandler pete.Hire, AddressOf NewEmployee

AddHandler pete.Fire, AddressOf RetireEmployee

AddHandler fred.Hire, AddressOf NewEmployee

AddHandler fred.Fire, AddressOf RetireEmployee

Console.WriteLine("Let the hiring and firing commence...") pete.HireEmployee("Pete")

fred.HireEmployee("Fred")

pete.FireEmployee()

fred.FireEmployee()

Console.ReadLine()

End Sub

Private Sub NewEmployee(ByVal name As String)

Console.WriteLine("We just hired {0}", name)

End Sub

Private Sub RetireEmployee(ByVal name As String)

Console.WriteLine("{0} just took early retirement", name)

End Sub

End Module

Notice that all you are doing here is calling methods on the Employee objects. Those methods, because of the code you wrote earlier, fire events. The events are wired up to call the NewEmployee() and RetireEmployee() methods in your program. So, if you run the program now, you’ll see Figure 12-5.

C H A P T E R 1 2 E V E N T S

327

Figure 12-5. When the program is run, the events fire and your event handlers catch them and output the results.

Just to demonstrate the point I made about adding multiple handlers to an event, go ahead and double up pete’s handlers, like this:

Sub Main()

Dim pete As New Employee()

Dim fred As New Employee()

AddHandler pete.Hire, AddressOf NewEmployee

AddHandler pete.Fire, AddressOf RetireEmployee

AddHandler pete.Hire, AddressOf NewEmployee

AddHandler pete.Fire, AddressOf RetireEmployee

AddHandler fred.Hire, AddressOf NewEmployee

AddHandler fred.Fire, AddressOf RetireEmployee

Console.WriteLine("Let the hiring and firing commence...") pete.HireEmployee("Pete")

fred.HireEmployee("Fred")

pete.FireEmployee()

fred.FireEmployee()

Console.ReadLine()

End Sub

328 C H A P T E R 1 2 E V E N T S

Because we’ve added our handlers twice here, if you run the program again you’ll see the output in Figure 12-6.

Figure 12-6. Events can be set up to call more than one delegate method!

Summary

The first time I came across delegates and events, my mind melted. For that reason, I’ve tried really hard here to lighten the subject a little. Even so, if you are confused, it may help you to reread the chapter and also just mess around with the code.

In short, a delegate is a list of functions to call, and an event is a container for delegates. At runtime you simply create instances of delegates and add them into events. You can then call the event just as if it were a standard method, and it in turn will call every single delegate method that has been added to it.

This will open up a lot of possibilities for you when you start to design really complex systems of your own later.

C H A P T E R 1 3

■ ■ ■

Lists and Generics

You’ve looked at lists of data quite a lot already. Back in the early chapters, for example, I touched on arrays, the absolute simplest form of list out there, and very handy they are too from time to time. After you started to explore the world of Windows user interface programming, you came across controls that are meant to do nothing more than work with lists and present them to users (list boxes, combo boxes, and so on). What I didn’t mention, though, are the subtle differences and potential problems.

Take a look at how to declare an array, for example:

Dim names(100) As String

Obviously, in your own code you’d go ahead and replace string with whatever data type you want to store in a list, even your own custom classes. There’s a problem with arrays, though. How would you add items into an array? What about removing items from the array? How do you sort it?

The only answer to all these questions is code, and lots of it. Invariably the code would look pretty ugly as well, because you would have to undertake an unpleasant ReDim of the array in question. For example, let’s say that we had an array of strings and wanted to add a new one into it. The code might end up looking like this:

Sub AddStringToArray(ByRef names() As String, ByVal newName As String) Dim size As Integer = names.Length

ReDim Preserve names(size + 1) names(size) = newName

End Sub

It’s not a particularly complex snippet of code, but it sure is ugly for a task as seemingly trivial as adding an item onto the end of a list. In addition, if you wanted to do something such as sort the array, the code would grow even more, and that’s still not including the nasty, ugly line of code that needs to call the function in the first place:

AddStringToArray( myArray, "The new string");

All of this is really quite inefficient as well. Do that little lot enough times in a loop and

you’ll find the performance of your code go down through a hole. So, there’s no getting

329