Creating Events with Delegates and the Observer Design Pattern


Anyone doing Windows programming must have done some event handling in one or another way: capturing the double-click of some button, handling the click of a menu item, reacting to the mouse moving over a label, and so forth. But, what about creating your own event in your own control and letting others capture that event? In this section, you will learn how to use the Observer design pattern to raise and handle events for your .NET control and learn how to pass event argument data.

An event is a message sent by an object to notify other object(s) of the occurrence of an action. The action could be caused by user interaction, such as a mouse click, or it could be triggered by some other program logic. The object that raises the event is the event sender, and the object that receives the event notification is the event receiver. The event receiver has a method that executes automatically in response to the event.

The .NET Framework supports easy event-driven Windows programming. It is so easy that often the programmer does not need to know how events work in the underlying .NET technology. All you have to remember is this: If you are interested in receiving an event from a Windows control, you provide an event handler and register the event handler with the event source. This is called event wiring. In Visual Basic .NET (VB .NET), you need to write the following syntax, normally in the class constructor of your form:

 AddHandler eventSource.someEvent, AddressOf someMethod 

For instance, if you want to handle the Click event of a button named button1, and you want the private button1_Clicked method to execute when the Click event occurs, you write the following:

 AddHandler button1.Click, AddressOf button1_Clicked 

Then, you must also provide the implementation of button1_Clicked in your class as follows:

 Private Sub button1_Clicked(sender As Object sender, e As EventArgs)    ' code to be executed when the Click event occurs End Sub 

The method does not have to be private, but it must accept two arguments: an object of type Object and a System.EventArgs object.

Alternatively, declare a control with the WithEvents keyword, omit the AddHandler statement, and assign an event handler to handle an event by using the keyword Handles followed by the event source. In the previous example, this would be the following.

 Friend WithEvents button1 As TextBox Private Sub button1_Clicked(sender As Object sender, e As EventArgs) Handles  button1.Click    ' code to be executed when the Click event occurs End Sub 

So, consuming events is a piece of cake (and I am sure you have been doing it all the time). Now, let's look at creating your own custom event that other programmers can use. Before doing this, however, you should first learn about the Observer pattern in object-oriented programming.

Introducing the Observer Pattern

There are two key objects in the Observer pattern: the subject and the observer. The subject represents the data and may have one or many observers. These observers register their interest in the subject and listen for notification from the subject of a state change inside the subject. This pattern is also known as dependents or publish-subscribe. Figure 1-2 shows the UML class diagram of the Observer pattern.

click to expand
Figure 1-2: The Observer pattern class diagram

As an example, Listing 1-4 shows a form that has a TextBox control named myTextBox.

Listing 1-4: The Observer Design Pattern at Work

start example
 Imports System Public Class Form1 : Inherits System.Windows.Forms.Form   Friend WithEvents myTextBox As System.Windows.Forms.TextBox   Public Sub New()     myTextBox = New System.Windows.Forms.TextBox()     myTextBox.Location = New System.Drawing.Point(24, 24)     myTextBox.Size = New System.Drawing.Size(248, 20)     AddHandler myTextBox.TextChanged, AddressOf myTextBox_TextChanged     Me.Controls.Add(myTextBox)   End Sub   Private Sub myTextBox_TextChanged(ByVal sender As System.Object, _     ByVal e As System.EventArgs)     Me.Text = myTextBox.Text   End Sub   <STAThread()> Shared Sub Main()     System.Windows.Forms.Application.Run(New Form1())   End Sub End Class 
end example

Note

To compile the code in Listing 1-4, use the following command from the directory in which the listing-01.03.vb file resides: vbc /t:winexe /r:System.dll,System.Windows.Forms.dll, System.Drawing.dll listing-01.03.vb (all on one line).

Note that you declare the TextBox with the WithEvents keyword. Note also that an AddHandler statement is present in the class's constructor to wire the TextChanged event of myTextBox with the myTextBox_TextChanged event handler. As a result, every time the TextChanged event occurs in the TextBox control, the myTextBox_TextChanged event handler is invoked. This in turn updates the text in the form's title bar. Figure 1-3 shows this form.

click to expand
Figure 1-3: A form (observer) listening to a TextBox (subject)

This example demonstrates the Observer design pattern in action. The TextBox control behaves as the subject. It contains data, which is the text entered by the user. An observer, which is the form Form1, is interested in the changes in the TextBox control. It registers its interest using the AddHandler statement:

 AddHandler myTextBox.TextChanged, AddressOf myTextBox_TextChanged 

