Properties with property changed event, part 3

A while ago, I talked about how to write basic events for changed properties, and about the INotifyPropertyChanged interface. There is a third way to manage events, which is especially useful when your class has many events, but you expect a very low number of them to be actually handled.

As discussed before, every EventHandler stored in your object takes up space, which is a bit of a waste if most of those will be null. But events allow you to implement the add and remove methods yourself, so you can choose where to store the EventHandler delegate.

One way to do that is through the System.ComponentModel.EventHandlerList class. System.ComponentModel.Component exposes an Events property of this type, which is used by all classes inheriting from Component, including all Windows Forms controls.

The following example inherits from System.Windows.Forms.TextBox, adding a CueBanner property (like the Internet Explorer 7 search box) and a CueBannerChanged event:

using System; 
using System.ComponentModel;
using System.Windows.Forms;

namespace U2U.Framework.Windows.Forms
{
/// <summary>
/// Textbox that displays a CueBanner when the Text is empty.
/// </summary>
public class CueBannerTextBox : TextBox
{
private string cueBanner = string.Empty;

/// <summary>
/// Gets or sets the prompt text to display when there is nothing in the Text property.
/// </summary>

[Browsable(true)]
[EditorBrowsable(EditorBrowsableState.Always)]
[Category("Appearance")]
[Description("The prompt text to display when there is nothing in the Text property.")]
[DefaultValue("")]
public string CueBanner
{
get { return cueBanner; }
set
{
if (value == null)
{
value = string.Empty;
}
if (value != cueBanner)
{
cueBanner = value;
NativeMethods.SendMessage(Handle, EM_SETCUEBANNER, IntPtr.Zero, cueBanner);
OnCueBannerChanged(EventArgs.Empty);
}
}
}

private const int EM_SETCUEBANNER = 0x1501;
private static readonly object EVENT_CUEBANNERCHANGED = new object();

/// <summary>
/// Occurs when the value of the <see cref="CueBanner"/> property has changed.
/// </summary>
[Category("Property Changed")]
[Description("Event raised when the value of the CueBanner property changed.")]
public event EventHandler CueBannerChanged
{
add { base.Events.AddHandler(EVENT_CUEBANNERCHANGED, value); }
remove { base.Events.RemoveHandler(EVENT_CUEBANNERCHANGED, value); }
}

/// <summary>
/// Raises the CueBannerChanged event.
/// </summary>
/// <param name="e">An <see cref="EventArgs"/> that contains the event data.</param>
protected virtual void OnCueBannerChanged(EventArgs e)
{
EventHandler handler = base.Events[EVENT_CUEBANNERCHANGED] as EventHandler;
if (handler != null)
{
handler(this, e);
}
}
}
}


You'll also need this:

using System; 
using System.Runtime.InteropServices;

namespace U2U.Framework.Windows.Forms
{
internal static class NativeMethods
{
[DllImport("user32", CharSet = CharSet.Unicode)]
internal static extern IntPtr SendMessage(IntPtr hWnd, int message, IntPtr wParam, string lParam);
}
}


Notice how the CueBannerChanged event provides an explicit implementation of the add and remove methods. The add method adds the delegate in the inherited Events collection, using a private static object as the key, and the remove method removes it from that same collection.

The OnCueBannerChanged method retrieves the delegate from the same collection, using the same key. Notice how this collection can store any type of delegate, not just EventHandlers, so we need to cast it back to EventHandler before we can use it.

Enjoy.

Technorati Tags: , , , , ,