Wenn man heute jemand sein will, muss ein Silverlight Projekt mindestens MVVM drin haben und besonders cool soll ja PRISM oder Unity sein. Als erklärter Pragmatiker, muss ich mir zuerst einmal immer mindestens 30 minütige Ausführungen über mich ergehen lassen, warum das MVxx Ding so wichtig ist. Da fallen Argumente wie Testbarkeit (ich hasse Wörter die mit keit enden), Trennung von UI und Logik, sauberer Code und überhaupt Weltfrieden. In den nächsten 30 Minuten versucht dann mein Gegenüber mich von der Notwendigkeit seines Lieblings MVVM Frameworks zu überzeugen. Weil mit MVVM wird zwar alles einfacher wird, aber man braucht viel mehr Code dafür und eine Design Ansicht haben sowieso nur Luschen. In der Gesamtheit betrachtet eine Lösung für ein Problem, das ich nicht habe. Warum soll es so schrecklich sein 10 Zeilen Code in einer ASPX Seite oder Silverlight Code behind Datei zu haben?
Gar nichts. Wer das so machen möchte kann meist sehr sauberen Code schreiben den andere auch pflegen können. PUNKT.
Trotzdem muss jeder Entwickler mit ein wenig Erfahrung seine Optionen kennen. Und da ist der7das MVVM (Model View ViewModel) Design Pattern eben dabei. Ich will mich auch nicht auf Entwurfsmuster versteifen, nur so viel. Auch Patterns regeln das Leben des Entwicklers nicht. Es ist vielmehr eine gemeinsame Kurzsprache die vieles offen lässt.
Mein aktuelles Silverlight Beispiel ist deswegen auf das absolut notwendigste reduziert. Selbst das Model entfällt (also nur VVM). Üblicherweise gehts im Model um Daten. Brauchen wir für die Basics nicht. Wir werden ein UI schreiben, das keinen Code enthält und eine Klasse (das Viewmodel) die die Jobs erledigt. Die UI hat einen Textbox und einen Button. Bei Click auf den Button wird der Inhalt der Textbox per Messagebox angezeigt.
Meine Klasse wird eine Eigenschaft bekommen und einen Button, der eine Methode aufruft. Die Property die später dem Button zugeordnet wird, muss seit Silverlight 4 das Interface ICommand beinhalten. Das ist eben so. Andere MVxxx Frameworks lösen das durchaus anders.
Public Class page60viewmodel
Public Property Button1() As ICommand
Nun beginnt der eigentliche Trick. In XAML kann man sehr leicht Objekte deklarativ instanzieren. Dies ist auch ein Superfeature, das ich gerne und dauernd benutze.
xmlns:local="clr-namespace:KursSL">
<UserControl.DataContext>
<local:page60viewmodel/>
</UserControl.DataContext>
Das ist nur eine mögliche Deklaration. Man sieht auch öfter das man das in einem Resources Element mit einem Key erledigt und dann das DataContext Attribut zuweist. Es spielt auch keine Rolle auf welcher Hierarchie Ebene man das tut. Üblicherweise ganz oben per Layoutroot oder wie hier gleich im Usercontrol. Und jetzt kann man sehr lässig per Command Attribut an die Eigenschaft Button1 binden.
<Button Content="Button"
Command="{Binding Button1}"
Das entspricht klassischem Databinding in WPF und Silverlight.
Jetzt stellt sich die berechtigte Frage: wo schreibe ich den Code hin, der ausgeführt werden soll? Dazu geht's wieder ab in die Klasse (Viewmodel). Im Konstruktor wird das Event registriert, mit dem Delegate Event. In anderen Frameworks kommen andere Methoden als ActionCommand zur Anwendung. Bei Verwendung von PRISM (eine optionale Bibliothek für WPF und SL) muss ein DelegateCommand erzeugt werden.
Public Sub New()
Me.Button1 = New ActionCommand(AddressOf pressButton1)
End Sub
Also schreiben wir ein ganz wenig Code für den eigentlichen Button Click Event.
Private Sub pressButton1(ByVal obj As Object)
MessageBox.Show("presses" + ????)
End Sub
Damit ist das implementieren eines Commands auch fertig. Ein wichtiger Teil fehlt noch. Wie kommen die UI Daten an die Stelle der drei Fragezeichen. Dafür bekommt die Klasse eben noch eine Eigenschaft. Die Eigenschaft wird dann wieder mit der Binding Syntax im UI an die Eigenschaft der Textbox gebunden. Auch das hat nichts mit Entwurfsmustern zu tun. Das ist seit den Anfängen von WPF so.
Hier also die komplette Seite in XAML
<UserControl x:Class="KursSL.page60"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400"
xmlns:local="clr-namespace:KursSL">
<UserControl.DataContext>
<local:page60viewmodel/>
</UserControl.DataContext>
<Grid x:Name="LayoutRoot" Background="White">
<Button Content="Button" Height="35"
HorizontalAlignment="Left" Margin="114,82,0,0"
Command="{Binding Button1}"
VerticalAlignment="Top" Width="136" />
<TextBox Height="42" HorizontalAlignment="Left" Margin="38,24,0,0"
Text="{Binding Wert,Mode=TwoWay}"
VerticalAlignment="Top" Width="212" />
</Grid>
</UserControl>
Nicht schön aber tut. Ist doch einfach oder? ( Bei nein, nochmal von vorne zu lesen beginnen)
Wieder zurück in der Klasse. Um zwischen der Eigenschaft und der Textbox eine richtig dicke Verbding herzustellen wird Silverlight typisch das Interface INotifyPropertyChanged implementiert. Wäre in diesem Beispiel nicht wirklich nötig, betrachte ich aber als essentiell. Dadurch wird ein Event System etabliert, das bei einer Änderung der Daten allen gebunden Controls mitteilt, das sie die Anzeige aktualisieren sollen. Das ist in asynchronen Szenarien, in denen Silverlight läuft unbedingt nötig. Ein Datagrid das an eine Liste gebunden wird, zeigt die Daten erst an, wenn ein Web Service geantwortet hat. Und mit diesem Binding Mechanismus muss sich der Entwickler nicht ums Timing kümmern. Wenn sich also der Wert ändert, teilt der Setter dem EventSystem mit, welchen Namen (Achtung Case Sensitive Zeichenkette) diese Variable hat. Hier nun das komplette Beispiel.
Imports Microsoft.Expression.Interactivity.Core
Imports System.ComponentModel
Public Class page60viewmodel
Implements INotifyPropertyChanged
Public Event PropertyChanged(ByVal sender As Object,
ByVal e As System.ComponentModel.PropertyChangedEventArgs)
Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
Public Sub New()
Me.Button1 = New ActionCommand(AddressOf pressButton1)
End Sub
Public Property Button1() As ICommand
Private _Wert As String
Public Property Wert() As String
Get
Return _Wert
End Get
Set(ByVal value As String)
_Wert = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Wert"))
End Set
End Property
Private Sub pressButton1(ByVal obj As Object)
MessageBox.Show("presses" + Wert)
End Sub
End Class
Nun kann man sich fragen ob das mit einem einfachen Dreizeiler nicht auch funktioniert. Natürlich. Das ist auch der Haupt Kritik Punkt an MVVM. Der Coding und Logik Aufwand können erheblich steigen. Um diesen Aufwand wieder zu reduzieren, gibt es eben Frameworks wie PRSIM oder MVVMlight.
Meine persönliche Meinung dazu. Code im User Interface ist nicht per se zu verteufeln. Aber es ist Deine Entscheidung. Ich hoffe ich konnte ein wenig Licht ins dunkel bringen.
Ergänzend: man kann Commands auch mit noch weniger Aufwand verwenden. Dazu gibt es einen Artikel hier im Blog.