Every time the TextBox control changes, the TextBox control notifies the form. This notification materializes in the invocation of the myTextBox_TextChanged event handler.

According to "The Gang of Four" (Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides) in their book Design Pattern: Elements of Reusable Object-Oriented Software (Addison-Wesley, 1995), you can apply the Observer pattern in the following situations:

  • When an abstraction has two aspects, one dependent on the other. Encapsulating these aspects in separate objects lets you vary and reuse them independently.

  • When a change to one object requires changing others, and you do not know how many objects need to be changed.

  • When an object should be able to notify other objects without making assumptions about what these objects are. In other words, you do not want these objects to be tightly coupled.

The Observer pattern is useful because it introduces abstract coupling to the subject. The subject does not need to know the details of its observers. However, there is a potential disadvantage of repeated notification to the observers when there is a series of incremental changes to the subject. This can sometimes complicate application programming. This consequence also arises in the StyledTextArea control development when a series of characters need to be pasted from the Clipboard in the Paste method of the StyledTextArea class. This problem, and how you get around it, should become apparent when you study the project carefully.

In event communication in the .NET Framework, the event sender class does not know which object or method will receive the events it raises. In the case of Listing 1-4, the TextBox does not know who will be listening to its state change. What is needed is an intermediary (or pointer-like mechanism) between the source and the receiver. The .NET Framework defines a special type called delegate that provides the capability of a function pointer.

A delegate is a class that can hold a reference to a method. Unlike other classes, a delegate class has a signature, and it can hold references only to methods that match its signature. A delegate is thus equivalent to a type-safe function pointer or a callback.

The "Understanding the Model-View-Controller Paradigm" section describes how to use delegates to communicate an event from an object to another object. But first, the next section puts the theory into practice by providing a control that has two custom events.

Writing a Custom Event Step-by-Step

Suppose you want to write a custom event called MyEvent for your custom control named MyControl, which extends System.Windows.Forms.UserControl. You need to follow these steps:

  1. Declare a delegate with the public access modifier. For this example, call this delegate MyEventHandler. It has two arguments: an Object called sender and MyEventArgs called e. You will look at MyEventArgs in step 2. Note that you must declare the delegate outside your Control class:

     Public Delegate Sub MyEventHandler(ByVal sender As Object, _   ByVal e As MyEventArgs) Public Class MyControl: Inherits UserControl   ... End Class 
  2. MyEventArgs in step 1 is the object that contains the data, which can be passed from the event sender (MyControl) to the event receiver. MyEventArgs must extend the System.EventArgs class. Therefore, you now have the following code:

     Public Class MyEventArgs: Inherits EventArgs   ... End Class Public Delegate Sub MyEventHandler(ByVal sender As Object, _   ByVal e As MyEventArgs) Public Class MyControl: Inherits UserControl   ... End Class 

    You still need to write some implementation inside MyEventArgs class; however, you will learn about it when you write your own custom control.

  3. In your Control class, declare an event called MyEvent:

     Public Class MyEventArgs: Inherits EventArgs   ... End Class Public Delegate Sub MyEventHandler(ByVal sender As Object, _   ByVal e As MyEventArgs) Public Class MyControl: Inherits UserControl   Public Event MyEvent As MyEventHandler   ... End Class 
  4. In your Control class, declare a protected virtual method called On plus the name of the event. Because the event in this example is MyEvent, the method is OnMyEvent. Note that OnMyEvent has one argument of type MyEventArgs. Inside this method you raise the event. In VB .NET, you raise an event by using the RaiseEvent statement. To the event, you pass two arguments: the sender (the control MyControl) and the MyEventArgs object passed to the method:

     Public Class MyEventArgs: Inherits EventArgs   ... End Class Public Delegate Sub MyEventHandler(ByVal sender As Object, _   ByVal e As MyEventArgs) Public Class MyControl: Inherits UserControl   Public Event MyEvent As MyEventHandler   Protected Overridable Sub OnMyEvent(ByVal e As MyEventArgs)     RaiseEvent MyEvent (Me, e)   End Sub   ... End Class 

  5. Finally, call OnMyEvent from somewhere in the MyControl class. How you do this depends on what should cause the event to occur. This will become clear when you see the StyledTextArea control in the next section.

Afterward, users of your control can consume the MyEvent event in your control by wiring the event to an event handler in their form, as shown at the beginning of this section.




Real World. NET Applications
Real-World .NET Applications
ISBN: 1590590821
EAN: 2147483647
Year: 2005
Pages: 82

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net