Kolumne: .NETversum

Tipps und Tricks rund um .NET und Visual Studio
Kommentare

Ohne Datenbindung wäre die Umsetzung des Musters Model-View-ViewModel (MVVM) in jener eleganten Variante, wie wir sie aus dem WPF-Umfeld kennen, nicht möglich. Neben dem Binden von Daten benötigt eine durchgängige Umsetzung dieses Musters jedoch auch die Möglichkeit zum Binden von Ereignissen. Am einfachsten kann das durch den Einsatz von Commands erreicht werden. Leider bieten die existierenden WPF-Steuerelemente nur für bestimmte ausgewählte Ereignisse Command-Gegenstücke an. Buttons bieten beispielsweise lediglich ein einziges Command, das auf das Click-Ereignis reagiert.

Eine mögliche Lösung für dieses Problem stellt der Einsatz von Attached Properties dar. Diese können an allen Steuerelementen angehängt werden. Um den Brückenschlag zu den vielen möglichen Events herzustellen, muss die zu diesem Zweck entworfene Attached Property mit einer Ereignisbehandlungsroutine versehen werden, die bei jeder Wertänderung – und somit auch beim Vergeben des Initialwerts via XAML – zur Ausführung gebracht wird. Innerhalb dieser Methode kann dann via Reflection eine Methode im ViewModel beim gewünschten Ereignis registriert werden. Listing 1 zeigt anhand der Klasse EventHelper und deren Attached Property EventBinding, wie diese Idee realisiert werden kann. Dabei wird davon ausgegangen, dass an diese Attached Property ein String mit folgendem Format zugewiesen wird: Ereignis1: Methode1, Ereignis2: Methode 2, …, EreignisN: MethodeN. Mit Ereignis1 bis N sind hierbei die zu bindenden Ereignisse des verwendeten Steuerelements gemeint, mit Methode1 bis N die an das jeweilige Ereignis zu bindende Methode aus dem ViewModel.

Als Hilfsklasse kommt dabei die innere Klasse EventBridge zum Einsatz. Sie ist für den Brückenschlag zwischen dem gewünschten Ereignis und der zu bindenden Methode im ViewModel verantwortlich. Sie nimmt diese Aufgabe war, indem sie die Methode OnEventRaised anbietet, die zum einen als Ereignisbehandlungsmethode registriert werden kann und zum anderen via Reflection an die gewünschte Methode im ViewModel weiterdelegiert.

Das Kernstück der Lösung befindet sich in der Methode EventBindingChanged, die bei jeder Änderung der Attached Property aufgerufen wird. Sie parst den übergebenen String und erzeugt via Reflection Verweise auf die ViewModel-Methoden beziehungsweise Ereignisse des Steuerelements. Unter Verwendung der oben beschriebenen EventBridge werden die ViewModel-Methoden beim jeweiligen Ereignis registriert. Zur Vereinfachung beinhaltet diese Implementierung keine Logik für das Abmelden von Ereignissen im Falle von nachträglichen Änderungen an der bereitgestellten Attached Property. Ein Beispiel für den Einsatz dieser Hilfsklasse befindet sich in Listing 2. Es bindet einige Ereignisse von Steuerelementen an gleichnamige Methode im ViewModel.

Listing 1
class EventHelper
{
    class EventBridge
    {
        private MethodInfo methodInfo;
        private object viewModel;

        public EventBridge(MethodInfo methodInfo, object viewModel)
        {
            this.methodInfo = methodInfo;
            this.viewModel = viewModel;
        }

        public void OnEventRaised(object sender, EventArgs e)
        {
            methodInfo.Invoke(viewModel, null);
        }
    }

    public static readonly DependencyProperty EventBinding = DependencyProperty.RegisterAttached(
        "EventBinding",
        typeof(string),
        typeof(EventHelper),
        new PropertyMetadata("", EventBindingChanged));
        
    private static void EventBindingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var element = d as FrameworkElement;
        var viewModel = element.DataContext;

        if (viewModel == null || element == null) return;

        var assignments = e.NewValue.ToString().Split(',');

        foreach (var assignment in assignments)
        {
            var parts = assignment.Split(':');
            if (parts.Length != 2) continue;

            var eventName = parts[0].Trim();
            var methodName = parts[1].Trim();

            if (string.IsNullOrEmpty(eventName) || string.IsNullOrEmpty(methodName)) continue;

            var eventHandle = element.GetType().GetEvent(eventName);
            var methodInfo = viewModel.GetType().GetMethod(methodName);

            var bridge = new EventBridge(methodInfo, viewModel);

            Delegate del = Delegate.CreateDelegate(eventHandle.EventHandlerType, bridge, bridge.GetType().GetMethod("OnEventRaised"));

            eventHandle.AddEventHandler(element, del);

        }
    }
  
}  

Listing 2

    
       
Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -