Roland Weigelt

Born to Code

News

.NET related Links

Archives

EventFilter Helper Class

EventFilter is a generic helper class for dealing with events that may be raised multiple times in rapid succession, when only the last event of a “burst” is of interest.

Introduction

 

Imagine a Windows Forms program mimicking the GUI of the Windows Explorer, where selecting a folder in the tree view on the left side will update the list of files on the right. A "quick'n dirty" implementation would handle the SelectedNodeChanged event of the TreeView control to update the file list, but a robust implementation that works nicely with slow media like CD/DVD or network drives should use a different approach.

When playing around with an actual instance of Windows Explorer and watching it more closely, you'll quickly notice that the file list is not updated immediately, but after a slight delay. You can use the keyboard in the tree view to move quickly from folder to folder, skipping folders you are not interested in. Only after you stay on a folder for a little while, the file list gets updated.

This approach of "wait until things have settled down a bit and then handle the last occurrence of an event" is pretty common in GUI development. The typical implementation uses a timer that is reset each time a new event is raised within a certain time interval, until the timer is finally allowed to elapse. Only at that time the event will actually be handled.

During development of a small hobby project called RemoteCanvas I got tired of taking care of timers, helper variables and event handlers over and over again, so I finally wrote a helper class acting as a "filter" for events.

Usage

  • Declare a member variable to hold an instance of the EventFilter class, with an event argument type matching that of the event to be filtered:
    private EventFilter<EventArgs> _filter
        = new EventFilter<EventArgs>();.
  • Hook up the HandleOriginalEvent method to the original event of the control. There's no great design time support for this, so you have to do that manually, e.g.
    myControl.SelectedIndexChanged += _filter.HandleOriginalEvent;
  • Connect the FilteredEventRaised event to your event handler:
    _filter.FilteredEventRaised += MyHandler;
  • That's it!

Download

The source code for the helper class (plus a small demo project for Visual Studio 2005) can be downloaded here.

Comments

Omer Mor said:

I like your helper class - looks very usefull.

I added support for a new syntax to the class which I find more concise:

publisher.Event += m_eventFilter

   .Filter(myHandler)

   .HandleOriginalEvent;

I made it possible by changing your class in the following way:

-------------------------------------------------

public class EventFilter<TEventArgs> : IEventWrapper<TEventArgs>

where TEventArgs : EventArgs

{

private const int _DefaultTimeWindow=150; // 150msek ist i.d.R. OK

private Timer _timer;

private object _pendingSender;

private TEventArgs _pendingEventArgs;

private void Timer_Tick( object sender, EventArgs e )

{

_timer.Stop();

EventHandler<TEventArgs> handler = this.FilteredEventRaised;

if (handler!=null)

handler( _pendingSender, _pendingEventArgs );

_pendingSender = null;

_pendingEventArgs = null;

}

       public IEventWrapper<TEventArgs> Filter(EventHandler<TEventArgs> wrappedEvent)

       {

           FilteredEventRaised += wrappedEvent;

           return this;

       }

/// <summary>

/// Handles the original event.

/// </summary>

/// <param name="sender">The sender.</param>

/// <param name="e">The <typeparamref name="TEventArgs"/> instance containing the event data.</param>

public void HandleOriginalEvent( object sender, TEventArgs e )

{

_timer.Stop();

_pendingSender = sender;

_pendingEventArgs = e;

_timer.Start();

}

/// <summary>

/// Occurs when an event has finally made it through the filter.

/// </summary>

public event EventHandler<TEventArgs> FilteredEventRaised;

/// <summary>

/// Initializes a new instance of the <see cref="EventFilter&lt;TEventArgs&gt;"/> class.

/// </summary>

/// <param name="timeWindow">The time window in milliseconds (default is 150msec).</param>

public EventFilter( int timeWindow )

{

_timer = new Timer();

_timer.Interval = timeWindow;

_timer.Tick += Timer_Tick;

}

/// <summary>

/// Initializes a new instance of the <see cref="EventFilter&lt;TEventArgs&gt;"/> class.

/// </summary>

public EventFilter()

:this(_DefaultTimeWindow)

{

}

}

   public interface IEventWrapper<TEventArgs> where TEventArgs : EventArgs

   {

       void HandleOriginalEvent(object sender, TEventArgs e);

   }

-------------------------------------------------

# December 28, 2007 2:23 PM

WeigeltRo said:

Omer: Your approach seems to be influenced by the idea of "fluent interfaces" (martinfowler.com/.../FluentInterface.html), but to be honest I'm not quite convinced by your syntax.

I think the problem in general is that it's not possible to get rid of the "+=" in an elegant way. Otherwise it would be interesting to either come up with a fluent interface that reads like "filter, please take care of this event and call that handler when necessary", or simply define a "traditional" function EventFilter.Connect(event,handler)

# December 28, 2007 3:32 PM

Omer Mor said:

Yeah - I intended it to be more fluent, but since I can't get rid of the += (as you mentioned) I didn't wrote "fluent" but used "concise" instead.

There was another nuisance that prevented me to make it better: I couldn't simply return an EventHandler<EventArgs> delegate from the Filter method because C# can't implicitly convert it to a non generic EventHandler delegate. So the only way was to return a method and not a delegate object, and this is the reason I needed the extra ".HandleOriginalEvent" in the end.

All in all - I agree that it's not prettier, but it is more concise, and personnaly I prefer it that way.

What for sure - if C# allowed us to use your EventFilter.Connect(event,handler) everyone would be happy...

# December 30, 2007 12:20 PM