Im 2. Teil meiner drei-teiligen Reihe über das MVVM Pattern hab ich euch die Komponenten und deren Zusammenspiel vorgestellt.
Nun möchte ich dazu übergehen wie das ViewModel mittels Commands mit der Oberfläche kommuniziert. Die WPF bietet dafür das ICommand Interface an, welche aus folgenden Komponenten besteht.
{
event EventHandler CanExecuteChanged;
bool CanExecute(object parameter);
void Execute(object parameter);
}
Jedes Command muss nun diese Methoden implementieren und in der Execute-Methode findet dann die Magie statt, die das Command darstellen soll.
Ich habe in meinem Projekt eine kleine Simulation gewählt die beim Klick auf den Button “Simulation Starten” ausgeführt werden soll. Vorher möchte ich euch jedoch die konkrete Implementation meines Commands demonstrieren, es ist nicht meinen Gehirnwindungen entsprungen sondern basiert auf dem Standard-RelayCommand von Josh Smith seinem MVVM-Artikel.
{
#region Events
public event EventHandler CanExecuteChanged;
#endregion
#region Fields
readonly Action<object> execute;
readonly Predicate<object> canExecute;
#endregion // Fields
#region Constructors
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
this.execute = execute;
this.canExecute = canExecute;
}
#endregion
#region ICommand Members
public bool CanExecute(object parameter)
{
return canExecute == null ? true : canExecute(parameter);
}
public void RaiseCanExecuteChanged()
{
if (CanExecuteChanged != null) CanExecuteChanged(this, EventArgs.Empty);
}
public void Execute(object parameter)
{
execute(parameter);
}
#endregion
}
Diese RelayCommand nutze ich jetzt in meiner PersonVMRepository-Klasse um meine Simulation zu erstellen.
{
#region Properties
private Timer timer;
private Random randomPersonVM = new Random();
private ObservableCollection<PersonVM> _personVmCollection;
public ObservableCollection<PersonVM> Personen
{
get
{
return this._personVmCollection;
}
private set
{
if (this._personVmCollection != value)
{
this._personVmCollection = value;
this.OnPropertyChanged(() => this.Personen);
}
}
}
private PersonVM _currentPerson;
public PersonVM CurrentPerson
{
get
{
return this._currentPerson;
}
set
{
if (this._currentPerson != value)
{
this._currentPerson = value;
this.OnPropertyChanged(() => this.CurrentPerson);
}
}
}
private RelayCommand timerCommand;
public ICommand SimulationCommand
{
get
{
if (this.timerCommand == null)
this.timerCommand = new RelayCommand(a => this.timer.Start());
return this.timerCommand;
}
}
#endregion
public PersonRepositoryVM(IEnumerable<PersonVM> personViewModels)
{
if (personViewModels == null)
throw new ArgumentNullException("personViewModels");
this._personVmCollection = new ObservableCollection<PersonVM>(personViewModels);
if (this._personVmCollection.Count != 0)
this.CurrentPerson = this._personVmCollection.FirstOrDefault();
this.timer = new Timer(1000);
this.timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
}
private void timer_Elapsed(object sender, ElapsedEventArgs e)
{
this.CurrentPerson = this.Personen[this.randomPersonVM.Next(0, this.Personen.Count)];
this.CurrentPerson.IsOnline = !this.CurrentPerson.IsOnline;
}
#region IDisposable Member
public void Dispose()
{
this.timer.Dispose();
}
#endregion
}
Wichtig ist hier das SimulationCommand. Diese Command-Objekt stellt unsere Simulation dar. An dieser Stelle wird nichts weiter gemacht als ein neues RelayCommand Objekt mit dem Aufruf der Timer.Start() Funktion zu erstellen. Dieses Command können wir nun an die GUI binden. Das geschieht wie folgt:
Grid.Row="1"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Content="Simulation Starten"
Margin="10"
Command="{Binding SimulationCommand}"/>
Sobald nun der Button gedrückt wird, wird der Timer gestartet und die Execute-Methode des Commands wird ausgeführt womit die CurrentPerson einen neuen Online-Status bekommt. Ohne das ich dafür in der GUI auch nur einmal auf die Listbox zugreifen musste.
Da merkt man schon einen Vorteil von Commands: Man kann Objekte schaffen welche eine Aktion in der GUI darstellen sollen, durch die Bindung der einzelnen Eigenschaften an die GUI wird diese automatisch aktuallisiert ohne das ich in meinem Command die GUI in irgend einer Form kenne.
Anbei möchte ich euch den Quellcode zur Verfügung stellen. Das Editieren einer Person habe ich jetzt aus Zeitgründen rausgenommen, weil ich euch nicht so lange auf den letzten Teil warten lassen wollte.
Im 1. Teil habe ich euch kurz erklärt wie die MVVM im groben aufgebaut ist. Jetzt ist sicherlich auch interessant zu wissen wie diese Komponenten zusammenspielen. Dafür möchte ich euch zuerst das einfach Model präsentieren:
Model:
{
private static readonly string[] FIRSTNAMES ={"Bernd","Thomas","Jens",
"Christopher","Hannes","Dietmar",
"Hans","Hermes","Sebastian",
"Emil","Johannes","Erwin"};
private static readonly string[] LASTNAMES = {"Kruczek","Hansen","Polanski",
"Mueller","Schulze","Meier",
"Schneider","Richter","Lange",
"Neumann","Wagner","Koch"};
#region Properties
public bool IsOnline { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
#endregion
private Person(bool isOnline,string firstName,string lastName)
{
this.IsOnline = isOnline;
this.FirstName = firstName;
this.LastName = lastName;
}
/// <summary>
/// Generates sequence of persons by the given count.
/// </summary>
/// <param name="countOfPersonsToCreate">Amount of persons to create.</param>
/// <returns></returns>
public static IEnumerable<Person> CreatePersonRepository(uint countOfPersonsToCreate)
{
Random isOnlineRandom = new Random();
Random firstNameRandom = new Random();
Random lastNameRandom = new Random();
List<Person> personenList = new List<Person>();
for (int i = 0; i < countOfPersonsToCreate; i++)
{
bool isOnline = Convert.ToBoolean(isOnlineRandom.Next(0, 2));
string firstName = Person.FIRSTNAMES[firstNameRandom.Next(0, Person.FIRSTNAMES.Length)];
string lastName = Person.LASTNAMES[lastNameRandom.Next(0, Person.LASTNAMES.Length)];
Person person = new Person(isOnline, firstName, lastName);
yield return person;
}
}
}
Ich habe aus Demonstrationszwecken mit Absicht einen privaten Ctor gewählt, denn die Personen sollen nur über die eine statische Funktion erzeugt werden können.
An dem Model ist jetzt noch nichts außergewöhnliches zu erkennen.
Das zugehörige XAML dafür sieht wie folgt aus:
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MvvmDemo"
xmlns:Microsoft_Windows_Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
Title="MainWindow" Height="369" Width="671">
<Window.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FF838383" Offset="0" />
<GradientStop Color="#FFA3E809" Offset="1" />
</LinearGradientBrush>
</Window.Background>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="222*" />
<RowDefinition Height="108*" />
</Grid.RowDefinitions>
<ListBox x:Name="personListBox" ItemTemplate="{StaticResource PersonListItemTemplate}"
ItemsSource="{Binding Personen}"
SelectedItem="{Binding CurrentPerson}" Grid.Row="0" />
<Button x:Name="btn_startSimulation"
Grid.Row="1"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Content="Simulation Starten"
Margin="10"
Command="{Binding SimulationCommand}"/>
<Button x:Name="btn_EditPerson"
Grid.Row="1"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Margin="10"
Command="{Binding EditCommand}"
Content="Bearbeiten"
IsEnabled="{Binding CanEdit}"/>
</Grid>
</Window>
Wie ihr seht ist das nichts weiter als eine Listbox und zwei Buttons. Für die Listbox gibt es noch ein DataTemplate, was ihr hier seht:
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MvvmDemo.Assets.Converter">
<DataTemplate x:Key="PersonListItemTemplate">
<DataTemplate.Resources>
<local:StatusToImageConverter x:Key="StatusToImageConverter"/>
</DataTemplate.Resources>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding IsOnline,Converter={StaticResource StatusToImageConverter}}" Stretch="None" Margin="5"/>
<TextBlock Text="{Binding FirstName}" Margin="2" FontWeight="Bold"/>
<TextBlock Text="{Binding LastName}" Margin="2" FontWeight="Bold"/>
</StackPanel>
</DataTemplate>
</ResourceDictionary>
Jetzt kommt das eigentlich wichtigste an der Sache, das ViewModel und die ViewModelBase Klasse.
ViewModelBase:
{
#region INotifyPropertyChanged Member
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged<TResult>(Expression<Func<TResult>> propertyExpression)
{
if (!this.CheckExpressionForMemberAccess(propertyExpression.Body))
throw new ArgumentException("propertyExpression",
string.Format("The expected expression is no ‘MemberAccess’; its a ‘{0}’",propertyExpression.Body.NodeType));
if (propertyExpression == null)
throw new ArgumentNullException("propertyExpression");
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(this.GetPropertyNameFromExpression(propertyExpression)));
}
private bool CheckExpressionForMemberAccess(System.Linq.Expressions.Expression propertyExpression)
{
return propertyExpression.NodeType == ExpressionType.MemberAccess;
}
private string GetPropertyNameFromExpression<TResult>(System.Linq.Expressions.Expression<Func<TResult>> propertyExpression)
{
System.Linq.Expressions.MemberExpression memberExpression = (System.Linq.Expressions.MemberExpression)propertyExpression.Body;
if (memberExpression != null)
{
return memberExpression.Member.Name;
}
else
throw new ArgumentException("propertyExpression");
}
#endregion
}
Diese Klasse implementiert das bereits Angekündigte INotifyPropertyChanged Interface.
Im nächsten Schritt habe ich ein ViewModel für eine einzelne Person angelegt.
PersonVM:
{
#region Properties
private Person _person;
public bool IsOnline
{
get { return this._person.IsOnline; }
set
{
if (this._person.IsOnline != value)
{
this._person.IsOnline = value;
this.OnPropertyChanged(() => this._person.IsOnline);
}
}
}
public string FirstName
{
get { return this._person.FirstName; }
set
{
if (this._person.FirstName != value)
{
this._person.FirstName = value;
this.OnPropertyChanged(() => this._person.FirstName);
}
}
}
public string LastName
{
get { return this._person.LastName; }
set
{
if (this._person.LastName != value)
{
this._person.LastName = value;
this.OnPropertyChanged(() => this._person.LastName);
}
}
}
#endregion
#region ctor
public PersonVM(Person person)
{
if (person != null)
this._person = person;
}
#endregion
public static IEnumerable<PersonVM> CreatePersonVMsForRepository(IEnumerable<Person> persons)
{
foreach (var person in persons)
yield return new PersonVM(person);
}
}
Dieses ViewModel implementiert die einzelnen Komponenten der Person nochmalig und leitet die Änderungen entweder an die GUI weiter oder an die private Person Variable.
Weiterhin habe ich eine PersonRepositoryVM Klasse angelegt welche eine Sammlung von allen Personen und der Momentan ausgewählten Person in der Listbox darstellen soll. Diese Klasse sieht wie folgt aus(Bitte ignoriert vorerst die Commands, auf die möcht ich im letzten Teil eingehen).
PersonRepositoryVM:
private Timer timer;
private Random randomPersonVM = new Random();
private ObservableCollection<PersonVM> _personVmCollection;
public ObservableCollection<PersonVM> Personen
{
get
{
return this._personVmCollection;
}
private set
{
if (this._personVmCollection != value)
{
this._personVmCollection = value;
this.OnPropertyChanged(() => this.Personen);
}
}
}
private PersonVM _currentPerson;
public PersonVM CurrentPerson
{
get
{
return this._currentPerson;
}
set
{
if (this._currentPerson != value)
{
this._currentPerson = value;
this.OnPropertyChanged(() => this.CurrentPerson);
}
}
}
private RelayCommand timerCommand;
public ICommand SimulationCommand
{
get
{
if (this.timerCommand == null)
this.timerCommand = new RelayCommand(a => this.timer.Start());
return this.timerCommand;
}
}
private RelayCommand editCommand;
public ICommand EditCommand
{
get
{
if (this.editCommand == null)
this.editCommand = new RelayCommand(a => this.timer.Stop()
, p => this.CurrentPerson != null);
return this.editCommand;
}
}
public bool CanEdit
{
get
{
return this.EditCommand.CanExecute(null);
}
}
#endregion
public PersonRepositoryVM(IEnumerable<PersonVM> personViewModels)
{
if (personViewModels == null)
throw new ArgumentNullException("personViewModels");
this._personVmCollection = new ObservableCollection<PersonVM>(personViewModels);
if (this._personVmCollection.Count != 0)
this.CurrentPerson = this._personVmCollection.FirstOrDefault();
this.timer = new Timer(1000);
this.timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
}
private void timer_Elapsed(object sender, ElapsedEventArgs e)
{
this.CurrentPerson = this.Personen[this.randomPersonVM.Next(0, this.Personen.Count)];
this.CurrentPerson.IsOnline = !this.CurrentPerson.IsOnline;
}
#region IDisposable Member
public void Dispose()
{
this.timer.Dispose();
}
#endregion
}
Wie ihr seht existiert in dieser Klasse eine ObservableCollection
Wie wird das ganze nun mit der Oberfläche verbunden?
In dem Mainwindow passieren eigentlich nur drei Schritte:
MainWindow:
{
public MainWindow()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(MainWindow_Loaded);
this.Closing += new System.ComponentModel.CancelEventHandler(MainWindow_Closing);
}
void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
var personRepVM = this.DataContext as PersonRepositoryVM;
personRepVM.Dispose();
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
var personsList = Person.CreatePersonRepository(10).ToList();
var personVMs = PersonVM.CreatePersonVMsForRepository(personsList).ToList();
PersonRepositoryVM repository = new PersonRepositoryVM(personVMs);
this.DataContext = repository;
}
}
Sobald das Fenster geladen wurde, lade ich alle Personen, mache aus diesen Personen die zugehörigen PersonenViewModels, und übergebe diese Liste von PersonenViewModels an das Repository welches zum Schluss als DataContext des Fensters gesetzt wird. Über die Eigenschaften welche ich in der GUI gebunden habe, habe ich nun Zugriff auf das ViewModel. Alle Änderungen am Model, wie die Änderung des Status werden an die Oberfläche durch das Event PropertyChanged weitergeleitet, und meine Oberfläche aktuallisiert sich.
Soweit so gut, im nächsten Teil werde ich nochmal kurz auf die Commands eingehen und euch den Quellcode zur Verfügung stellen.
Ich will euch präsentieren wie man eine WPF-Applikation mit dem Model – View – ViewModel Pattern umsetzt. Ich habe das ganze in mehrere Teile aufgebaut und will heute mit dem ersten Teil beginnen.
Viele kennen das MCV-Pattern welches uns ermöglicht die Oberflächenlogik von der Geschäftslogik zu trennen und so eine skalierbare Anwendung zu schaffen. Mit WPF wurde ein neues Pattern eingeführt was den Controller in dem Sinne wegfallen lässt und ein anderes Model dazwischen schiebt. Das ViewModel. Das ViewModel dient als Brücke zwischen Oberfläche und dem Model. Das ViewModel beinhaltet das Model und leitet die Änderungen an die Oberfläche weiter und empfängt diese. Somit ist das Model immer auf dem neusten Stand. Hierfür implementiert das ViewModel das Interface INotifyPropertyChanged.
Diese Interface bietet ein Event an, welches Änderungen am Model signalisieren soll. Mit diesem Event arbeit die WPF dann um die Änderungen an den Elementen zu publizieren.
Damit die Oberfläche das Model darstellen kann, bindet es nicht etwa das Model an die Oberflächenelemente, sondern die Eigenschaften des ViewModels,welches die nötigen Eigenschaften des Model ,nach außen für die Oberfläche, kapselt. Doch warum sollte man jetzt noch eine Ebene dazwischen schieben? Zum einen hat das den Vorteil das man nun Oberfläche und Model komplett kapseln kann und die Oberfläche unabhängig von dem Model agiern kann, denn das ViewModel kommuniziert zwischen beiden. Weiterhin brauch man im ViewModel keine Referenz auf die Oberfläche, sondern das ViewModel wird an die Eigenschaften der Oberfläche gebunden. Für WPF hat das die Vorteile das man sich nicht darum kümmern muss die Oberfläche zu aktualisieren, denn die Datenbindungsinfrastruktur macht das für einen automatisch.
Soviel zu einer kurzen Einführung. Im nächsten Artikel zeig ich euch dann wie man ein einfaches ViewModel aufsetzt und die Eigenschaften an die Oberfläche bindet und mit INotifyPropertyChanged die Daten aktualisieren lässt.
Sharepoint 2010 – Ribbon anpassen
Wenn ihr wie in dem Blogeintrag von Fabian Moritz: Das Ribbon anpassen. Und dieses dann Debuggt um zu testen, dann läuft das beim ersten Debuggdurchgang ohne Probleme. Wenn ihr nun aber an der XML-Datei was ändert, und erneut debuggt so wird es nicht funktionieren und man hat das Gefühl die Anwendung nutzt die alte XML-Datei.
Lösung: Ihr müsst nach jedem Debuggdurchgang den Cache von eurem Browser leeren!!! Sehr Wichtig!
Um eine SPRoleDefinition im SPWeb zu finden gibt es zwei Wege. Der eine Weg ist es über den Namen der Rolle zu machen, ungefähr so:
Wenn wir uns jetzt aber auf einem Englischen Sharepoint befinden, wird diese Variante mit Sicherheit eine Exception auslösen weil die Rolle nicht gefunden werden kann. Lösung für diese Problem sieht so aus:
Äquivalente Rolen im deutschen SharePoint für die SPRoleTypes sind:
SPRoleType.Reader -> Lesen
SPRoleType.Contributer -> Mitwirken
SPRoleType.Administrator -> Vollzugriff
SPRoleType.Guest -> Besucher SPRoleType.None -> Keine
SPRoleType.WebDesigner -> Entwerfen


