This is the archived version of Roland Weigelt's weblog that ran from 2003 to 2023 at weblogs.asp.net

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.

3 Comments

  • 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 : IEventWrapper
    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 handler = this.FilteredEventRaised;
    if (handler!=null)
    handler( _pendingSender, _pendingEventArgs );

    _pendingSender = null;
    _pendingEventArgs = null;
    }

    public IEventWrapper Filter(EventHandler wrappedEvent)
    {
    FilteredEventRaised += wrappedEvent;
    return this;
    }

    ///
    /// Handles the original event.
    ///
    /// The sender.

    /// The instance containing the event data.

    public void HandleOriginalEvent( object sender, TEventArgs e )
    {
    _timer.Stop();
    _pendingSender = sender;
    _pendingEventArgs = e;
    _timer.Start();
    }

    ///
    /// Occurs when an event has finally made it through the filter.
    ///
    public event EventHandler FilteredEventRaised;

    ///
    /// Initializes a new instance of the class.
    ///
    /// The time window in milliseconds (default is 150msec).

    public EventFilter( int timeWindow )
    {
    _timer = new Timer();
    _timer.Interval = timeWindow;
    _timer.Tick += Timer_Tick;
    }

    ///
    /// Initializes a new instance of the class.
    ///
    public EventFilter()
    :this(_DefaultTimeWindow)
    {
    }
    }

    public interface IEventWrapper where TEventArgs : EventArgs
    {
    void HandleOriginalEvent(object sender, TEventArgs e);
    }
    -------------------------------------------------

  • Omer: Your approach seems to be influenced by the idea of "fluent interfaces" (http://martinfowler.com/bliki/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)

  • 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 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...

Comments have been disabled for this content.