Ab sofort steht unter http://devtyr.norberteder.com mein neues Blog zur Verfügung. Dieses Blog wird nicht weiter betreut, bleibt aber erhalten. Neue Eintr%auml;ge erfolgen nur mehr im neuen Blog. Kommentare werden ebenfalls nicht mehr behandelt. Wer weiterhin meinen Einträgen und Aktivitäten folgen möchte, möge bitte RSS-Feeds, Verlinkungen etc. an die neue Location anpassen.
.NET BlogBook mit eigener Webpräsenz
27.01.08 - .NET, Grundlagen, Base Framework, WPF, ASP.NET, Silverlight, Mobile Devices, Datenverwaltung, Visual Studio, Allerlei, Internet, Community Beitrag von Norbert Eder
Das .NET BlogBook ist ab sofort auf einer eigenen Webpräsenz zu finden. Der Hintergrund besteht aus mehreren Punkten, wovon ich einige hier nennen möchte.
.NET Casts Ursprünglich wurde das .NET BlogBook als Nebenprodukt von .NET Casts geführt. Thematisch passten beide Bereiche nicht optimal zusammen, wodurch diese nun getrennt wurden. Auf beiden Seiten wird nun der eigentilche Bereich entsprechend gefördert.
Andrang Das .NET BlogBook darf sich größter Beliebtheit erfreuen, wodurch eine eigene Webpräsenz fast unumgänglich wurde. Dadurch besteht nun die Möglichkeit, zukünftig besser auf unsere Leser eingehen zu können. Aber dazu möchte ich vorerst noch nicht zuviel verraten.
Dieser Schritt soll das .NET BlogBook zu einem eigenständigen Produkt ausbauen und zukünftig auch wesentlich zur Verbesserung der Qualität beitragen.
PS: Die Inhalte bleiben noch die nächsten Tage auf der .NET Casts Website erhalten, werden anschließend jedoch mit einer entsprechenden Meldung von der Site genommen.
Wie in jeder anderen Anwendung, kann natürlich auch in einer WPF-Applikation der Fehlerteufel stecken. Werden etwaige Fehlverhalten nicht abgefangen, kommt es zu ungewollten Abstürzen, die nicht nur beim Endkunden/-benutzer nur sehr unwillig aufgenommen werden. es gilt also, sämtliche Fehler abzufangen.
Grundsätzlich gibt es beim Exception-Handling keine Unterschiede zum Windows-Forms-Pendant, da es sich ja um eine Funktionalität des Frameworks handelt. Wobei es jedoch einen Unterschied gibt, ist das globale Abfangen von Fehlern.
Implementierung
Hat man bei einer Windows-Forms-Anwendung einen try-catch-Block um den Aufruf des Hauptfensters gelegt, steht in der Windows Presentation Foundation das Event DispatcherUnhandledException zur Verfügung. Hier stehen nun zwei Wege zur Verfügung, wie der dazu notwendige Eventhandler gesetzt werden kann.
Die erste Variante besteht in der Deklaration in der App.xaml, wie folgt:
Weiters kann das Event, wie unter Windows Forms, direkt per Code abonniert werden:
publicpartialclass App : Application
{
public App()
{
this.DispatcherUnhandledException +=
new DispatcherUnhandledExceptionEventHandler(
App_DispatcherUnhandledException);
}
}
Schlussendlich muss noch der Eventhandler selbst implementiert werden, welcher in diesem kleinen Beispiel lediglich eine MessageBox mit der Exception-Message ausgibt. An dieser Stelle wäre es durchaus sinnvoll, die nicht behandelten Exceptions in eine Log-Datei mitzuschreiben um später die Möglichkeit einer Auswertung zu haben.
Durch das Setzen der Eigenschaft Handled wird festgesetzt, dass WPF diese Exception nicht weiter behandelt, da dies bereits innerhalb des Handlers vorgenommen wurde.
Wichtige Hinweise
Durch das Event DispatcherUnhandledException werden alle unbehandelte Ausnahmen gemeldet, welche im Thread des User Interfaces laufen. Treten Fehler beispielsweise innerhalb eines Background Workers auf, gibt es darüber keine Benachrichtigung über DispatcherUnhandledException. In diesen Fällen müssen Sie sich selbst darum kümmern, in dem entweder eigene Fehlerbehandlungsroutinen geschrieben werden, diese Exceptions an den Haupt-UI-Thread übergeben werden bzw. diese im Haupt-UI-Thread nochmals werfen, ohne sie direkt zu behandeln. Auch in diesem Fall wird diese Exception dann über das Event DispatcherUnhandledException gemeldet.
Durch den Parameter DispatcherUnhandledExceptionEventArgs wird zum einen die geworfene Exception überreicht und weiters der Dispatcher, von dem aus die Exception geworfen wurde. Durch die Angabe der Exception kann nun auch überprüft werden, ob dieser Fehler überhaupt behebbar ist oder nicht. Nicht behebbar wäre beispielsweise eine StackOverflowException.
Fazit
Fehlerbehandlung ist ein absolut wichtiges Konzept, um den Benutzer vor Fehlern zu schützen und ein ungewolltes Beenden einer Anwendung zu verhindern. Verwenden Sie diese Methode, um alle behebbaren Exceptions abzufangen, die in ihrer Anwendung nicht behandelt werden. Achten Sie jedoch explizit auf Fehler, die in unterschiedlichen Threads auftreten.
An diesem Beispiel ist zu sehen, dass ein Button definiert wurde. Dieser reagiert auf das Click-Ereignis. Anstatt nun einen Eventhandler zu setzen, welcher in der Code-Behind-Datei behandelt wird, wird dieser über x:Code direkt im XAML behandelt.
Hinweise zum x:Code Element
Der Vollständigkeit halber, hier ein paar Hinweise zu diesem Element.
Da hier Code direkt in XAML eingefügt wird, sollte normalerweise ein CDATA verwendet werden. Hintergrund ist, dass beispielsweise das Einfügen von < und > zu einem invaliden XML führen würde - und XAML ist ja im Endeffekt uach nur XML. Im obigen Beispiel wurde CDATA weggelassen, da kein spezielles Zeichen vorkommt.
Weiters gibt es bei der Verwendung von x:Code einige Limitierungen. Bei der Kompilierung wird sämtlicher Code innerhalb eines x:Code Elements als Teil der dahinterliegenden partiellen Klasse angesehen. Daher ist es nicht möglich, zusätzliche Klassen zu definieren (ausser nested Classes). Wird auf Entitäten ausserhalb der partiellen Klasse verwiesen, kann dies nur voll qualifiziert passieren.
Resultierende Probleme
Aus der Verwendung des x:Code Elements ergeben sich einige Probleme, die es zu wissen gilt.
Interpretierung/Kompilierung Anwendungen, die XAML-Code interpretieren, müssen darin enthaltenen Sourcecode kompilieren können. Dies ist jedoch nicht immer der Fall. Beispielsweise kann dies der Internet Explorer nicht, Visual Studio hingegen schon. Dies bedeutet, dass obiger Code nicht im IE funktionieren würde, da die notwendigen Assemblies nicht im Hintergrund generiert werden können.
Trennung Design und Implementierung WPF besitzt den Vorteil, dass Design und Implementierung voneinander getrennt werden können. Bei entsprechenden Ressourcen kann als XAML durch einen Designer aufbereitet werden, während ein Entwickler die tatsächliche Implementierung vornimmt. Wird nun das x:Code Element verwendet, wird der Präsentationsteil mit der Logik verbunden, was zum einen eine hohe Koppelung bringt und zudem einen wesentlich erhöhten Wartungsaufwand. Selbst Microsoft weist darauf hin:
In terms of architecture and coding philosophy, maintaining a separation between markup and code-behind keeps the designer and developer roles much more distinct.
Limitierung Da für das x:Code Element (wie oben beschrieben) Limitierungen bestehen, kann kaum etwas ernsthaftes in ein XAML eingebettet werden.
Daher stellt sich die grundsätzliche Frage, warum es dieses Element überhaupt gibt, da es schlussendlich verleitet, eben mal schnell dann doch den Code direkt ins XAML zu schreiben.
Fazit
Warum es das x:Code Element gibt, kann schwer beantwortet werden. Möglicherweise gibt es einen guten Grund, welcher sich mir im Moment entzieht. In der Praxis sollte dieses Element jedoch nicht verwendet werden. Selbst für eine kurze Demonstration sollten man doch lieber zur sauberen Variante greifen.
Animationen mittels WPF sind nicht schwer. Dieser Artikel soll eine kleine Einführung liefern und zeigen, wie einfach eine kleine Animation mit Fotos gemacht werden kann. Das Resultat ist nachfolgend kurz zu sehen.
Erste Schritte
Bevor es an die Animation selbst geht, entwerfen wir ein UserControl, welches für die Darstellung der Fotos zuständig ist. Hier auch gleich der entsprechende Code.
publicpartialclass PictureControl : UserControl
{
private Size _originSize;
private Point _originLocation;
publicstatic DependencyProperty OriginSizeProperty =
DependencyProperty.Register(
"OriginSize",
typeof(Size), typeof(Size));
publicstatic DependencyProperty OriginLocationProperty =
DependencyProperty.Register(
"OriginLocation",
typeof(Point), typeof(Point));
public PictureControl()
{
InitializeComponent();
}
publicvoid SetImage(string path)
{
ImageControl.Source = BitmapFrame.Create(new Uri(path));
}
public Size OriginSize
{
get { return _originSize; }
set { this._originSize = value; }
}
public Point OriginLocation
{
get { return _originLocation; }
set { _originLocation = value; }
}
}
Darin befinden sich keine Besonderheiten. Es wird lediglich eine Eigenschaft SetImage angeboten. Darüber kann das gewünschte Foto gesetzt und somit angezeigt werden.
Weiters wird dieses erstellte Control nun in ein Window eingebettet, damit auch tatsächlich Fotos angezeigt werden können. In dieser Demoanwendung werden insgesamt vier Fotos dargestellt - also wenig dynamisch.
Da nun die grundlegenden Arbeiten erledigt sind, werden wir uns dem Thema Animation zuwenden.
Animation erstellen
Wichtigster Bestandteil für eine Animation ist das Storyboard. Dabei handelt es sich um eine Timeline, die auf einzelne Elemente und dazugehörige Animationen verweist. Mittels Begin werden die in einem Storyboard enthaltenen Animationen ausgeführt. Der Ablauf des StoryBoards kann mittels Stop angehalten werden. Weiters stehen Pause, Resume und Seek zur Verfügung. Zusätzlich stehen Events zur Verfügung, um beispielsweise auf die Beendigung des Durchlaufs eines Storyboards reagieren zu können. Es empfiehlt sich einen Blick auf die Storyboard Übersicht zu werfen.
Durch die WPF werden bereits einige vordefinierte Animationen unterstützt. Diese leiten von AnimationTimeline. Diese Klasse basiert wiederum auf Timeline.
Folgende Ableitungen von AnimationTimeline stehen zur Verfügung, worauf dann weitere Timelines basieren.
DoubleAnimationBase
BooleanAnimationBase
ByteAnimationBase
CharAnimationBase
ColorAnimationBase
DecimalAnimationBase
Int16AnimationBase
Int32AnimationBase
Int64AnimationBase
MatrixAnimationBase
ObjectAnimationBase
Point3DAnimationBase
PointAnimationBase
QuaternionAnimationBase
RectAnimationBase
Rotation3DAnimationBase
SingleAnimationBase
SizeAnimationBase
StringAnimationBase
Vector3DAnimationBase
VectorAnimationBase
In unserem Beispiel wird DoubleAnimation verwendet (basiert auf DoubleAnimationBase). Jede dieser Animationen beschränkt sich (wie zu sehen ist) auf einen bestimmten Typ, welcher verändert werden kann.
Hier ein Beispiel zur Verdeutlichung:
DoubleAnimation doubleAnimHeight = new DoubleAnimation();
doubleAnimHeight.From = 10;
doubleAnimHeight.To = 100;
doubleAnimHeight.Duration = new Duration(
TimeSpan.FromSeconds(0.5));
Diese defnierte DoubleAnimation bewirkt, dass innerhalb einer bestimmten Duration (also Zeitpannse) der Wert von 10 auf 100 erhöht wird. Andere Animationen funktionieren auf dieselbe Art und Weise.
Im zugrunde liegenden Beispiel soll nun bei einem Klick auf ein Foto, dieses in den Contentbereich verschoben und vergrößert werden. Bei nochmaligem Klick, soll sich das Foto an die ursprüngliche Position zurück animieren. Dafür benötigen wir wir insgesamt vier DoubleAnimations, jeweils für die Höhe, Breite, Top-Position und Left-Position:
DoubleAnimation doubleAnimWidth = new DoubleAnimation();
doubleAnimWidth.From = picControl.Width;
doubleAnimWidth.To = size.Width;
doubleAnimWidth.Duration = new Duration(
TimeSpan.FromSeconds(0.5));
DoubleAnimation doubleAnimHeight = new DoubleAnimation();
doubleAnimHeight.From = picControl.Height;
doubleAnimHeight.To = size.Height;
doubleAnimHeight.Duration = new Duration(
TimeSpan.FromSeconds(0.5));
DoubleAnimation doubleAnimTop = new DoubleAnimation();
doubleAnimTop.From = Canvas.GetTop(picControl);
doubleAnimTop.To = location.Y;
doubleAnimTop.Duration = new Duration(
TimeSpan.FromSeconds(0.5));
DoubleAnimation doubleAnimLeft = new DoubleAnimation();
doubleAnimLeft.From = Canvas.GetLeft(picControl);
doubleAnimLeft.To = location.X;
doubleAnimLeft.Duration = new Duration(
TimeSpan.FromSeconds(0.5));
Wie zu sehen ist, werden die Eigenschaften From und To nicht fix zugewiesen, sondern stammen von einer zuvor definierten Logik:
PictureControl picControl = (PictureControl)sender;
Point location = new Point(
Canvas.GetLeft(picControl),
Canvas.GetTop(picControl));
Size size = new Size(
picControl.ActualWidth,
picControl.ActualHeight);
if (location != _targetLocation)
{
location = _targetLocation;
size = _targetSize;
}
else
{
location = picControl.OriginLocation;
size = picControl.OriginSize;
}
Zu beachten ist, dass sich dieser Code im MouseDown-Eventhandler befindet. Bei der Instanzierung der Picture-Steuerelemente wird die ursprüngliche Position und Größe gesetzt. Mit lokalen Variablen kann nun die aktuelle Position abgefragt werden, entspricht diese der Position des Contentbereichs, muss das Foto wieder an den ursprünglichen Platz zurück animiert werden, andernfalls ist der Contentbereich zu füllen.
Im abschließenden Schritt muss noch das Storyboard mit den Animationen und den notwendigen Zuweisungen gefüttert werden:
board.Children.Clear();
board.Children.Add(doubleAnimWidth);
board.Children.Add(doubleAnimHeight);
board.Children.Add(doubleAnimTop);
board.Children.Add(doubleAnimLeft);
Storyboard.SetTargetName(
doubleAnimWidth,
picControl.Name);
Storyboard.SetTargetName(
doubleAnimHeight,
picControl.Name);
Storyboard.SetTargetName(
doubleAnimLeft,
picControl.Name);
Storyboard.SetTargetName(
doubleAnimTop,
picControl.Name);
Storyboard.SetTargetProperty(
doubleAnimWidth,
new PropertyPath(PictureControl.WidthProperty));
Storyboard.SetTargetProperty(
doubleAnimHeight,
new PropertyPath(PictureControl.HeightProperty));
Storyboard.SetTargetProperty(
doubleAnimLeft,
new PropertyPath(Canvas.LeftProperty));
Storyboard.SetTargetProperty(
doubleAnimTop,
new PropertyPath(Canvas.TopProperty));
board.Begin(this);
Zuerst werden alle Kindelemente unseres Storyboard namens board mittels Clear gelöscht und alle zuvor definierten Animationen hinzugefügt. Danach muss das Zielelement definiert werden. Dieses wird beim MouseDown-Event durch das Objekt sender präsentiert, worüber wir den Namen des Objektes abfragen und dem Storyboard mittels SetTargetName übermitteln. Hier muss auch die entsprechende Animation als Parameter übergeben werden, damit das Storyboard weiß, welche Animation auf welches Element angewandt werden muss.
Ist dies erledigt, muss dem Storyboard noch mitgeteilt werden, welche Properties durch die Animationen betroffen sind. Hier gilt es nun, ebenfalls pro Animation den PropertyPath des Zielelementes zu definieren.
Schlussendlich wird das Storyboard mittels Begin gestartet.
Fazit
Dies war eine kurze Einführung in die Welt der Animationen unter WPF. Natürlich sind weitaus komplexere Animation möglich. Auf dieser Basis ist es aber möglich, sich weiter in dieses Thema zu vertiefen und verbesserte Lösungen zu schaffen.
Eingaben sollten immer validiert, also auf Korrektheit überprüft, werden, um Fehler bereits frühzeitig abzufangen und so eventuell einen langen Verarbeitungsweg einzusparen. Dieser Artikel zeigt, wie die Eingabe von Datumswerten in Textfelder unter WPF auf Gültigkeit hin überprüft werden können.
Einführung
In der Windows Presentation Foundation gibt es die sogenannten ValidationRules, welche an einem Databinding hängen und darüber auch verwendet werden können. Mit Hilfe dieser ValidationRules können nun Eingaben auf einfache Art und Weise überprüft werden. Es wird jedoch vorausgesetzt, dass der zu prüfende Wert per Databinding an das jeweilige Control gebunden wurde.
Erstellung einer ValidationRule
Das Erstellen einer ValidationRule ist sehr einfach gehalten. Dazu muss von der abstrakten Klasse System.Windows.Controls.ValidationRule abgeleitet werden. Weiters muss die abstrakte Methode Validate überschrieben werden. Diese bekommt als Parameter den geänderten Wert der Eingabe übermittelt, welcher in weiterer Folge überprüft werden muss. Als Ergebnis wird ein Objekt des Typs ValidationResult zurück geliefert. Darin wird vermerkt, ob die Validierung erfolgreich war (oder nicht) und ein Fehlerobjekt (das kann beispielsweise eine Fehlermeldung sein).
Für das hier gezeigte Beispiel wird eine ValidationRule für die Validierung eines Datums implementiert und kann wie folgt aussehen:
publicclass DateTimeValidationRule : ValidationRule
{
publicoverride ValidationResult Validate(
objectvalue,
System.Globalization.CultureInfo cultureInfo)
{
string val = valueasstring;
DateTime dtResult;
bool bRet = DateTime.TryParse(val, out dtResult);
ValidationResult vr = new ValidationResult(bRet, "No valid date");
return vr;
}
}
Damit ist die notwendige ValidationRule erstellt. Da diese jedoch nur auf ein Binding angewendet werden kann, wird noch eine Datenklasse benötigt, welche den entsprechenden Wert an unser Textfeld bindet.
Binding erstellen
Für das Binding wird eine einfache Datenklasse erstellt. Diese enthält nur den angezeigten Datumswert und wird bei der Instanzierung mit dem aktuellen Datum befüllt (siehe Konstruktor).
publicclass MyData
{
privatestring _date;
publicstring Date
{
get { return _date; }
set { _date = value; }
}
public MyData()
{
_date = DateTime.Now.ToShortDateString();
}
}
Der nächste Schritt besteht nun darin, ein Window mit den entsprechenden Feldern und dem Binding zu erstellen.
WPF Window und Bindings erstellen
Für das Fenster der Testanwendung müssen einige kleine Punkte erledigt werden. So ist die Datenquelle anzubinden, eine TextBox zu erstellen und das DataBinding zu setzen. Weiters muss dem DataBinding die ValidationRule bekannt gemacht werden, sowie ein entsprechender Trigger gesetzt werden (PropertyChanged). Letztere Schritt wird benötigt, um Änderungen an der Eingabe zu erkennen und dadurch die Validierung zu starten.
In den Ressourcen für das Window wird im ersten Schritt die Datenquelle für das Binding angegeben. my ist lediglich ein Namespace, welcher auf das aktuelle Projekt verweist, damit die Klasse MyData gefunden werden kann.
Interessant ist der definierte Style, welcher an den Typ TextBox gebunden wird und einen Trigger auf die Eigenschaft Validation.HasError besitzt. Dieser wird also immer dann ausgelöst, wenn die Validierung einen Fehler zurück liefert. Ist dies der Fall, wird die Schriftfarbe gesetzt, als auch der ToolTip befüllt (dieser ist zuständig, um die zurück gelieferte Fehlermeldung anzuzeigen).
Schließlich fehlt noch die Definition des Textfeldes, welches der grundlegende Bestandteil dieser Demo ist.
Es wird deutlich, dass hier sowohl ein ErrorTemplate zugewiesen wird und ein entsprechender Style. Zudem findet ein Binding auf unsere zuvor definierte Datenquelle statt. Dieser wird ein UpdateSourceTrigger zugewiesen, der bei einer Veränderung des eingegebenen Wertes die ValidationRule startet. In diesem Fall DateTimeValidationRule, welche zu Beginn dieses Artikels erstellt wurde.
Der Rest besteht eigentlich nur darin, um die TextBox herum die grundlegenden Bestandteile eines Fensters zu definieren. In diesem Beispiel wurde mit einem Grid gearbeitet, was durchaus auch an der Definition des Textfeldes zu erkennen ist.
Fazit
Mit diesem Beispiel sollte es nun möglich sein, eigene Validierungen für Felder zu implementieren. Es ist durchaus sinnvoll, ein wenig mit dem oben gezeigten Code zu experimentieren und einige Einstellungen etc. zu ändern, um daraus zu lernen.
Karsten Januszewski und Jaime Rodriguez verfassten ein Whitepaper zu XAML, WPF und dem Design-Workflow, welches sehr empfehlenswert für WPF-Interessierte ist.
The New Iteration (PDF)
How XAML Transforms the Collaboration
Between Designers and Developers in Windows Presentation Foundation
Auf 32 Seiten werden Fragen nach der Sinnhaftigkeit von XAML, Rollenverteilung, Workflow und unterschiedlichsten Tools beantwortet. Ebenfalls sind "Best Practices"-Kapitel, sowohl für Designer, als auch Entwickler zu finden.
5. Ausgabe .NET BlogBook - Gewinne ein VSone-Ticket
07.01.08 - .NET, Grundlagen, Base Framework, WPF, ASP.NET, Silverlight, Mobile Devices, Datenverwaltung, Visual Studio Beitrag von Norbert Eder
Die nunmehr fünfte Ausgabe des .NET BlogBooks ist erschienen.
Die aktuelle Ausgabe wurde vor allem in den Bereichen Windows Presentation Foundation, C# 3.0 und Tools erweitert. Auch viele andere Bereiche profitieren von neuen Einträgen. Insgesamt bietet die fünfte Ausgabe nun knapp 320 Seiten Inhalte, um 40 mehr als Ausgabe vier.
Aber nicht nur beim Inhalt hat sich etwas getan. Nein, auch dieses Mal gibt es wieder ein Gewinnspiel. Zu gewinnen gibt es ein Ticket der VSone. Also ran ans Zeug! Downloaden, auf Seite 3 die Teilnahmebedingungen lesen und vielleicht gewinnen.
Im letzten Beitrag wurde die Frage geklärt, wann denn eine Umsetzung mit Hilfe der Windows Presentation Foundation als sinnvoll erscheint und in welchen Fällen dann doch zu den herkömmlichen Windows Forms gegriffen werden soll. Ist eine Entscheidung zugunsten der WPF gefallen, müssen einige Vorarbeiten erledigt werden. Unter anderem betrifft dies das Design der Software selbst. Einige Verhaltensweisen sind dann doch unterschiedlich, was sich unter anderem auch im Softwaredesign auswirkt. Nachfolgend werden diesbezüglich einige Varianten vorgestellt als auch einige allgemeine Punkte behandelt.
Bietet die WPF bereits ein entsprechendes Konzept?
In die Windows Presentation Foundation wurde einiges an Funktionalität und viele neue Konzepte hineingepackt. Auch für das Softwaredesign wurde etwas getan. So ist es wichtig, Businesslogik von der Darstellung zu trennen. Dies wird mit Hilfe der WPF-Commands ermöglicht. Wie der verlinkte Artikel zeigt, stehen mehrere Varianten zur Verfügung. Mal enger mit der UI gekoppelt, mal davon losgelöst.
Für kleinere Projekte mag dies durchaus eine gute Wahl darstellen. Für größere Projekte (beispielsweise von Anwendungen mit einigen Mannmonaten Aufwand) meine ich, dass dieses Konzept nicht vollkommen taugt. Hier bedarf es einer größeren und besseren Lösung.
Das Model-View ViewModel Pattern
Eine wesentlich bessere Lösung wird uns hier mit dem Model-View ViewModel in die Hand gelegt. Es ist dies eine Variante des Model-View-Controller Patterns.
Dies sieht auf den ersten Blick möglicherweise etwas verwirrend aus, tatsächlich gestaltet sich die Verwendung jedoch nicht schwierig. Im Gegensatz zum MVC wird der Controller hier mit einer ModelView ausgetauscht (wobei im Hintergrund weiterhin auch ein Controller seinen Dienst tun kann). Die ModelView besitzt eine enge Kopplung zur View -> DataBinding. Dadurch kann die UI auf sehr einfache Weise aktualisiert werden. Diese enge Bindung muss jedoch nicht an das Model weitergegeben werden. Letzteres hält die einzelnen Entities.
Durch die Trennung in diese einzelnen Bestandteile ist es nun möglich, Daten- und Geschäftsklassen vom User Interface zu trennen. Der Vorteil besteht darin, dass die gesamte Anwendungsarchitektur dadurch flexibler, austauschbarer und vor allem testbarer wird.
Zu diesem Thema gibt es eine Serie von Dan Crevier welche ich dem Leser nicht vorenthalten möchte:
Im WPF-Umfeld stehen derzeit nicht mehr wirklich andere Patterns für die Kommunikation zwischen dem User Interface und den darunter liegenden Schichten zur Verfügung. Natürlich muss hier erwähnt werden, dass selbst das MVVM Anpassungen erfahren kann und im Laufe der Entwicklung an einer eigenen Lösung auch erfahren wird.
Es empfiehlt sich jedoch auf jeden Fall, alle Anforderungen zu definieren und daraus weitere mögliche Patterns, die innerhalb des MVVMs zum Zuge kommen abzuleiten.
Fazit
Immer, wenn neue Technologien oder Konzepte ins Spiel kommen, müssen neue Mittel und Wege gefunden werden, ein Projekt erfolgreich abzuschließen. Das MVVM ist ein erster Schritt in die richtige Richtung. Mit Fortbestand der WPF werden weitere Lösungen, Abwandlungen des MVVMs und weitere Verbesserungen auf uns Entwickler zukommen.
Grundsätzlich empfehle ich, das erwähnte Pattern in kleinem Rahmen zu testen und einen kleinen Prototyp zu entwickeln, um ein erstes Gefühl zu bekommen. Darauf aufbauend können anschließend die ersten ernsthaften Anwendungen entwickelt werden.
Tipp: Setzen Sie sich mit den Eigenheiten der WPF auseinander, um vor unliebsamen Überraschungen geschützt zu sein. Gerade wenn unbekannte Patterns das erste Mal eingesetzt werden, kann es sehr leicht zu Designfehlern kommen, die im weiteren Zuge größere Umstellungen/Refactorings notwendig machen.
WPF: RoutedCommand, RoutedUICommand, ICommand und Commands im Allgemeinen - Eine Einführung
02.01.08 - .NET, Grundlagen, WPF Beitrag von Norbert Eder
Einige mögen bereits wissen, dass unter der Windows Presentation Foundation die Möglichkeit besteht, Commands zu verwenden. Für andere mag es neu sein. Wem ein Command gar nichts sagt, der möge sich meinen Beitrag Patterns: Command Pattern zuvor zu Gemüte führen.
Das Command-System wurde in die Windows Presentation Foundation integriert, um durchzuführende Aufgaben/Tasks besser vom User Interface (Oberfläche) zu trennen.
Warum UI von Aufgaben trennen
Ein oft geschilderter Vorteil der WPF ist, dass das grafische Design nicht zwingend vom Entwickler gemacht werden muss. So kann hier in der Tat ein Designer Hand anlegen (jedoch muss er etwas von XAML verstehen). Das ist jedoch nicht der Punkt, auf den ich hinaus möchte.
Ein Grundsatz der Softwareentwicklung ist es, stabile und robuste Software zu bauen. Dazu muss natürlich getestet werden. Manuelle Tests, die alles abdecken sind aber einer bestimmten Projektgröße kaum durchzuführen. Automatisierte Tests müssen her. Mit Hilfe von Unit Tests können nun Teile der Anwendung getestet werden. Dies funktioniert jedoch nur dann, wenn diese Teile vom grafischen Aufbau komplett getrennt sind. Sinnvoller Weise werden nun sämtliche Aufgaben von der UI getrennt und werden/bleiben so testbar.
Wie kann die WPF hier helfen?
Mit Hilfe der WPF Commands muss hier kein großer Aufwand betrieben werden, um obiges Ziel auch tatsächlich zu erreichen. Nun gibt es jedoch zwei abzudeckende Möglichkeiten:
Commands müssen das UI aktualisieren
Commands können komplett frei von allen UI-Einflüssen ausgeführt werden
Muss der Command mit der Oberfläche interagieren, sind RoutedUICommands genau der richtige Ansatz. Im zweiten Fall ist das Interface ICommand zu implementieren.
Bezüglich der RoutedUICommands ist es nun so, dass durch die WPF bereits einige fertige Implementierungen vorhanden sind:
Der Vorteil liegt darin, dass diese Commands sehr einfach an UIElemente gebunden und ausgeführt werden können. Hier ein kleines (unvollständiges) Beispiel zur Veranschaulichung:
Da diese Commands jedoch ein spezifisches Verhalten anbieten, welches nicht immer benötigt wird, ist es mitunter notwendig, eigene Commands zu implementieren, um die gewünschte Funktionalität zu erhalten.
Eigene Commands via ICommand implementieren
Müssen eigene Funktionalitäten als Command abgebildet werden, kann dies mit Hilfe einer Implementierung des ICommand-Interfaces passieren. Dieses Interface schreibt zwei Methoden vor und ein Event vor:
CanExecute
Execute
CanExecuteChanged
Ein simpler Beispielcommand, welcher ein bestimmtes Objekt entgegeben nimmt und dieses manipuliert, könnte wie folgt aussehen:
publicclass MySaveCommand : ICommand
{
#region ICommand Members
publicbool CanExecute(object parameter)
{
returntrue;
}
publicevent EventHandler CanExecuteChanged;
publicvoid Execute(object parameter)
{
if (parameter is EasySum)
{
EasySum es = (EasySum)parameter;
es.Result = es.Summand1 + es.Summand2;
}
}
#endregion
}
Eine weitere - und vor allem übliche - Möglichkeit besteht darin, einen RoutedCommand zu erstellen.
RoutedCommand erstellen
Mit Hilfe von RoutedCommands ist es sehr einfach, einen Command für das User Interface zu erstellen. Dazu benötigt es nicht sehr viel. Das nachfolgende Beispiel zeigt, wie anhand eines RoutedCommands die Hintergrundfarbe eines Panels verändert werden kann.
Hierzu muss unser neuer Command definiert werden:
publicstatic RoutedCommand ChangeBackgroundCommand = new RoutedCommand();
Des weiteren müssen zwei Eventhandler implementiert werden. Zum einen für das CanExecute-Event und zum anderen für das Executed-Event.
Im Eventhandler für das CanExecute-Event überprüfen wir lediglich, ob das übergebene UIElement auch tatsächlich ein Panel ist. Ist dem so, lassen wir eine Manipulation der Hintergrundfarbe zu. Andernfalls wird eine Ausführung des Commands verwehrt.
Im Eventhandler für das Executed-Event wird dann die Hintergrundfarbe geändert bzw. wieder auf die Ursprungsfarbe zurück gestellt.
Damit der Command nun auch verwendet werden kann, muss ein CommandBinding erstellt werden:
Wie zu sehen ist, wird hier der aufzurufende Command definiert, als auch die beiden Eventhandler, die bereits implementiert wurden. custom definiert hier lediglich einen eigenen Namespace.
Das CommandBinding ist an einem StackPanel definiert, welches ein Label und einen Button enthält, welcher dann schlussendlich den Command auslöst.
Fazit
Dieser Artikel hat das der WPF zugrunde liegende Konzept der Commands vorgestellt und eine kleine Übersicht inklusive einiger Beispiele gegeben. Ebenso wurden die Unterschiede erläutert. Damit sollte nun jeder angehende WPF-Entwickler das notwendigste Wissen mitbringen, um eigene Commands zu entwickeln und diese in seinen Anwendungen einzusetzen. Natürlich wird es durchaus noch notwendig sein, das eine oder andere nachzulesen. Der Start sollte damit allerdings gemacht sein.
Guidelines sind eine gute Sache (sofern diese nicht zu übertrieben sind). Es gibt sie auch für die unterschiedlichsten Sprachen. Wer eine Guideline für XAML und WPF sucht, der wird auf dem Blog von Paul Stovell fündig: