Laden...

PropertyNotifying - Databinding auch für Nicht-Control-Klassen

Erstellt von ErfinderDesRades vor 14 Jahren Letzter Beitrag vor 14 Jahren 3.460 Views
ErfinderDesRades Themenstarter:in
5.299 Beiträge seit 2008
vor 14 Jahren
PropertyNotifying - Databinding auch für Nicht-Control-Klassen

Wie in Databinding zwischen controls, mit benutzerdefinierten Konvertern schon gezeigt, kann man alle Klassen, die INotifyPropertyChanged implementieren, oder zu bestimmten Properties das entsprechende .Changed-Event bereitstellen, mit Databinding an Controls binden.
Das heißt, man kann auch Properties von Controls direkt untereinander verbinden.

Man kann aber nicht alle Klassen untereinander verbinden, auch wenn sie eine der beiden Bedingungen erfüllen.
Nämlich, weil nur Controls eine BindingsCollection haben (genauer: Klassen, die IBindableComponent implementieren)

Ich hab mal im Reflector-Disassembler nachgeguckt, wie das im Detail abläuft, und gefunden, dass

System.ComponentModel.PropertyDescriptor.AddValueChanged(object, System.EventHandler)

des Pudels Kern ist.

Damit habich einen BindeMechanismus erstellt, der ohne BindingsCollection auskommt.
Mit dem also alle Klassen, die INotifyPropertyChanged implementieren, oder zu bestimmten Properties das entsprechende .Changed-Event bereitstellen, auch untereinander bindebar sind.

Ein Nutzen ist zB, wenn man versucht, die Checked-Property von ToolstripMenuItems miteinander zu verknüpfen. Databinding scheidet hier aus, weil ToolstripMenuItem nicht von Control erbt.

Hier also die Entsprechung des Samples von Databinding zwischen controls, mit benutzerdefinierten Konvertern , bei der zusätzlich 2 ToolstripMenuItem.Checked Properties invertiert synchronisiert werden:


using System;
using System.Windows.Forms;

namespace PropNotifyer {

   public partial class Form1 : Form {
      public Form1() {
         InitializeComponent();

         EventHandler toggleEnabled = (s, e) => { button1.Enabled = !button1.Enabled; };
         button1.Click += toggleEnabled;
         button2.Click += toggleEnabled;

         Converter<bool, bool> inverter = b => !b;

         // toolStripButton1.Checked mit toolStripButton2.Checked invertiert synchronisieren
         // OnInit() weist an, dass die Synchronisierung sofort erstmalig auszuführen ist
         toolStripButton1.Property("Checked").OnInit()
            .Synchronisize(toolStripButton2, "Checked", inverter);

         // button1.Enabled mit button2.Enabled invertiert synchronisieren, und gleich noch 
         // panel1.Enabled "benachrichtigen" (einseitige Synchronisation)
         button1.Property("Enabled").OnInit().Synchronisize(button2, "Enabled", inverter)
            .Notify(panel1, "Enabled");
      }

   }
}


Die ungewohnte Syntax, bei der im Einzeiler beliebig viele Synchronisierungen verkettet werden, rührt daher, dass ich das ganze in einer Art Flow Interface gepackt habe, (so ähnlich wie von Martin Fowler publiziert).
Dabei gibt das Objekt immer sich selbst zurück, sodaß immer weitere Anweisungen angehängt werden können.
"Einstieg" ins Flow-Interface ist dabei die Extension-Function .Property<T>(string Name) where T : class
Man ist übrigens nicht gezwungen, die Ketten-Syntax zu verwenden, ebensogut kann man schreiben:


var p = toolStripButton1.Property("Checked");
p.OnInit();
p.Synchronisize(toolStripButton2, "Checked", inverter);

oder auch Mischformen

Der frühe Apfel fängt den Wurm.