Feedback

C# - EventFilter

Veröffentlicht von am 12/27/2007
(6 Bewertungen)
Die generische Hilfsklasse EventFilter dient als Filter für Ereignisse, die sehr häufig hintereinander erzeugt werden, von denen jedoch nur das jeweils letzte einer Serie interessant ist.

Ein Anwendungsfall findet sich z.B. in der GUI eines Windows Forms Programmes, das dem Windows Explorer nachempfunden ist. Angenommen im Hauptfenster stellt links ein TreeView den Verzeichnisbaum dar und rechts zeigt ein ListView die Liste der Dateien in dem gerade markierten Ordner an. In einer naiven Implementation würde man einfach auf das SelectedNodeChanged-Ereignis des TreeViews reagieren und den ListView für die Dateiliste aktualisieren. Bewegt man allerdings bei diesem Ansatz die Markierung sehr schnell von Ordner zu Ordner (z.B. über die Pfeiltasten), fühlt sich die GUI irgendwie "zäh" an. Die Aktualisierung der Dateiliste hinkt z.T. hinterher, insbesondere wenn sich die Ordner auf einem langsamen Laufwerk (CD/Netzwerk) befinden und dadurch der Aufbau der Dateiliste eine gewisse Zeit dauert.

Spielt man hingegen ein wenig mit dem echten Windows Explorer herum, stellt man schnell fest, dass dieser die Dateiliste gar nicht sofort nach der Auswahl eines Ordners aktualisiert, sondern einen Moment wartet. Erst wenn die Markierung eine Zeit lang nicht bewegt wurde, wird die Dateiliste aufgebaut. Dadurch kann man die Markierung im Baum sehr schnell bewegen, denn für übersprungene Ordner braucht so gar keine Dateiliste aufgebaut zu werden.

Der gerade am Beispiel des Windows Explorers beschriebene Ansatz wird in der GUI-Programmierung schon seit langer Zeit verwendet. Dazu benutzt man in der Regel einen Timer, der bei jedem Auftreten eines bestimmten Ereignisses neu gestartet wird; erst wenn tatsächlich ein bestimmtes Zeitintervall vollständig verstrichen ist, wird das Ereignis behandelt.

Das Grundprinzip ist einfach, allerdings ist es wenig lästig, dies immer wieder für verschiedene Ereignistypen neu zu programmieren. Und wenn erst einmal mehrere Ereignisse auf einem Formular so behandelt werden, wird der Quellcode zunehmend hässlicher.

Dies war die Motivation für die Entwicklung der Klasse EventFilter, mit deren Hilfe man den Zeitfenster-Ansatz in seinen eigenen Programmen mit nur wenigen Zeilen Code verwenden kann:

* Filter passend zum Typ der Ereignisargumente erzeugen und einer Instanzvariable des Formulars zuweisen, z.B.:

private EventFilter<EventArgs> _filter = new EventFilter<EventArgs>();


* Im Konstruktor des Formulars (hinter InitializeComponent) die Behandlung des Ereignisses verdrahten; dazu zunächst das Original-Ereignis mit dem Filter verbinden:

myControl.SelectedIndexChanged+=_filter.HandleOriginalEvent;


* Das Ereignis des Filters mit der Ereignismethode MyHandler verbinden:

_filter.FilteredEventRaised+=MyHandler;


* Fertig!
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;

namespace Weigelt.Windows.Forms
{
	/// <summary>
	/// Filter für Ereignisse, die sehr häufig hintereinander erzeugt werden,
	/// von denen jedoch nur das jeweils letzte einer Serie interessant ist.
	/// </summary>
	/// <remarks>
	/// <para>
	/// Verwendung: Die generische Klasse für einen konkreten Argumenttypen
	/// instantiieren und die Methode <see cref="HandleOriginalEvent"/> als
	/// Ereignismethode für das zu behandelnde Ereignis verwenden.
	/// </para><para>
	/// Das Ereignis <see cref="FilteredEventRaised"/> wird nun erst dann
	/// ausgelöst, wenn nach dem Empfang eines Ereignisses eine bestimmte
	/// Zeit lang (Voreinstellung: 150msek) kein weiteres Ereignis empfangen
	/// wird.
	/// </para><para>
	/// Copyright 2007 by Roland Weigelt.
	/// </para><para>
	/// Die Verwendung dieses Quelltextes ist sowohl für nicht-kommerzielle
	/// als auch kommerzielle Zwecke gestattet, die Nutzung geschieht dabei
	/// auf eigene Gefahr.
	/// </para>
	/// </remarks>
	/// <typeparam name="TEventArgs">Typ der Ereignisargumente.</typeparam>
	public class EventFilter<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;
		}

		/// <summary>
		/// Empfänger für das zu filternde Ereignis.
		/// </summary>
		/// <param name="sender">Die Quelle des Ereignisses.</param>
		/// <param name="e">Die Ereignisargumente.</param>
		/// <remarks>
		/// Diese Methode kann direkt als Ereignismethode verwendet werden,
		/// man kann sie aber natürlich auch von einer bereits vorhandenen
		/// Ereignismethode aus aufrufen.
		/// </remarks>
		public void HandleOriginalEvent( object sender, TEventArgs e )
		{
			_timer.Stop();
			_pendingSender = sender;
			_pendingEventArgs = e;
			_timer.Start();
		}

		/// <summary>
		/// Dieses Ereignis wird ausgelöst, wenn ein "Original"-Ereignis
		/// erfolgreich den Filter passiert hat, d.h. wenn innerhalb eines
		/// bestimmten Zeitfensters kein weiteres Ereignis eingetroffen ist.
		/// </summary>
		public event EventHandler<TEventArgs> FilteredEventRaised;

		/// <summary>
		/// Initialisiert eine neue Instanz der
		/// <see cref="EventFilter&lt;TEventArgs&gt;"/> Klasse.
		/// </summary>
		/// <param name="timeWindow">
		/// Das Zeitfenster in Millisekunden (Standard: 150msek).
		/// </param>
		public EventFilter( int timeWindow )
		{
			_timer = new Timer();
			_timer.Interval = timeWindow;
			_timer.Tick += Timer_Tick;
		}

		/// <summary>
		/// Initialisiert eine neue Instanz der
		/// <see cref="EventFilter&lt;TEventArgs&gt;"/> Klasse.
		/// </summary>
		public EventFilter()
			:this(_DefaultTimeWindow)
		{
		}
	}
}

Abgelegt unter Events, Filter, Windows Forms, WinForms.

2 Kommentare zum Snippet

Roland Weigelt schrieb am 12/27/2007:
Unter http://www.roland-weigelt.de/downloads/libraries/EventFilter.zip gibt es die Klasse zusammen mit einem einfachen Demo-Projekt für VS2005 zum Herunterladen.
Tim Hartwig schrieb am 1/26/2008:
Glückwunsch zum wohl verdienten 1. Platz des zweiten Snippet Wettbewerbs!
 

Logge dich ein, um hier zu kommentieren!