Data Access and Operations |
The event concept is central in event-driven programming. Programs with graphical user interfaces are event-driven. With the purpose of discussing events we will see a simple example of a graphical user interface at the end of this chapter.
|
23.1. Events
Contents Up Previous Next Slide Annotated slide Aggregated slides Subject index Program index Exercise index
|
In command-driven programming, the computer prompts the user for input. When the user is prompted the program stops and waits a given program location. When a command is issued by the user, the program is continued at the mentioned location. The program will analyze the command and carry out an appropriate action.
In event-driven programming the program reacts on what happens on the elements of the user interface, or more generally, what happens on some selected state of the program. When a given event is triggered the actions that are associated with this particular event are carried out.
Don't call us - we call you |
The "Don't call us - we call you" idea is due to the observation that the operations called by the event mechanism is not activated explicitly by our own program. The operations triggered by events are called by the system, such as the graphical user interface framework. This is sometimes referred to as inversion of control.
Below, we compare operations (such as methods) and events.
|
In the following sections we will describe the event mechanism in C#. Fortunately, we have already made the preparations for this in Chapter 22, because an event can be modelled as a variable of a delegate type.
23.2. Events in C#
Contents Up Previous Next Slide Annotated slide Aggregated slides Subject index Program index Exercise index
In C# an event is a variable of a delegate type. Like classes, delegates are reference types. This implies that an event holds a reference to an instance of a delegate. The delegate is allocated on the heap.
An event is a variable of a delegate type A few restrictions apply compared with other variables of delegate types Events can be used for handling interactive events in graphical user interfaces |
The following restrictions apply to events:
|
In the System namespace there exists a (generic) delegate called EventHandler, which is recommended for event handling in the .NET framework. The EventHandler delegate takes two arguments: The object which generated the event and an object which describes the event as such. The latter is assumed to be a subclass of pre-existing class EventArgs. For more information, consult the documentation of the generic EventHandler delegate.
23.3. Examples of events
Contents Up Previous Next Slide Annotated slide Aggregated slides Subject index Program index Exercise index
In this section we will see examples of programs that make use of events.
First, in Program 23.1 we elaborate the Die class, which we have met several times before, see Section 10.1 , Section 12.5 , and Section 16.3.
In Program 23.1 the Toss operation of the Die class triggers a particular event in case it tosses two sixes in a row, see line 30-31.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | using System; using System.Collections.Generic; public class Die { public delegate void Notifier(string message); // A delegate type private int numberOfEyes; private Random randomNumberSupplier; private int maxNumberOfEyes; private List<int> history; public event Notifier twoSixesInARow; public int NumberOfEyes{ get {return numberOfEyes;} } public Die (): this(6){} public Die (int maxNumberOfEyes){ randomNumberSupplier = new Random(unchecked((int)DateTime.Now.Ticks)); this.maxNumberOfEyes = maxNumberOfEyes; numberOfEyes = randomNumberSupplier.Next(1, maxNumberOfEyes + 1); history = new List<int>(); history.Add(numberOfEyes); } public void Toss (){ numberOfEyes = randomNumberSupplier.Next(1,maxNumberOfEyes + 1); history.Add(numberOfEyes); if (DoWeHaveTwoSixesInARow(history)) twoSixesInARow("Two sixes in a row"); } private bool DoWeHaveTwoSixesInARow(List<int> history){ int histLength = history.Count; return histLength >= 2 && history[histLength-1] == 6 && history[histLength-2] == 6; } public override String ToString(){ return String.Format("Die[{0}]: {1}", maxNumberOfEyes, NumberOfEyes); } } | |||
|
In Program 23.1 Notifier is a delegate. Thus, Notifier is a type.
dieNotifier is an event - analogous to an instance variable - of type Notifier. Alternatively, we could have used the predefined EventHandler delegate (see Section 23.2) instead of Notifier. The event dieNotifier is public, and therefore we can add operations to this event from clients of Die objects. In line 9-11 of the class diceApp, shown in Program 23.2, we add an anonymous delegate to d1.dieNotifier, which reports the two sixes on the console.
Notice the keyword "event", used in declaration of variables of delegate types for event purposes. You can think of "event" as a modifier, which gives a slightly special semantics to a Notifier delegate. Technically in C#, however, event is not a modifier.
The predicate (boolean method) DoWeHaveTwoSixesInARow in line 34-39 of Program 23.1 in class Die identifies if the die has shown two sixes in a row. This is based on the extra history instance variable.
Finally, the Toss operation may trigger the dieNotifier in line 30-31 of Program 23.1. The event is triggered in case the history tells that we have seen two sixes in a row.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | using System; class diceApp { public static void Main(){ Die d1 = new Die(); d1.twoSixesInARow += delegate (string mes){ Console.WriteLine(mes); }; for(int i = 1; i < 100; i++){ d1.Toss(); Console.WriteLine("{0}: {1}", i, d1.NumberOfEyes); } } } | |||
|
In Program 23.3 we show the (abbreviated) output of Program 23.2. The "two sixes in a row" reporting turns out to be reported in between the two sixes. This is because the event is triggered by Toss, before Toss returns the last 6 value.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 1: 6 2: 4 ... 32: 3 33: 6 Two sixes in a row 34: 6 Two sixes in a row 35: 6 ... 66: 2 67: 6 Two sixes in a row 68: 6 69: 2 70: 4 ... 97: 6 Two sixes in a row 98: 6 99: 3 | |||
|
We will now turn to a another example in an entirely different domain, see Program 23.4. This program constructs a graphical user interface with two buttons and a textbox, see Figure 23.1. If the user pushes the Click Me button, this is reported in the textbox. If the user pushes the Erase button, the text in the textbox is deleted.
Figure 23.1 A graphical user interface with two buttons and a textbox. |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | using System; using System.Windows.Forms; using System.Drawing; // In System: // public delegate void EventHandler (Object sender, EventArgs e) public class Window: Form{ Button b1, b2; TextBox tb; // Constructor public Window (){ this.Size=new Size(150,200); b1 = new Button(); b1.Text="Click Me"; b1.Size=new Size(100,25); b1.Location = new Point(25,25); b1.BackColor = Color.Yellow; b1.Click += ClickHandler; // Alternatively: // b1.Click+=new EventHandler(ClickHandler); b2 = new Button(); b2.Text="Erase"; b2.Size=new Size(100,25); b2.Location = new Point(25,55); b2.BackColor=Color.Green; b2.Click += EraseHandler; // Alternatively: // b2.Click+=new EventHandler(EraseHandler); tb = new TextBox(); tb.Location = new Point(25,100); tb.Size=new Size(100,25); tb.BackColor=Color.White; tb.ReadOnly=true; tb.RightToLeft=RightToLeft.Yes; this.Controls.Add(b1); this.Controls.Add(b2); this.Controls.Add(tb); } // Event handler: private void ClickHandler(object obj, EventArgs ea) { tb.Text = "You clicked me"; } // Event handler: private void EraseHandler(object obj, EventArgs ea) { tb.Text = ""; } } class ButtonTest{ public static void Main(){ Window win = new Window(); Application.Run(win); } } | |||
|
The program makes use of the already existing delegate type System.EventHandler. Operations in this delegate accept an Object and an EventArg parameter, and they return nothing (void).
The constructor of the class Window (which inherits from Form - a built-in class) dominates the program. In this constructor the window, aggregated by two buttons and a textbox, is built.
As emphasized in Program 23.4 we add handlers to the events b1.Click and b2.Click. We could have instantiated EventHandler explicitly, as shown in the comments, but the notion b1.Click += ClickHandler and b2.Click += EraseHandler is shorter and more elegant.
The two private instance methods ClickHandler and EraseHandler serve as event handlers. Notice that they conform to the signature of the EventHandler. (The signature is characterized by the parameter types and the return type).