Diese Folien beschreiben die wichtigsten Informationen rund um das Thema MVVM mit WPF. Dazu gehört ein Vergleich von Frameworks, die Erläuterung von IoC Containern, die Klärung was MVVM ist und vieles mehr.
13. Eigene ViewModel Basisklasse
In aller Regel benötigt man eine Implementierung von INotifyProperty Changed.
Durch das CallerMemberName Attribut kann man sich viel Arbeit sparen.
16. ViewModelBase
Erbt von ObserableObject.
Kann ermitteln ob es sich im Designmodus befindet oder nicht.
Bietet Zugriff auf einen Messenger um Änderungen auch anderen ViewModels bekannt zu
machen.
18. ICommand
Aktionen werden in Form von Objekten gekapselt und können, verbunden mit ihren
Statusinformationen, vielfach weiter verwendet werden.
public interface ICommand
{
bool CanExecute(object parameter);
void Execute(object parameter);
event EventHandler CanExecuteChanged;
}
20. RelayCommands
Implementiert ICommand
Im Gegensatz zum DelegateCommand wird CanExecute
immer dann geprüft wenn sich Änderungen an der UI
ergeben.
D.h. leichte einzusetzen mit höherer Last zur Laufzeit.
Wird von allen MVVM Frameworks bereit gestellt.
21. CompositeCommands
Erlauben es verschiedene Commands „zeitgleich“ auszulösen.
Aktionen werden nur für registrierte Commands ausgeführt deren CanExecute true zurück gibt.
Wird durch Prism bereitgestellt.
var compositeCommand = new CompositeCommand();
var updateCommand = new DelegateCommand(Update).ObservesCanExecute((vm) => vm.WasChanged);
compositeCommand.Register(updateCommand);
var notificationCommand = new DelegateCommand(Notify).ObservesCanExecute((vm) =>
vm.RaiseNotification);
compositeCommand.Register(notificationCommand);
22. Property based Commands
Werden automatisch ausgeführt wenn sich eine Property verändert.
Das Command registriert sich für PropertyChanged der Property und führ die ExecuteMethode
aus wenn eine Änderung vorliegt.
Erlaubt bspw. die Umsetzung komplexer „CalculatedProperties“.
Wird von Prism bereitgestellt.
var command = new DelegateCommand(x => DoSomething(x)).ObservesProperty(() => this.Title);
24. Referenzierung in der View
1. Im Konstruktor auf den DataContext schreiben
◦ Einfach aber fehleranfällig
2. Im Xaml für den DesignTime Data Context registrieren
◦ Weniger fehleranfällig
◦ Macht zwei Registrierungsverfahren notwendig -> eins für Design- und eines für Laufzeit
25. Mehrere Konstruktoren
Man nutzt zwei Konstruktoren, einen mit und einen ohne Parameter.
Der IoC Container bedient nur den mit Parametern -> Somit ist man im Designer aktiv wenn der
parameterlose Konstruktor aufgerufen wurde.
26. Mehrere Konstruktoren
Man nutzt zwei Konstruktoren, einen mit und einen ohne Parameter.
Der IoC Container bedient nur den mit Parametern -> Somit ist man im Designer aktiv wenn der
parameterlose Konstruktor aufgerufen wurde.
Vorteile
- Sehr einfach umzusetzen.
- Der Aufwand bei der Synchronisierung ist recht einfach.
Nachteile
- Es kann schnell zu Fehlern kommen weil schwergewichtige Aktionen zur Designzeit ausgelöst
werden.
27. Mehrere Klassen
Man nutzt zwei Klassen. Eine für die Realimplementierung und eine für die Designdaten.
Im Designer wird nur die Designvariante verwendet.
Vorteile
- Eindeutige Trennung der Belange.
- Es können nicht versehentlich aufwändige Aktionen im Designer ausgeführt werden.
Nachteile
- Die Synchronisation beider ViewModel kann schwierig sein.
29. Designzeit Daten mit MVVM Light
ViewModelBase bietet eine Eigenschaft IsInDesignMode mit der überprüft wird ob man sich im
Designmodus befindet.
30. Designzeit Daten mit MVVM Light
ViewModelBase bietet eine Eigenschaft IsInDesignMode mit der überprüft wird ob man sich im
Designmodus befindet.
Dependency Injection geht in diesem Fall nicht und führt zu einer Exception im Designer.
Demnach braucht man mindestens 2 Konstruktoren.
34. Event to Command Binding
Üblicherweise lassen sich Events nicht and Commands binden.
Mit Interaction Triggers und Behaviours kann man dies umgehen.
Behaviours können selbst erstellt oder in Kombination mit Blend verwendet werden.
InteractionTrigger stammen aus System.Windows.Interactivity.
<i:Interaction.Triggers>
<i:EventTrigger>
<i:InvokeCommandAction Command="{Binding InitializationCommand, Mode=OneTime}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
35. Event to Command Binding
MVVM Light enthält ein entsprechendes Behaviour.
<i:Interaction.Triggers>
<i:EventTrigger>
<Custom:EventToCommand Command="{Binding InitializationCommand, Mode=OneTime}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
xmlns:Custom="http://www.galasoft.ch/mvvmlight"
37. Attached Properties & Behaviors
Sind DependencyProperties mit denen man Elemente im Xaml um Informationen erweitern
kann.
Löst das setzen der Daten auch Aktionen aus, handelt es sich um attached Behaviours.
38. Attached
Properties &
Behaviors
Das zusätzliche Verhalten kann durch
den Changed-Eventhandler der
Property angehangen bzw. ausgelöst
werden (blaue Pfeile).
Wichtig: Es handelt sich nicht um
„normale“ Dependency Properties. Sie
werden als attached registriert (roter
Pfeil).
40. Interactivity
Behaviors
Durch die Basisklasse Behavior wird
genau angegeben welche Funktionen
auf welchen Typ angewendet werden
können.
Dies spart viel Code, ist typsischer und
leichter verständlich als Attached
behaviors.
<TextBox Text="{Binding Title}">
<i:Interaction.Behaviors>
<MVVM:SpecialCharacterFilterBehavior />
</i:Interaction.Behaviors>
</TextBox>
44. View Discovery per Template
Bietet sich vor allem für EntityViewModels an.
Die View wird als Control oder DataTemplate für einen Typ registriert. Dadurch erfolgt die
Zuweisung zur View automatisch.
Durch Template Selector kann man sehr einfach das Template anhand des Status des
ViewModels umschalten.
45. View Discovery per Template
Hat man mehrere Typen die unterschiedlich dargestellt werden soll, gibt man die DataTemplates
mit entsprechender Typedefinition als Ressource an. Die Auswahl geschieht dann automatisch
anhand des Typs.
<ContentControl Content="{Binding SelectedPublication}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type viewModels:PublicationEntityViewModel}">
<local:PublicationDetailsView />
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:MediumEntityViewModel}">
<local:MediumDetailsView />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
47. ViewModel Locator in Prism
Nutzt eine Attached Property.
Sucht im Namespace „ViewModels“ nach einem ViewModel, dass so heißt wie die View.
◦ Bspw: PublicationView braucht ein PublicationViewModel
Die Instanziierung erfolgt per IoC Container sobald eine Navigation mit dem RegionManager
erfolgt.
mvvm:ViewModelLocator.AutoWireViewModel="True"
49. Bootstrapper?!?
Komponente die der Initialisierung der Anwendung zum Programmstart dient.
Typische Schritte sind:
1. Erstellen des IoC Containers und registrieren aller notwendigen Komponenten.
2. Initialisieren der wichtigsten Infrastruktur Komponenten (ViewModelLocator,
DialogManager, Logger).
3. Erstellen und anzeigen des Hauptfensters.
52. Registrieren und starten des
Bootstrappings
Entfernen der StartUpUri aus App.xaml und ersetzen durch
einen Eventhandler für OnLoad.
Instanziieren und starten des Bootstrappings.
63. http://autofac.org/
• Sehr leistungsfähig und umfangreich.
• Bietet viele verschiedene Lebenszyklen und
Einflussmöglichkeiten bei der Erstellung von
Instanzen.
• Erlaubt XML-Konfiguration.
• Bietet Schnittstellen für einfache Isolation von
Bestandteilen.
• Ist Verfügbar als PCL.
• Ist sehr gut dokumentiert.
AutoFac
64. http://lightcore.ch/
• Sehr leichtgewichtig und schnell.
• Erlaubte Lebenszyklen:
• Transient
• Singleton
• Bezogen auf HTTP Request
• Bezogen auf Thread
• Nicht verfügbar als PCL.
• Ist gut dokumentiert.
LightCore
65. http://mef.codeplex.com/
• Ist Teil des .Net Frameworks.
• Bietet die wichtigsten Funktionen zum
Auflösen von Abhängigkeitsgraphen.
• Verwendet häufig Reflection.
• Kein klassischer IoC Container.
• Steht als PCL auch für andere
Anwendungen zur Verfügung.
• Ist sehr gut dokumentiert.
MEF (Managed Extensibility Framework)
66. https://unity.codeplex.com/
• Wurde von Microsoft Patterns & Practices
entwickelt.
• Bietet viele verschiedene Lebenszyklen und
Einflussmöglichkeiten bei der Instanziierung
von Klassen.
• Erlaubt XML-Konfiguration.
• Erlaubt Aspekt-Orientierte-Programmierung
(AOP)
• Ist verfügbar als PCL.
• Ist sehr gut dokumentiert.
Unity
67. • Es gibt sehr viele IoC Container am Markt.
• Diese haben einen unterschiedlichen Funktionsumfang und werden
unterschiedlich gut gepflegt.
• Vor dem Einsatz sollte unbedingt geklärt werden, wie umfangreich die
Dokumentation ist und wann das letzte Update veröffentlicht wurde.
• Wenn Geschwindigkeit wichtig ist:
• sollte auf weniger umfangreiche Frameworks zurück gegriffen
• und auf XML Konfigurationen verzichtet werden.
Zusammenfassung
69. Grundkonzept
Es wird ein Interface erstellt über welches die Navigation gesteuert wird.
Eine oder mehrere Regionen in der Applikation werden als Navigationsziele definiert.
Eine Region kann ein ContentControl (zeigt nur eine View zeitgleich an) oder ein ItemsControl
(zeigt mehrere Views zeitgleich an).
Der NavigationService arbeitet sehr eng mit dem Hauptfenster zusammen und muss daher beim
Bootstrapping gesondert initialisiert werden!
70. MVVM Light INavigationService
public interface INavigationService
{
string CurrentPageKey { get; }
void GoBack();
void NavigateTo(string pageKey);
void NavigateTo(string pageKey, object parameter);
}
Es gibt keine Implementierung für WPF!
Es kann nur eine Region verwaltet werden!
Die Navigation erfolgt im Stil einer „Kioskapp“
71. Prism RegionManager
Verschiedene Regionen können verwaltet werden.
Es kann geprüft werden ob Navigation möglich ist.
ViewModels können selbst die Navigation verhindern.
Parameterübergabe ist möglich.
Es können jederzeit Regionen registriert und entfernt werden.
Regionen können Views verwalten die selbst Regionen enthalten.
73. Nutzung von DataTemplates
und Data Triggers
Status basierte Navigation ändert die Anzeige anhand des
Status des ViewModels.
Es gibt demnach zwei unterschiedliche Darstellungen für das
gleiche ViewModel.
Dazu werden verschiedene DataTemplates festgelegt und per
DataTrigger, VisualStateManager oder TemplateSelector
zwischen diesen umgeschaltet.
75. Validation Rules
Regelsätze werden eigenständig als Objekte bereit gestellt und an die Controls gebunden.
Sie sind somit nicht mehr Teil des ViewModels sondern liegen in der Verantwortung der View.
ValidationRules erben von der Klasse ValidationRule.
public class NotNullOrEmptyValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
if (value == null || string.IsNullOrWhiteSpace(value.ToString()))
{
return new ValidationResult(false, $"No value was set for {PropertyName}.");
}
return ValidationResult.ValidResult;
}
public string PropertyName { get; set; }
}
76. Validation Rules
Regelsätze werden eigenständig als Objekte bereit gestellt und an die Controls gebunden.
Sie sind somit nicht mehr Teil des ViewModels sondern liegen in der Verantwortung der View.
ValidationRules müssen als embedded collection im Xaml angegeben werden.
Sie haben nur sehr wenig Informationen über ihren Kontext!
<TextBox>
<TextBox.Text>
<Binding Path="Title" UpdateSourceTrigger="PropertyChanged" >
<Binding.ValidationRules>
<MVVM:NotNullOrEmptyValidationRule PropertyName="Title" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
77. Validierung mit Exceptions
Validierung geschieht auf Basis eines Bindings.
Wird eine Exception geworfen, gilt die Validierung automatisch als fehlgeschlagen.
Es handelt sich um eine sehr teure Art der Validierung und sollte daher möglichst vermieden
werden.
<TextBoxText="{Binding PublicationDate,
ValidatesOnExceptions=True,
UpdateSourceTrigger=PropertyChanged}" />
78. IDataErrorInfo
Definiert Eigenschaften um Fehlermeldungen für einzelne Properties oder das gesamte Objekt
abzurufen.
/// <summary>Provides the functionality to offer custom error information that a user interface can bind to.</summary>
public interface IDataErrorInfo
{
/// <summary>Gets the error message for the property with the given name.</summary>
/// <param name="columnName">The name of the property whose error message to get. </param>
/// <returns>The error message for the property. The default is an empty string ("").</returns>
string this[string columnName] { get; }
/// <summary>Gets an error message indicating what is wrong with this object.</summary>
/// <returns>An error message indicating what is wrong with this object. The default is an empty string ("").</returns>
string Error { get; }
}
79. IDataErrorInfo
Um Validierungen vorzunehmen und einen Validierungsrahmen zu erhalten, muss beim Binding
ValidatesOnDataErrors auf True gesetzt werden.
<TextBox Text="{Binding Title, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
public string this[string columnName]
{
get
{
if (columnName == GetPropertyName(() => Title) && string.IsNullOrEmpty(Title))
{
return "No title was given.";
}
return string.Empty;
}
}
Kommt aus dem ObserveableObject von
MVVM Light
80. IDataErrorInfo
Vorteile:
Integriert in WPF wodurch Validierungshinweise direkt an den Fehlerstellen angezeigt werden.
Nachteile:
Hat eine Property keinen Wert, wird keine Validierung vorgenommen!
Die Validierung geschieht synchron zur Eingabe, man kann sie nur sehr schwer steuern.
Properties werden schrittweise validiert, was bei Calculated Properties zu erheblichen
Problemen führen kann.
81. DataAnnotations
Über Annotationen kann für jede Property angegeben werden wie sie validiert werden soll.
Diese Annotations sind in System.ComponentModel.DataAnnotations definiert. Dazu muss diese
Dll referenziert werden!
Es gibt eine sehr große Zahl unterschiedlicher Annotationen.
[Required(AllowEmptyStrings = false, ErrorMessage = "Please enter valid title.")]
public string Title
{
get { return publication.Title; }
set { publication.Title = value; }
}
82. DataAnnotations
Die Auswertung kann im Rahmen von IDataErrorInfo mit Hilfe der Validator-Klasse für jede
Property geschehen.
protected virtual string Validate(string propertyName)
{
var error = string.Empty;
var value = GetPropertyValue(propertyName, this);
var results = new List<ValidationResult>(1);
var context = new ValidationContext(this, null, null) { MemberName = propertyName };
var result = Validator.TryValidateProperty(value, context, results);
if (!result)
{
var validationResult = results.First();
error = validationResult.ErrorMessage;
}
SetError(propertyName, error);
return error;
}
83. DataAnnotations
Über die Validator-Klasse kann aber auch ein komplettes Objekt Validiert werden.
public virtual bool Validate(object instance)
{
var validationContext = new ValidationContext(instance);
var validationResults = new List<ValidationResult>();
var isValid = Validator.TryValidateObject(instance, validationContext, validationResults);
SetErrors(validationResults);
return isValid;
}
84. INotifyDataErrorInfo
Definiert Eigenschaften um Validierung asynchron vorzunehmen.
Statt, dass direkt validiert wird, werden Fehler erst nach einem Event angezeigt.
Pro Property sind mehrere Fehlermeldungen möglich.
public interface INotifyDataErrorInfo
{
/// <summary>Gets a value that indicates whether the entity has validation errors. </summary>
/// <returns>true if the entity currently has validation errors; otherwise, false.</returns>
bool HasErrors { get; }
/// <summary>Gets the validation errors for a specified property or for the entire entity.</summary>
/// <returns>The validation errors for the property or entity.</returns>
IEnumerable GetErrors(string propertyName);
/// <summary>Occurs when the validation errors have changed for a property or for the entire entity.
event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
}
85. INotifyDataErrorInfo
Wenn validiert werden soll, wird zunächst HasErrors geprüft.
Dies triggert einen Zugriff auf GetErrors falls HasErrors true ist.
Mit ErrorsChanged kann der Vorgang indirekt von außen gestartet werden.
public interface INotifyDataErrorInfo
{
/// <summary>Gets a value that indicates whether the entity has validation errors. </summary>
/// <returns>true if the entity currently has validation errors; otherwise, false.</returns>
bool HasErrors { get; }
/// <summary>Gets the validation errors for a specified property or for the entire entity.</summary>
/// <returns>The validation errors for the property or entity.</returns>
IEnumerable GetErrors(string propertyName);
/// <summary>Occurs when the validation errors have changed for a property or for the entire entity.
event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
}
<TextBox Text="{Binding Title, ValidatesOnNotifyDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
86. Validierungsinformationen in Xaml
abrufen
Jedes UI Element hat Zugriff auf Validierungsinformationen.
Dies kann genutzt werden um das Control entsprechend zu stylen.
<TextBox Text="{Binding Title, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource ErrorStyle}">
<Style x:Key="ErrorStyle" TargetType="FrameworkElement">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors).CurrentItem.ErrorContent}" />
</Trigger>
</Style.Triggers>
</Style>
101. Logging mit
Visual Studio
In den Debugoptionen kann
eingestellt werden, welche Dinge
während des Debugs in die
OutputView ausgegeben werden.
102. Debugging mit PresentationTraceSources
WPF ermöglicht das „Logging“ in die Console über attached Properties.
Logging wird nur auf bestimmte Elemente angewendet.
Dabei steht der genaue Wert für komplexe Datentypen jedoch nicht zur Verfügung!
<TextBox Text="{Binding PublicationDate, PresentationTraceSources.TraceLevel=High}" />
104. Debugging mit
Convertern
Problem
Man kann nicht ohne weiteres in Xaml
debuggen oder loggen.
Lösung
Man definiert einen Converter der C#
Code in das Xaml „injiziert“.
106. Dependency Properties
Nutzt man wenn:
- Man eine Eigenschaft gegen andere Eigenschaften binden möchte.
- Wenn Animationen umgesetzt werden sollen.
- Wenn sie in Styles berücksichtigt werden sollen. Bspw. DataTrigger
- Wenn sie mit einer Ressource versehen werden sollen.
107. Dependency Properties
public static readonly DependencyProperty SetTextProperty =
DependencyProperty.Register("SetText", typeof(string), typeof(MyObject), new
PropertyMetadata("", new PropertyChangedCallback(OnSetTextChanged)));
public string SetText
{
get { return (string)GetValue(SetTextProperty); }
set { SetValue(SetTextProperty, value); }
}
Dependency Properties werden üblicherweise auf DependencyObjects und damit in
CustomControls oder UserControls verwendet.
Namenspattern: [Name]Property
Der Tatsächliche Name wird bei der Registrierung als Parameter angegeben. Die Werte werden
über Get und Set des DependencyObjects gesetzt.