Creating a Custom EventIf an event does not provide data but is merely a signal that something has happened, you can take advantage of the EventHandler delegate class and its empty EventArgs implementation. However, we want to provide additional information in the TextChanged event raised by our Textbox control. The newly minted event will track both before and after values of the Text property between postback submissions. The control loads the oldValue from data saved in ViewState; the newValue value loads from the data received in the <INPUT type="text"> HTML element through postback. We now move on to create our custom EventArgs class to support our custom event.Creating a TextChangedEventArgs ClassThe first requirement is to create an enhanced EventArgs-based class that holds the event data. We create a new class derived from EventArgs that exposes two read-only properties to clients, OldValue and NewValue, as shown in the following code:public class TextChangedEventArgs : EventArgs{ private string oldValue; private string newValue; public TextChangedEventArgs(string oldValue, string newValue) { this.oldValue = oldValue; this.newValue = newValue; } public string OldValue { get { return oldValue; } } public string NewValue { get { return newValue; } }}The class created is fairly straightforward. The two properties have only get accessors to make them read-only, making the constructor the only way to populate the internal fields with their values.Creating a TextChangedEventHandler DelegateDelegate creation is the next step in defining our custom event. There is not an inheritance chain that must be followed with delegates, as all delegate types are created using the keyword delegate. Instead, we choose to follow the method signature used by other controls in ASP.NET to build upon a successful design pattern.The signature of the delegate has two parameters and a void return value. The first parameter remains of type object, and the second parameter must be of type EventArgs or derived from it. Because we already created the TextChangedEventArgs class, we use that as our second parameter to take advantage of its OldValue and NewValue properties.The name used in the declaration of the following delegate is also important. The pattern for ASP.NET controls is to add the word "EventHandler" to the end of the event of the delegate. In this case, we add "TextChanged" to "EventHandler" to get TextChangedEventHandler as our name.Both the TextChangedEventArgs class and the TextChangedEventHandler delegate are put into a file named TextChanged.cs that is part of the ControlsBookLib library project for reference by our new control, as shown in Listing 5-5.Listing 5-5. The TextChanged Class File for the TextChangedEventArgs Class andTextChangedEventHandler Delegate Definitionsusing System;namespace ControlsBookLib.Ch05{ public delegate void TextChangedEventHandler(object o, TextChangedEventArgs tce); public class TextChangedEventArgs : EventArgs { private string oldValue; private string newValue; public TextChangedEventArgs(string oldValue, string newValue) { this.oldValue = oldValue; this.newValue = newValue; } public string OldValue { get { return oldValue; } } public string NewValue { get { return newValue; } } }}
Adding an Event to the CustomEventTextbox ControlTo demonstrate the newly minted TextChangedEventHandler delegate, we take our Textbox control and copy its contents into a class named CustomEventTextbox. Another option would be to customize the behavior in an object-oriented manner by overriding the necessary methods in a derived class. However, in this chapter, we choose the route of separate classes so that we can more clearly isolate the two Textbox control examples and highlight the different design decisions embodied in them.Replacing the event declaration is the easiest part. The control starts with an EventHandler delegate but is changed to take a TextChangedEventHandler delegate:public event TextChangedEventHandler TextChanged;
The second change is the replacement of the OnTextChanged event invocation method to take TextChangedEventArgs as the single parameter to the method, as shown in the following code. This is one of the reasons for having the On-prefixed methods in controls as an abstraction layer. It makes it a simpler code change to augment or replace the event mechanism. protected virtual void OnTextChanged(TextChangedEventArgs tce) { if (TextChanged != null) TextChanged(this, tce); }
The next step is to add logic to track the before and after values. A private string field named oldText is added to the class and is given its value inside LoadPostData. This gives us a chance to load TextChangedEventArgs properly when we raise the event. Here is a snippet of the code change from LoadPostData that does the work:if (!Text.Equals(postedValue)) { oldText = Text; Text = postedValue; return true; }
The last step is to replace all routines that call OnTextChanged. We have only one: RaisePostDataChanged. It takes the before and after values from the oldText field and the Text property in LoadPostData and creates a new TextChangedEventArgs class instance: public void RaisePostDataChangedEvent() { OnTextChanged(new TextChangedEventArgs(oldText, Text)); }Our control is now ready for testing on a Web Form to display its dazzling event capabilities. Listing 5-6 contains the full source code.Listing 5-6. The CustomEventTextbox Control Class Fileusing System;using System.Web;using System.Web.UI;using System.Collections.Specialized;using System.ComponentModel;namespace ControlsBookLib.Ch05{ [ToolboxData("<{0}:CustomEventTextbox runat=server></{0}:CustomEventTextbox>"), DefaultProperty("Text")] public class CustomEventTextbox : Control, IPostBackDataHandler { private string oldText; public virtual string Text { get { object text = ViewState["Text"]; if (text == null) return string.Empty; else return (string)text; } set { ViewState["Text"] = value; } } public bool LoadPostData(string postDataKey, NameValueCollection postCollection) { string postedValue = postCollection[postDataKey]; if (!Text.Equals(postedValue)) { oldText = Text; Text = postedValue; return true; } else return false; } public void RaisePostDataChangedEvent() { OnTextChanged(new TextChangedEventArgs(oldText, Text)); } protected void OnTextChanged(TextChangedEventArgs tce) { if (TextChanged != null) TextChanged(this, tce); } public event TextChangedEventHandler TextChanged; override protected void Render(HtmlTextWriter writer) { base.Render(writer); Page.VerifyRenderingInServerForm(this); // write out the <INPUT type="text"> tag writer.Write("<INPUT type=\"text\" name=\""); writer.Write(this.UniqueID); writer.Write("\" value=\"" + this.Text + "\" />"); } }}