Читать книгу Dojos für Entwickler - Stefan Lieser - Страница 33
Portal
ОглавлениеDas Portal hatte es in sich. Obwohl ich mit WPF schon einiges gemacht habe, fühlte ich mich etwas unsicher, diese sehr dynamische Aufgabenstellung mitWPF anzugehen, und entschied mich daher, das Problem mit Windows Forms zu lösen, weil mir das schneller von der Hand geht. Doch der Reihe nach. Wie die Benutzerschnittstelle des Testdatengenerators ungefähr aussehen könnte, habe ich in der Aufgabenstellung bereits durch ein Mockup angedeutet. Abbildung 8 zeigt, wie mein Ergebnis aussieht.
[Abb. 8] Das fertige Portal.
Ich sehe beim Portal zwei Herausforderungen: Die Anzahl der Spalten in den zu generierenden Daten ist variabel. Daraus ergibt sich, dass die Anzahl der Controls für Spaltendefinitionen variabel sein muss. Im Mockup habe ich daher Schaltflächen vorgesehen, mit denen eine Spaltendefinition entfernt bzw. hinzugefügt werden kann.
Die zweite Herausforderung sehe ich im Aufbau der Spaltendefinitionen. Je nach ausgewähltem Generatortyp sind unterschiedliche Eingaben notwendig. Mal sind zwei Textfelder für Minimum und Maximum erforderlich, mal nur eine für die Elemente einer Liste. Das heißt, dass sich der Aufbau der Benutzeroberfläche mit der Wahl des Generatortyps ändert.
Um diese beiden Herausforderungen möglichst isoliert angehen zu können, habe ich für die variablen Anteile einer Spaltendefinition mit UserControls gearbeitet. So habe ich für einen Generator, der Minimum- und Maximumwerte benötigt, ein UserControl erstellt, in dem zwei Textboxen mit zugehörigen Labels zusammengefasst sind.
Wird aus der Dropdownliste ein Generator ausgewählt, muss das zum Generator passende Control angezeigt werden. Ferner muss zum ausgewählten Generator später die zugehörige ColumnDefinition erzeugt werden, um damit dann die Daten zu generieren. Diese Informationen habe ich im Portal in einer Datenklasse Spalten-Definition zusammengefasst. Objekte dieser Klasse werden direkt in der Dropdownliste verwendet. Daher enthält die Spalten-Definition auch eine Beschreibung. Diese wird als DisplayMember in der Dropdownliste angezeigt, siehe Listing 10.
Listing 10: Eine Spalte definieren.
public class SpaltenDefinition {
public string Bezeichnung { get; set; }
public Type ControlType { get; set; }
public Func<string, object, ColumnDefinition> Columndefinition { get; set; }
}
Die Eigenschaft ControlType enthält den Typ des zu verwendenden Controls. Genügt ein Textfeld, kann hier typeof(TextBox) gesetzt werden. In komplizierteren Fällen wird der Typ eines dafür implementierten UserControls gesetzt.
Um für den ausgewählten Generatortyp eine ColumnDefinition erzeugen zu können, habe ich eine Eigenschaft ergänzt, die eine Funktion erhält, die genau dies bewerkstelligt: Sie erzeugt eine ColumnDefinition. Dazu erhält sie als Eingangsparameter zum einen den Namen der Spalte, zum anderen das Control mit allen weiteren Angaben. Da der Typ des Controls variabel ist, wird es vom Typ object übergeben. Die Funktion muss dieses Objekt dann auf den erwarteten Typ casten.
Bei der Initialisierung des Portals wird für die verfügbaren Generatoren jeweils eine Spaltendefinition erzeugt und in die Item-Liste des Dropdown-Controls gestellt, siehe Listing 11.
Listing 11: Spalten definieren.
new SpaltenDefinition {
Bezeichnung = "Random DateTime",
ControlType = typeof(MinimumMaximum),
Columndefinition = (columnName, control) => new ColumnDefinition(columnName,
new RandomDateTimeGenerator(
DateTime.Parse(((MinimumMaximum)control).Minimum),
DateTime.Parse(((MinimumMaximum)control).Maximum)))
}
Interessant hierbei ist die Lambda Expression. Diese erhält die beiden Parameter columnName und control und erzeugt daraus eine ColumnDefinition mit dem ausgewählten Generator. Da diese Lambda Expression im Kontext einer SpaltenDefinition steht, kann das übergebene Control gefahrlos auf den Typ gecastet werden, der auch in der Eigenschaft ControlType verwendet wird. Auch hier sähe eine Lösung mit Generics sicher eleganter aus, ist aber ohne Ko-/Kontravarianz nicht möglich.
Wird nun in der Combobox ein anderer Generatortyp ausgewählt, muss das in der Spaltendefinition angegebene Control angezeigt werden. Um dynamisch die zugehörigen Controls zu finden, füge ich alle Controls, die zu einer Spalte gehören (Plus-und Minus-Button, Textfeld für den Spaltennamen, Combobox, Platzhalter für UserControl) in ein Panel ein. Um in diesem Panel später dynamisch das UserControl austauschen zu können, füge ich dieses zusätzlich in ein weiteres Panel. Dieses dient jeweils als Platzhalter für das auszutauschende Control.
In der Form sind nur wenige statische Elemente vorhanden. Den Aufbau der Form zeigt die Document Outline in Abbildung 9. Darin ist dargestellt, wie die einzelnen Controls ineinandergeschachtelt sind.
[Abb. 9] Document Outline.
Abbildung 10 zeigt, wie die Controls für eine Spaltendefinition dynamisch zur Laufzeit zusammengesetzt werden. Dabei zeigen die Pfeile an, auf welches Control gegebenenfalls die Tag-Eigenschaft verweist.
[Abb. 10] Controls im Panel.
Nun zur zweiten Herausforderung, dem dynamischen Ergänzen und Löschen von Spaltendefinitionen. Jede Spaltendefinition verfügt über die beiden Schaltflächen zum Hinzufügen und Löschen von Spaltendefinitionen. Zurzeit füge ich eine neue Spaltendefinition jeweils ans Ende an, künftig könnte diese aber auch an der betreffenden Position eingefügt werden. Daher habe ich bereits an jeder Spaltendefinition einen Plus-Button vorgesehen. Für die Aufnahme aller Spaltenbeschreibungen ist im statischen Teil der Form ein Panel zuständig. Wird mit der Minus-Schaltfläche versucht, eine Spaltenbeschreibung zu entfernen, müssen die zugehörigen Controls aus diesem Panel entfernt werden. Um dies zu vereinfachen, ist das zugehörige Panel an der Tag-Eigenschaft des Buttons gesetzt. So „weiß" der Button, zu welchem Panel er gehört und kann dieses aus dem umschließenden Panel entfernen.
Wird im Portal die Schaltfläche Generieren angeklickt, muss für jede Spaltenbeschreibung eine ColumnDefinition erzeugt werden, um dann die Testdaten zu generieren. Dazu wird die Liste der Spaltenbeschreibungen im statischen Panel durchlaufen. Darin befindet sich jeweils ein Textfeld, das den Namen der Spalte enthält. Ferner befindet sich im Platzhalterpanel ein Control, in dem die Parameter für den Generator enthalten sind. In der Dropdownliste enthält das SelectedItem eine SpaltenDefinition, aus der sich die ColumnDefinition erstellen lässt. Dazu wird aus der SpaltenDefinition die Funktion zum Erzeugen der ColumnDefinition aufgerufen.
Insgesamt hat das Erstellen des Portals knapp zwei Stunden in Anspruch genommen. Automatisierte Tests habe ich dazu fast keine erstellt. Diese würde ich allerdings in einem „echten" Projekt im Nachhinein ergänzen, da die Logik für den dynamischen Aufbau des Portals doch recht umfangreich geworden ist. Um hier bei späteren Erweiterungen Fehler auszuschließen, würde ich die typischen Bedienungsschritte eines Anwenders automatisiert testen.