Das PropertyGrid besitzt ein Event namens PropertyValueChanged. Dieses wird ausgelöst, wenn eine Eigenschaft editiert wird. Dies funktioniert soweit auch gut, außer die Eigenschaft wird durch eine Collection dargestellt. In diesem Falle wird nichts ausgelöst.
Hintergrund
Das Problem liegt darin, dass sich die Collection selbst nicht ändert, wenn sich in einem der Werte eines Child-Objektes ein Wert ändert. Ebenfalls ändert sich die Collection nach außen hin nicht, wenn neue Elemente hinzugefügt werden. Sehen wir uns die Implementierung auf Seiten des PropertyGrids an.
Das PropertyGrid besitzt eine Auflistung GridEntryCollection. Diese beinhält GridEntry-Objekte, welche die einzelnen Properties darstellen. GridEntry besitzt nun eine Methode EditPropertyValue. Diese überprüft, ob sich der Wert verändert hat und setzt darauf hin ein Commit ab, oder nicht (wenn keine Änderung). Im Commit wird anschließend der besagte Event ausgelöst. Hier nun der relevante Teil aus der Methode:
Hier ist nun schön zu erkennen, dass lediglich die Objekte miteinander verglichen werden, was bei einer Collection natürlich grundsätzlich keine Änderung entdecken lässt. Dadurch muss man sich mit einem Workaround behelfen.
Mögliche Workarounds
Der erste Workaround ist vermutlich der, der mit dem geringsten Aufwand verbunden ist: Es gibt einige 3rd Party Produkte, welche diesen Fehler erkannt und ausgemerzt haben. Damit muss selbst keine Implementierung vorgenommen werden, jedoch sind die meisten dieser Produkte kommerzieller Natur und kosten somit einige Euros.
Eine weitere Möglichkeit ist, selbst Hand anzulegen und in die Tasten zu klopfen. Dazu muss man sich nun einen grundsätzlichen Gedanken machen. Wie kann erreicht werden, dass der Objekt-Vergleich in oben gezeigter Methode erkennt, dass das Objekt geändert wurde? Korrekt, wir müssen die Collection klonen. Das heißt, die Objekte der Collection und auch die Collection selbst müssen das Interface ICloneable implementieren.
Die Collections selbst werden im PropertyGrid in einem CollectionEditor angezeigt. Dieser ist standardmäßig für alle Collections der gleiche. Diesen müssen wir nun für unsere Collection-Eigenschaft austauschen, indem wir eine eigene Klasse erstellen, die von UITypeEditor ableitet:
Im Grunde passiert hier wenig Spannendes. Es wird lediglich bei einem Edit die Collection geklont und die neue Collection zurück geliefert. Dadurch erkennt nun das PropertyGrid eine Änderung und aktiviert ihrerseits das Event PropertyValueChanged. Daraufhin ist es nun möglich festzustellen, dass a) in der Collection eine Änderung passiert sein muss und b) können wir feststellen wo diese passiert ist.
Schlussendlich muss nun noch der jeweiligen Eigenschaft zugewiesen werden, dass diese mit dem eigenen CustomCollectionEditor zu öffnen ist. Dazu sind folgende Attribute bei der entsprechenden Property zu setzen:
private CustomCollection _collection = new CustomCollection();
[TypeConverter(typeof(CustomCollection))]
[Editor(typeof(CustomCollectionEditor), typeof(UITypeEditor))]
public CustomCollection MyCollection
{
get { returnthis._collection; }
set { this._collection = value; }
}
Hierzu gibt es noch einen kleinen Tipp: Es ist darauf zu achten, dass auch der Setter tatsächlich vorhanden ist. Im GridEntry wird abgefragt, ob das Object editierbar ist (IsValueEditable). Wäre nur ein Getter vorhanden, wäre IsValueEditable auf false und der Wert würde nicht committed warden, wodurch auch das Event PropertyValueChanged nicht ausgelöst werden würde.
Fazit
Schön wäre es natürlich, würde das PropertyGrid diese Funktionalität anbieten. Da das nicht der Fall ist, muss man selbst etwas nachhelfen. Die oben gezeigte Variante kommt dem gewünschten schon recht nahe, auch wenn der besprochene Event erst angetriggert wird, nachdem der CollectionEditor geschlossen wird. Zwar in manchen Fällen (Eingabevalidierung) nicht erwünscht, lässt sich aber so einfach nicht beheben. Dennoch bietet diese Variante eine einfache und gute Möglichkeit, das gewünschte Ziel zu erreichen.