MVVM und SelectionChanged

Mit den Expression Blend Behaviors SDK in Windows 8.1 sind nun auch komplexere Befehle im Viewmodel möglich, ohne sich groß über ein ICommand verrenken zu müssen. Das Relaycommand funktioniert ohnehin nur mit wenigen Steuerelementen die ein Command Attribut bieten. Ein Standard Anwendungsfall ist eine Liste, egal ob als ListView, GridView oder DropDown. Eine veränderte Auswahl eines Eintrags soll im Viewmodell etwas auslösen. Dazu wird eine gewöhnliche Mehode (hier loeschen) angelegt. Nach meinen Versuchen benötigt diese idente Methodensignatur, wie in der Code Behind Methode. In meiner Erinnerung funktionierte das in Silverlight auch mit einfachen Eventargs. Das wirft allerdings in WinRT 8.1 ein böse Laufzeitfehlermeldung. Da die Listview eine Auswahlliste ist, wird der gewählte Eintrag den AddedItems hinzugefügt, der hier gleich wieder gelöscht wird. Durch die Oberservable Collection der Bananen verschwindet der Eintrag auch im User Interface der Windows Store App. 1: Public Class bananeVM 2: .... 3: Public Sub loeschen(sender As Object, args As SelectionChangedEventArgs) 4: If args.AddedItems.Count > 0 Then 5: bananen.Remove(args.AddedItems(0)) 6: End If 7: 8: End Sub 9: Public Property bananen As ObservableCollection(Of banane) .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Im XAML wird dann über CallMethodAction die Methode ausgeführt. Das ViewModel ist an die Page gebunden. Folglich ist das Ziel des Binding, das Viewmodel der Page. 1: <Page.DataContext> 2: <local:bananeVM/> 3: </Page.DataContext> 4:   5: .... 6: <ListView x:Name="liste1" 7: ItemsSource="{Binding bananen}" > 8: <ListView.ItemTemplate> 9: ... 10: </ListView.ItemTemplate> 11: <Interactivity:Interaction.Behaviors> 12: <Core:EventTriggerBehavior EventName="SelectionChanged"> 13: <Core:CallMethodAction MethodName="loeschen" 14: TargetObject="{Binding}" > 15: </Core:CallMethodAction> 16: </Core:EventTriggerBehavior> 17: </Interactivity:Interaction.Behaviors> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Dies ist ein gutes Beispiel für, sieht nach MVVM aus und passt trotzdem nicht ganz. Im Viewmodel gibt es mit dem Parameter SelectionChangedEventArgs einen Abhängigkeit zum Steuerelement der Sicht. Im nächsten Schritt wird diese Abhängigkeit in der Methode entfernt. Um den Status zu halten wird dann ein Property im ViewModel benötigt das den selektierten Listeneintrag enthält. 1: Public Class bananeVM 2: Implements INotifyPropertyChanged 3: Public Sub loeschen() 4: If selectedBanane > -1 Then 5: bananen.RemoveAt(selectedBanane) 6: End If 7: End Sub 8:   9: Public Property selectedBanane() As Integer .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Hinweis: da XAML und damit die Listview zuerst gerendert wird, entsteht einen potentielle schwer zu entdeckende Fehlerquelle im selektierten Eintrag. Dieser muss auf –1 ( z.B im Konstruktor des ViewModels) gesetzt werden, da die Liste vorerst noch leer ist. Der XAML Teil muss dann um das Binding auf die Property erweitert werden. 1: <ListView x:Name="liste1" ... 2: SelectedIndex="{Binding selectedBanane,Mode=TwoWay}" .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

Windows 8.1 DataTrigger

Was gehört ins Userinterface und was in die Daten? Am folgenden Beispiel an einer reifenden Banane soll das diskutiert werden. Der Reifegrad ist eine Eigenschaft des ViewModel, das wiederum auf das Model zurück greift. Der Entwickler definiert so einen Integer für Reifegrad und implementiert das Interface INotifypropertyChanged um das UI über Änderungen informieren zu können. 1: Public Class bananenVM 2: Property banane As banane 3: Public Sub New() 4: Me.banane = New banane 5: End Sub 6: End Class 7:   8: Public Class banane 9: Implements INotifyPropertyChanged 10:   11: Private _reifegrad As Integer 12: Public Property reifegrad() As Integer 13: Get 14: Return _reifegrad 15: End Get 16: Set(ByVal value As Integer) 17: If _reifegrad <> value Then 18: _reifegrad = value 19: RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("reifegrad")) 20: End If 21: End Set 22: End Property 23: Public Event PropertyChanged(sender As Object, e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged 24: End Class .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Hinweis: ab .net 4 wird man in den Beispielen eine Hilfsmethode onPropertyChanged finden, die das CompilerAttribut CallerMemberName nutzt. Oft findet man diese Funktion in einer Basisklasse gebündelt BindableBase. Um das Beispiel so einfach wie möglich zu halten, wird darauf verzichtet. Nun sollen abhängig vom Reifewert unterschiedliche Bananen angezeigt werden. Gesteuert wird das für Demozwecke über einen Slider. Wie stellt man diese Bananen nun dar? Man könnte ein Image nehmen und dessen Source Eigenschaft an den Value des Sliders binden. Um aus Integer  ImageSource zu machen, wird ein Konverter benötigt. Dann bestimmt aber der Entwickler wesentliche Bestandteile der Visualisierung (Typ und Bild). Dies gehört aber in die Hände des Designers (ich weis das bist Du auch und Marketing und Vertrieb und und). In WIndows 8.1 wird für die WinRT API nun ein Behaviors SDK mitgeliefert. Damit lassen sich im XAML deklarativ Verhalten und Aktionen festlegen. Am einfachsten geht das mit Expression Blend. Um das gewünschte Verhalten zu erreichen, wird ein Data Trigger angelegt, der bei einem bestimmten Schwellwert eine Aktion auslöst oder einen anderen Wert verändert. Man kann also bei erreichen des Schwellwertes das Bild austauschen. Der XAML Code dafür ist selbsterklärend. Das Viewmodel wird an den Datacontext der Page gebunden. Für jeden Schritt wird ein neuer DataTriggerBehavior benötigt. 1: <Slider HorizontalAlignment="Left" Margin="349,57,0,0" VerticalAlignment="Top" Width="100" 2: Value="{Binding banane.reifegrad, Mode=TwoWay}"> 3: <Interactivity:Interaction.Behaviors> 4: <Core:DataTriggerBehavior Binding="{Binding banane.reifegrad}" 5: ComparisonCondition="GreaterThan" Value="7"> 6: <Core:ChangePropertyAction TargetObject="{Binding ElementName=image1}" 7: PropertyName="Source" > 8: <Core:ChangePropertyAction.Value> 9: <ImageSource>images/ba2.png</ImageSource> 10: </Core:ChangePropertyAction.Value> 11: </Core:ChangePropertyAction> 12: </Core:DataTriggerBehavior> 13:   14: <Core:DataTriggerBehavior Binding="{Binding banane.reifegrad}" 15: ComparisonCondition="GreaterThan" Value="14"> 16: <Core:ChangePropertyAction TargetObject="{Binding ElementName=image1}" 17: PropertyName="Source" > 18: <Core:ChangePropertyAction.Value> 19: <ImageSource>images/ba3.png</ImageSource> 20: </Core:ChangePropertyAction.Value> 21: </Core:ChangePropertyAction> 22: </Core:DataTriggerBehavior> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Für jeden Schwellwert muss ein Trigger angelegt werden, hier also 16. Besonders elegant erscheint mir das nicht. Für Anregungen einfach mail an hannesp AT ppedv.de.

UX Entwicklung ohne Echtdaten in Windows 8.1

Für den Einstieg in eine Oberflächen Entwurfssprache ist es immer leichter mit einem Designer zu starten. Bekannt auch aus Visual Studio. Button aus Werkzeugleiste auf das Formular ziehen und doppelclicken und Ereignis Programmcode tippen. ( gerne als Pixelschupser bezeichnet). Nicht mehr ganz so leicht ist das mit z.B. Listensteuerelementen. Wenn die Struktur bekannt ist, im Sinne streng typisiert, dann kann ein Designer noch den Header und Dummy Zeilen darstellen. ASP.NET Webforms oder auch Winforms gehen diesen Weg. Für XAML stehen schon seit einer Weile sogenannte Entwurfszeit (Design Time Daten) zur Verfügung. Man bindet an die Eigenschaften und stellt je nach Umgebung echte Daten oder Fake Daten ( auch Mock genannt) bereit. Dafür gibt es einen Weg im Konstruktor mit einer Logikverzweigung per DesignMode.DesignModeEnabled zu verzweigen. Spannend ist aber, was sich die Microsoft Entwickler einfallen haben lassen. Schon länger gibt es den Ansatz im XAML per d: für den Editor Alternative Datenbindungen einzugehen. Bisher wurden diese Daten aber gerne per Code erzeugt (meist SampledataSource.cs oder .vb). Jetzt nehmen Sie XAML. Schritt für Schritt Zuerst wird ein einfaches ViewModel erstellt. Eine Liste von Banane. 1: Public Class bananeVM 2: Property bananen As List(Of banane) 3: Public Sub New() 4: bananen = New List(Of banane) 5: End Sub 6: End Class .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Dann ruft man Expression Blend aus dem Windows 8.1 Store Projekt (in Visual Studio 2013) auf Dort kann man im Reiter Daten das Icon clicken und aus einer vorhanden Klasse die Beispieldaten generieren lassen. Der nachfolgende Dialog sieht so aus Wenns gut gelaufen ist, sollte rechts im Daten Reiter die Liste und ihre Propertys auftauchen. Wie kommen nun diese Daten ins Formular? Man kann es im XAML Tippen oder per Drag und Drop (hier Bananen) ins Formular ziehen. Da wird dann leider ein Standard DataTemplate aus dem Gridview verwendet, das selten wirklich passt. Aber das spannende ist, die generierte Zuweisung des Datacontextes. 1: <Grid d:DataContext="{d:DesignData /SampleData/bananeVMSampleData.xaml}" .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Wir haben nun zwei Datacontexte. Auf den normalen DataContext, der in der Regel auf das Viewmodel verweist und den D: der die DummyDaten aus der XAML Datei zieht. Natürlich kann auch der Visual Studi Designer das korrekt als WYSIWYG Ansicht darstellen. Das ganze klappt, weil man in XAML Objekte deklarativ als  instanziieren kann. Wem die Daten zu realitätsfern sind, der kann sich am XAML direkt vergreifen. 1: <sampleData:bananeVM xmlns:sampleData="using:sampleData"> 2: <sampleData:bananeVM.bananen> 3: <sampleData:banane ID="24" Name="Curabitur aliquam nam vestibulum cras" geschaelt="22" lang="323.07"/> 4: <sampleData:banane ID="62" Name="Integer quisque vivamus sed" geschaelt="83" lang="384.76"/> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Die Idee für das entscheidende Attribut d: DesignData stammt vermutlich aus Silverlight. In Windows 8 WinRT klappt das nicht. Soweit ich bisher gesehen habe, gibt es keine Möglichkeiten eine geänderte Klasse als Refresh in die Entwurfsdaten zu bringen. 

Die Sache mit dem Kacheln reloaded 8.1

Das Konzept des blinkenden Menüs stammt von Windows Phone. Die Ursprungsidee, die Anzahl der neuen Anrufe beim Icon für Telefon zu zeigen, ist allerdings schon älter (in Windows 8 Badge genannt). Ich habe vor einiger Zeit einen Artikel dazu geschrieben, der noch aktuell ist. Neu hinzugekommen ist eine Einstellung im Visual Studio 2013 Windows Store App Mainfest, das in Richtung Periodicupdate zielt. Auf diese Art kann man aus einer XML Datei von einem Webserver per Intervall die Kachel füllen. Das kleinste Refresh Intervall ist 30 Minuten. Zu beachten ist, das dafür kein Code benötigt wird und die Anwendung nicht gestartet werden muss. Das Betriebssystem (hier Windows 8.1) kümmert sich drum. Das ist auch der größte Unterschied zum Tileupdater ist, das auch wenn die Anwendung nie gestartet wird, die Kachel aktualisiert wird. Um Spracheinstellungen oder Landeseinstellungen beim Inhalt der Kacheln berücksichtigen zu können, stehen zwei Platzhalter zur Verfügung. Der Screenshot zeigt den Request. Diese Beispiel enthält nur das XML Template für eine quadratische Kachel. Es können in der Rückgabe auch die anderen Kachelgrößen und deren Templateformate mitgeliefert werden. 1: <tile> 2: <visual> 3: <binding template="TileSquareText01"> 4: <text id="1">Hannes</text> 5: <text id="2">Text Field 2</text> 6: <text id="3">Text Field 3</text> 7: <text id="4">Text Field 4</text> 8: </binding> 9: </visual> 10: </tile> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Für den Test einfach das Projekt in Visual Studio 2013 kompilieren und dann die Funktion “Projektmappe Bereitstellen”. Genau in dem Augenblick wird der Request zum Webserver ausgeführt und die Kachel aktualisiert. Wenn später in der Anwendung App der Tileupdater verwendet wird, wird dieses Setting überschrieben.

Gruppierte Liste und MVVM

In meiner heutigen Windows 8 Modern UI (METRO) Schulung, ging’s um grouped Lists für Listview oder auch Gridview. Ich verwende im meinen VB.NET Beispielen gerne deutschsprachige Namen um auf einen Blick erkennen zu können, was API und was eigener Code ist. Dabei ging es um Personen, die anhand des Geschlechts gruppiert werden sollen. Für Geschlecht habe ich aus irgendwelchen Gründen das Englische Sex genommen und wenn man das Gruppiert landet man schnell bei Variablen wie GruppenSex. Nicht wundern. Mein Ziel war Gruppen und das Viewmodel in Einklang zu bringen. Zunächst das Model mit der zweiten Klasse um die erste zu gruppieren. 1: Public Class Personen 2: Public Property ID As Integer 3: Public Property Name As String 4: Public Property Sex As String 5: End Class 6: Public Class GruppeSex 7: Public Property Sex As String 8: Public Property Menge As List(Of Personen) 9: End Class .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }   Im ViewModel werden dann Dummy Daten erstellt, die erfreulicherweise so auch im Designer gleich angezeigt werden. 1: Public Class PersonenVM 2: Public Sub New() 3: PersonenListe = New List(Of Personen) 4: PersonenListe.Add(New Personen With {.ID = 1, .Name = "Hannes", .Sex = "Mann"}) 5: PersonenListe.Add(New Personen With {.ID = 2, .Name = "Maria", .Sex = "Mann"}) 6: PersonenListe.Add(New Personen With {.ID = 3, .Name = "Maria", .Sex = "Frau"}) 7: PersonenListe.Add(New Personen With {.ID = 4, .Name = "Marion", .Sex = "Frau"}) 8: PersonenListe.Add(New Personen With {.ID = 5, .Name = "Franz", .Sex = "Mann"}) 9: PersonenListe.Add(New Personen With {.ID = 6, .Name = "Hermann", .Sex = "Mann"}) 10: PersonenListe.Add(New Personen With {.ID = 7, .Name = "Hanna", .Sex = "Frau"}) 11: PersonenListe.Add(New Personen With {.ID = 8, .Name = "Hubert", .Sex = "Mann"}) 12: PersonenListe.Add(New Personen With {.ID = 9, .Name = "Horst", .Sex = "Mann"}) 13: PersonenListe.Add(New Personen With {.ID = 10, .Name = "Anna", .Sex = "Frau"}) .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Als öffentliche Eigenschaften gibt es die Personen und die Gruppieriung. Benötigt wird hier nur Gruppelist. 1: Property PersonenListe As List(Of Personen) 2: Property GruppeList As List(Of GruppeSex) .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Achtung: wenn das Property fehlt oder in C# der Set und Get, handelt es sich um einen normale Eigenschaft, die in der Bindung unsichtbar bleibt. Ein häufiger Fehler, der auch bei den Teilnehmer dieses Trainings auftrat. Um die Gruppierung der Daten zu erreichen kommt hier ein LINQ Statement im Konstruktur (nach den Dummy Daten) zum Einsatz. 1: Dim query = From item In PersonenListe 2: Order By item.Sex 3: Group item By Key = item.Sex Into g = Group 4: Select New GruppeSex With {.Sex = Key, .Menge = g.ToList} 5: GruppeList = query.ToList .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Nun geht es in den deklarativen XAML Teil. Das Viewmodel wird über den DataContext der Page zugewiesen und damit instanziiert. Dazu wird unbedingt die CollectionViewSource benötigt. Der wird dann aus dem Modell die passende Liste zugewiesen, gruppierung angeschalten und der Pfad für die 1:n mit Item zugewiesen (aus der Klasse GruppeSex) 1: <Page.DataContext> 2: <local:PersonenVM></local:PersonenVM> 3: </Page.DataContext> 4: <Page.Resources> 5: <CollectionViewSource x:Name="cvs1" Source="{Binding GruppeList}" 6: IsSourceGrouped="True" 7: ItemsPath="Menge"></CollectionViewSource> 8: </Page.Resources> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Ich gehe davon aus, das die Templating und Binding Konzepte von XAML bekannt sind. Die Besoderheit hier ist lediglich das GroupStyle Template für die Überschriften. Das Gridview Steuerelement bekommt aus dem CollectionViewsource die Daten zugewiesen (Staticresource weil in Resources deklariert) und Source statt Path, weil Liste. Im Kopf wird dann das Geschlecht angezeigt (aus der Klasse GruppeSex). Im Itemtemplate der Name (aus der Klasse Personen) 1: <GridView ItemsSource="{Binding Source={StaticResource cvs1}}" 2: HorizontalAlignment="Left" Margin="62,38,0,0" VerticalAlignment="Top" Width="961" Height="235"> 3: <GridView.GroupStyle> 4: <GroupStyle> 5: <GroupStyle.HeaderTemplate> 6: <DataTemplate> 7: <TextBlock Text="{Binding Sex}" FontSize="42"></TextBlock> 8: </DataTemplate> 9: </GroupStyle.HeaderTemplate> 10: </GroupStyle> 11: </GridView.GroupStyle> 12: <GridView.ItemTemplate> 13: <DataTemplate> 14: <TextBlock Text="{Binding Name}" Width="100"></TextBlock> 15: </DataTemplate> 16: </GridView.ItemTemplate> 17: </GridView> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Wirklich nett ist die sofortige Anzeige in Visual Studio 2013 oder Expression Blend. Hinweis: Unter WinRT Windows 8 hat das geringfügig anders funktioniert.

RelayCommand in Windows 8.1

Einige neue Templates in Visual Studio 2013 Windows Store Apps enthalten bereits Hilfsklassen. Dazu gehören Hub-APP, Raster-App und Split-App. Es fallen auch einige Klassen weg wie Bindablebase oder LayoutAwarePage. Neu hinzugekommen sind NavigationHelper oder SuspensionManager. Hier wird das RelayCommand betrachtet. Wer mit leerer App startet, kopiert einfach die Klasse ins Projekt. Darin befindet sich eine Implementierung des ICommand Interfaces. Per ICommand wird in WinRT MVVM Command Binding realisiert. Das typische Button Click Event landet dann im ViewModel und nicht in der Page. 1: Namespace Common 2: ''' <summary> 3: ''' 4: ''' </summary> 5: ''' <remarks></remarks> 6: Public Class RelayCommand 7: Implements ICommand 8: Private ReadOnly _execute As Action 9: Private ReadOnly _canExecute As Func(Of Boolean) 10:   11: Public Sub New(execute As Action) 12: Me.New(execute, Nothing) 13: End Sub 14:   15: Public Sub New(execute As Action, canExecute As Func(Of Boolean)) 16: If execute Is Nothing Then 17: Throw New ArgumentNullException("execute") 18: End If 19: _execute = execute 20: _canExecute = canExecute 21: End Sub 22: Public Event CanExecuteChanged As EventHandler 23: Implements ICommand.CanExecuteChanged 24: Public Sub RaiseCanExecuteChanged() 25: RaiseEvent CanExecuteChanged(Me, EventArgs.Empty) 26: End Sub 27: Public Function CanExecute(parameter As Object) As Boolean 28: Implements ICommand.CanExecute 29: Return If(_canExecute Is Nothing, True, _canExecute()) 30: End Function 31: Public Sub Execute(parameter As Object) Implements ICommand.Execute 32: _execute() 33: End Sub 34: End Class 35:   36: End Namespace Satt dem Click Handler im “Codebehind” wird ein einfaches Property in einer Klasse angelegt (gern ViewModel genannt).  Hier im VB.NET Code Beispiel mit einer namenlosen Methode (auch Lambda genannt). 1: Imports first81.Common 2: Imports Windows.UI.Popups 3:   4: Public Class fuuVM 5: Public Property Button1 As New RelayCommand(Async Sub() 6: Dim msg = New MessageDialog("hallo Welt") 7: Await msg.ShowAsync 8: End Sub) 9: End Class Natürlich kann man auch eine klassische Sub per Addressof eingebunden werden. Dann eben mit Namen, hier myClick. 1: Public Property Button1 As New RelayCommand(AddressOf myClick) 2:   3: Private Async Function myClick() As Task 4: Dim msg = New MessageDialog("hallo Welt") 5: Await msg.ShowAsync 6: End Function Nur mehr mittelspannend ist die des Buttons per Bindung im XAML per Markup Extension. 1: <Page.DataContext> 2: <local:fuuVM/> 3: </Page.DataContext> 4:   5: <Grid > 6: <Button Content="Button" Command="{Binding Button1}" /> 7:   8: </Grid>

XAML Converter und MVVM

Ich hatte mir vorgenommen einen einfachen Converter zu schreiben, bin dann aber bei einem etwas ausführlicheren VB.NET WinRT Beispiel gelandet, Mit drei Slidern wird die Hintergrundfarbe eines Rechtecks verändert. Entsprechend den Farbkanälen Rot Grün Blau. Das ist ein gutes Beispiel für einen Converter, weil der Typ Brush nicht mit den Integer Werten von Schiebereglern korrespondiert. Im XAML kann man Fill=”blue” schreiben. Aber hier retten einem Implizite Typ converter den Arsch und konvertieren nach Brush. Im Code geht das ohnehin nicht. In anderen XAML Dialekten könnte man sich per Multibinding in Kombination mit Elementbinding retten. Dies wird aber in Windows 8.1 nicht unterstüzt. Ein weiterer theoretischer Ansatz ( der nicht funktioniert) ist direkt über die Color Kanäle zu binden. 1: <Rectangle.Fill> 2: <SolidColorBrush > 3: <SolidColorBrush.Color> 4: <Color A="255" R="255" G="0" B="0" /> 5: </SolidColorBrush.Color> 6: </SolidColorBrush> 7: </Rectangle.Fill>--> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }   Visual Studio 2013 zeigt das obige XAML im Designer korrekt an, Zur Laufzeit wirft es allerdings einen Fehler, Darüber hinaus muss eine Eigenschaft, die gebunden werden soll, ein Dependencyproperty sein. Meine Klasse ( nennen wir sie ViewModel) enhält defacto ein Farbmodell mit den Propertys rot, grün blau. Sehr oft lässt sich beim designen des Viewmodells, der Blick auf die spätere Implementierung in der Sicht (View) nicht unterdrücken. Hier werden die Slider an die Integer Werte gebunden. Eine weitere Eigenschaft hält die dadurch gemischte Farbe, Die Farbe muss künstlich beim sliden aktualisiert werden, damit das UI auch aktualisiert wird. Dadurch wird das Event PropertyChanged ins UI gefeuert und das Rechteck ändert seine Farbe. 1: Imports Windows.UI 2: Imports Windows.UI.ColorHelper 3: Public Class farbeVM 4: Implements INotifyPropertyChanged 5:   6: Public Event PropertyChanged(sender As Object, e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged 7: Public Sub onPropertyChanged(<CallerMemberName> Optional name As String = "") 8: RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(name)) 9:   10: End Sub 11: Public Sub New() 12: _dieFarbe = New Color 13: 14: End Sub 15:   16:   17: Private _dieFarbe As Color 18: Public Property dieFarbe() As Color 19: Get 20:   21: Return _dieFarbe 22: End Get 23: Set(ByVal value As Color) 24: _dieFarbe = value 25: onPropertyChanged() 26: End Set 27: End Property 28: Public Sub updateColor() 29: dieFarbe = FromArgb(255, rot, grun, blau) 30: End Sub 31: Private _rot As Integer 32: Public Property rot() As Integer 33: Get 34: Return _rot 35: End Get 36: Set(ByVal value As Integer) 37: _rot = value 38: updateColor() 39: End Set 40: End Property 41: Private _blau As Integer 42: Public Property blau() As Integer 43: Get 44: Return _blau 45: End Get 46: Set(ByVal value As Integer) 47: _blau = value 48:   49: updateColor() 50: End Set 51: End Property 52: Private _grun As Integer 53: Public Property grun() As Integer 54: Get 55: Return _grun 56: End Get 57: Set(ByVal value As Integer) 58: _grun = value 59: updateColor() 60: End Set 61: End Property 62:   63:   64: End Class .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Die Slider schicken direkt jede Änderung an das Viewmodel 1: <Slider Value="{Binding rot, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" ... Background="#29FF0000" Maximum="255" /> 2: <Slider Value="{Binding grun,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" ... Background="#2921FF00" Maximum="255"/> 3: <Slider Value="{Binding blau,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" .... Background="#290031FF" Maximum="255"/> 4: .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Nun kommt der Konverter zum Einsatz, der aus Color einen Soldicolorbrush formt. Auch Konverter werden deklarativ im XAML instanziert.  1: <Page.DataContext> 2: <local:farbeVM/> 3: </Page.DataContext> 4: <Page.Resources> 5: <local:farbeconf x:Key="konf1"/> 6: </Page.Resources> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Intellisense schlägt den Eintrag “farbconf” auch vor, wenn das Windows Store Projekt erfolgreich kompiliert ist. Der Converter hat zwei Methoden, die über ein Interface erzwungen werden. 1: Public Class farbeconf 2: Implements IValueConverter 3:   4:   5: Public Function Convert(value As Object, targetType As Type, 6: parameter As Object, language As String) As Object Implements IValueConverter.Convert 7: Return New SolidColorBrush(value) 8:   9: End Function 10:   11: Public Function ConvertBack(value As Object, targetType As Type, 12: parameter As Object, language As String) As Object Implements IValueConverter.ConvertBack 13:   14: End Function 15: End Class Die Methode ConvertBack bleibt ungenutzt. Nur benötigt, wenn der Benutzer direkt im Rechteck malen könnte und damit die Slider verschoben werden sollen. Die Bindung an die Farbe wie üblich mit Intellisense unterstützung. Da der Converter per deklaration statisch instanziert wurde, mit passenden Schlüsselwort und dem Key. 1: <Rectangle Fill="{Binding dieFarbe, Converter={StaticResource konf1},Mode=TwoWay}" .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

XAML Windows 8.1 Binding 101

Das Databinding von XAML ist für mich eine sehr praktische Sache. Designtimeunterstützung, Designer Werkzeuge und asynchrone Daten. In Windows 8.1 gibt es ein paar kleine Neuerungen. Um alle abzuholen  mal ganz von vorne. Daten In einer Klasse, gerne auch ViewModel genannt werden Daten erstellt. In diesem VB.NET Beispiel eine einfache Eigenschaft für einen Raum. 1: Private _raum As Integer 2: <Range(1, 23)> 3: Public Property raum() As Integer 4: Get 5: Return _raum 6: End Get 7: Set(ByVal value As Integer) 8: _raum = value 9: onPropertyChanged() 10: End Set 11: End Property .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Das es nur drei Räume gibt, sollten dieses validiert werden. Theoretisch klappt sowas in XAML mit DataAnnotations. Nicht in Windows 8.1. Um eine Veränderung des Raums ans Userinterface bzw das gebundene Steuerelement übermitteln zu können, wird ein gemeinsames Event benötigt (PropertyChanged), das über ein Interface erzwungen wird. 1: Public Class listVM 2: Implements INotifyPropertyChanged 3: .... 4:   5: Public Event PropertyChanged(sender As Object, e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged 6: Public Sub onPropertyChanged(<CallerMemberName> Optional name As String = "") 7: RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(name)) 8: End Sub .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Die Methode on… ist selbst geschrieben und erleichert das feuern (raise) des Events. In früheren XAML Versionen (Kleiner 4) musste man den Propertynamen direkt mit angeben. Das löst nun der Compiler mit dem CallerMemberName. In der XAML Seite wird das Viewmodel deklarativ instanziert 1: <Page.DataContext> 2: <local:listVM/> 3: </Page.DataContext> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }   und steht dann mit seinen Eigenschaften zum Binden bereit. Beim obigen deklarieren können auch gleichzeitig Werte per Attribut ala raum=3 vorbelegt werden. 1: <TextBox Text="{Binding raum,UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Mit dem Schlüsselwort Binding wird díe zuweisung Klasse Eigenscaft zu Steuerelement Eigenschaft eingeleitet. Es gibt eine alternative Syntax mit Path=. Um Änderungen in beide Richtungen zu erhalten (jemand anders ändert den Raum) wird Mode=TwoWay gewählt. Neu hinzugekommen ist die Eigenschaft Updatesourcetrigger. Wenn diese fehlt, wird der Update bei Lostfocus ausgeführt. In diesem Beispiel sofort beim tippen jeder einzelnen Zahl. Dritte Möglichkeit ist Explizites Updaten nur auf Befehl. 1: Private Sub Button_Click(sender As Object, e As RoutedEventArgs) 2: Dim expression = txtNummer.GetBindingExpression(TextBox.TextProperty) 3: expression.UpdateSource() 4: End Sub .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Visual Studio 2013 Intellisense ist beim Binding eine echte Stütze Wenn die gebundene Eigenschaft nicht vorhanden ist, was bei dynamischen Daten auch Feature ist, kann man mit dem neuen Attribut FallBackValue arbeiten. Hier wird an das nicht vorhanden Property Raum1 gebunden. Falls die Daten noch nicht gefüllt sind, weil z.B. asynchron ein Service verarbeitet wird, nimmt man das Binding Attribut TargetNullValue. Obwohl System.ComponentModel.DataAnnotations vorhanden ist und es ein Interface INotifyDataErrorInfo, fehlt eine Validierung die per Deklaration  zb ValidatesOnNotifyDataErrors  funktioniert. Es gibt natürlich wie immer mehrere Toolkits, die sich derer annehmen (XAML Toolkit, PRISM…). Ein weiteres wichtiges Attribut behandelt die Datentypen. Per Converter und dazu gehöriger Klasse wird aus den Daten der Typ der letztendlich an ein UI Property gebdunden werden kann. Dazu aber in einem späteren Blog Post mehr.

Windows 8.1 Blend behavior CallmethodAction

Mit Expression Blend sind zu Silverlight 3 Zeiten (oder doch schon früher?) Verhalten (englisch behavior) eingeführt worden. Damit lassen sich über Attribute oder Subelemente komplett neue Funktion zu einem UI Element hinzufügen. Das ganze in Blend per Drag&Drop. So kann ein Button mit dem passenden Behavior MouseDragElementBehavior mit der Maus bewegt werden. Behaviors haben nun auch in Windows 8.1 Einzug gehalten. Speziell im Zusammenhang mit MVVM wurde dies gefordert. Sowohl Anzahl, als auch Implementierung unterscheiden sich erheblich zu Silverlight oder WPF. Unter anderem finden sich die Gründe dafür in der WinRT und den Regeln zur Vererbung über API Grenzen. In diesem VB.NET Beispiel wird aus einer Liste per Geste ein Eintrag in eine andere Liste übernommen. Für die Bindung an eine ICommand basierte Eigenschaft aus dem Viewmodel, kann nur auf das Command Attribut gebunden werden. Nur wenige UI Elemente haben dieses. Hier soll eine Border und das spezielle Event Manipulationstarted verwendet werden. Da bleibt nur der CallMethodAction Eventtrigger. Das sehr einfach gehaltene Viewmodell beinhaltet zwei Listen und eine Methode um einen Eintrag anzulegen. 1: Public Class listVM 2: Public Property namen As ObservableCollection(Of String) 3: Public Property ziel As ObservableCollection(Of String) 4: Public Sub New() 5: ziel = New ObservableCollection(Of String) 6: namen = New ObservableCollection(Of String) 7: namen.Add("hannes") 8: namen.Add("franz") 9: namen.Add("anna") 10: namen.Add("dora") 11: namen.Add("norbert") 12: namen.Add("meila") 13: namen.Add("nina") 14:   15: End Sub 16: Public Sub addziel(sender As Object, e As ManipulationStartedRoutedEventArgs) 17: ziel.Add(e.OriginalSource.tag) 18: End Sub 19:   20: End Class .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Im Gegensatz zu einem Command Binding gibt es keine Parameter um auf die Daten zu kommen. Ich habe mir mit dem Tag Attribut beholfen um den Wert zu halten. Der ViewModel Code enthält noch eine weitere Abhängigkeit zur View, die EventArgs. In Expression Blend werden dann in der XAML Seite Demodaten  aus der Klasse erzeugt (hier rot umrandet) die nur zur Entwurfszeit benötigt werden. Per drag und Drop können dann Felder und Listen in das XAML Formular gezogen werden. Die Darstellung umfasst dann ein Gridview mit u.a. ein Image. Um das Itemtemplate zu ändern, wird oben mit dem kleinen Pfeil rechts neben Gridview in das weitere Template “generierte Elemente” gewechselt. Dann wird das Bild und ein Textblock entfernt. Dafür wird das Stackpanel in ein Border gepackt Durch Click auf das kleine (noch graue Rechteck) neben der Text Eigenschaft des TextBlock Elements kann die Bindung an ein Property aus dem Viewmodel definiert werden. Um auch zur Laufzeit Daten zu haben, muss die Page noch an das Viewmodell gebunden werden. Konkret die Datacontext Eigenschaft. Nun fehlt nur noch die Zuordnung zur Addziel Methode. Dazu wird das behavior CallMethodAction auf das Border Element gezogen. In den Eigenschaften des EventtriggerBehavior, wird der EventName, hier Manipulationstarted definiert. Damit das Event zur Laufzeit auch kommt, nicht vergessen ManipulationMode von Border zu setzen. In den Eigenschaften von CallMethodAction  wird das UI Element festgelegt, das den Context für die Methode aus dem ViewModell hält. Auf den ersten Blick nicht ganz logisch TargetObject genannt. Das kann visuell mit der Zielscheibe und der Maus erfolgen. Da das Viewmodell an den Page Context gebunden ist und man sich aber im Itemtemplate des GridView befindet, wird das nicht klappen.  Entweder man legt sich außerhalb des Gridviews ein Control um den Drag&Drop way zu beschreiten und das Ergebnis dann in das Gridview zu legen. Oder man tippt es gleich im XAML. 1: <Page 2: xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3: xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4: xmlns:local="using:App4dragMVVM" 5: xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 6: xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 7: xmlns:Interactivity="using:Microsoft.Xaml.Interactivity" 8: xmlns:Core="using:Microsoft.Xaml.Interactions.Core" 9: x:Class="App4dragMVVM.MainPage" 10: mc:Ignorable="d" x:Name="root" > 11: <Page.Resources> 12: <DataTemplate x:Key="StringTemplate"> 13: <Grid Height="110" Width="308" Margin="10" > 14: <Grid.ColumnDefinitions> 15: <ColumnDefinition Width="Auto"/> 16: <ColumnDefinition Width="*"/> 17: </Grid.ColumnDefinitions> 18: <Border Grid.Column="1" Margin="10,0,29,0" ManipulationMode="TranslateY" 19: Tag="{Binding }" 20: BorderThickness="2" BorderBrush="#FF075BF8" CornerRadius="10" Padding="5"> 21: <Interactivity:Interaction.Behaviors> 22: <Core:EventTriggerBehavior EventName="ManipulationStarted"> 23: <Core:CallMethodAction MethodName="addziel" TargetObject="{Binding ElementName=root,Path=DataContext}"/> 24: </Core:EventTriggerBehavior> 25: </Interactivity:Interaction.Behaviors> 26: <Border.Background> 27: <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0"> 28: <GradientStop Color="Black" Offset="0"/> 29: <GradientStop Color="White" Offset="1"/> 30: </LinearGradientBrush> 31: </Border.Background> 32: <StackPanel> 33: <TextBlock x:Name="textBlock" Text="{Binding Mode=OneWay}" Style="{StaticResource TitleTextBlockStyle}"/> 34: 35: </StackPanel> 36: </Border> 37: </Grid> 38: </DataTemplate> 39: </Page.Resources> 40: <Page.DataContext> 41: <local:listVM/> 42: </Page.DataContext> 43:   44: <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" d:DataContext="{d:DesignData /SampleData/listVMSampleData.xaml}"> 45: 46:   47:   48: <GridView HorizontalAlignment="Left" Margin="188,49,0,0" VerticalAlignment="Top" Width="946" Height="301" ItemTemplate="{StaticResource StringTemplate}" ItemsSource="{Binding namen}" IsSwipeEnabled="False" SelectionMode="Single"/> 49: <GridView HorizontalAlignment="Left" Margin="167,392,0,0" VerticalAlignment="Top" Width="984" Height="348" ItemTemplate="{StaticResource StringTemplate}" ItemsSource="{Binding ziel}" /> 50:   51: </Grid> 52: </Page> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

Nachricht von–SingnalR is calling

Im Web löst ein Client (in der Regel ein Browser) einen Request an eine Server aus und der Antwortet .. irgendwann. Schon lange herrscht das Bedürfnisse vom Server einen Push an den Client durchzuführen, z.B. Chat. Wenn einen neue Nachricht da ist, gleich anzeigen, oder auch ganz spannend im LOB Umfeld, wenn der Benutzer einen Datensatz anzeigt oder bearbeitet, eine Benachrichtigung, wenn jemand zweiter das tut. Ganz ohne Record Locking. Die Mechanismen sind schon lange vorhanden. Neu hinzugekommen ist im HTML Standard WebSocket, die das Duplex Communication viel effizienter lösen. Allerdings bleibt eine Grundanforderung, das der Client die Verbindung Initial öffnen muss. Es braucht als einen Server, der sozusagen das Message Routing vornimmt. SingnalR ist nun eigentlich nur eine komfortable Library von Microsoft um Websockets, Frames und co außen rum. Für Web (also JavaScript), .NET, Silverlight und auch WinRT. Am Beginn steht das Nuget Packet zu installieren. Als nächstes wird in der global.asax das Routing eingerichtet. Ähnlich wie Web Api oder ASP.NET MVC gibt es eine Standard Router, hier “hub”. 1: Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs) 2: RouteTable.Routes.MapHubs() 3: End Sub .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }   Die Nachrichtenzentrale ist eine Klasse die von Hub geerbt wird. In diesem VB.NET Beispiel wird eine Zahl erhalten, um eins inkrementiert und an all weiter gereicht. 1: Imports Microsoft.AspNet.SignalR 2: Imports Microsoft.AspNet.SignalR.Hubs 3:   4: <HubName("Hannes2")> 5: Public Class Hannes1Hub 6: Inherits Hub 7: Public Sub Hannes1(i As Integer) 8: ' Clients.Others.Hannestatwas(i + 1) 9: Clients.All.Hannestatwas(i + 1) 10:   11: End Sub 12:   13: End Class .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Wichtig ist in Zeile 4 der Name, der nach Außen die Schnittstelle identifiziert (hannes2). Soweit ich eruiert habe, spielt der Name der Klasse keine Rolle. Die Methode in Zeile 7 wird dann vom Client aufgerufen und am Client die Methode in Zeile 9 (hannerstatwas) initiiert im Sinne eines Callbacks. Es gibt eine Reihe von Optionen die Anzahl der Endgeräte zu beschränken oder auch  nur einzelne User anzusprechen. In einer HTML Seite wird dann die Anzeige und Aktion definiert. Sehr wichtig sind die JavaScript Referenzen 1: <script src="Scripts/jquery-2.0.3.min.js"></script> 2: <script src="Scripts/jquery.signalR-1.1.2.min.js"></script> 3: <script src="signalr/hubs"></script> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Die Referenz in Zeile 3 ist eine virtuelle und stellt den Proxy dar. Weiters wird die Connection in Zeile 4 geöffnet und der Callback für den Browser angelegt. Als kleine optische Hilfe für den Benutzer zählt meine Anzeige von 0 auf 1 hoch, wenn die Verbindung erfolgreich gestartet wurde (Zeile 9)   1: <form id="form1" runat="server"> 2: <script >var hub 3: $(function () { 4: hub = $.connection.Hannes2; 5: hub.client.Hannestatwas = function (i) { 6: $("#empfange").text(i) ; 7: }; 8: $.connection.hub.start().done(function () { 9: $("#empfange").text(1); 10: }); 11: }); 12: function sende() { 13: 14: hub.server.hannes1($("#empfange").text()); //proxy macht lowcase 15: } 16: </script> 17: <div> 18: <div id="empfange">0</div> 19: 20: <input id="Button1" type="button" value="sende" onclick="sende();"/> 21: </div> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }   In Ziel 14 wird das senden einer ASP.NET Signal R Nachricht behandelt, wobei mir aufgefallen ist, das im VB.NET Klasse eigentlich mit einem Großbuchstaben beginnt, aber im Browser mit kleinem Buchstaben ankommt. Fiddler hat mir das wieder mal verraten. Wobei Websocket Nachrichten nicht per Fiddler gesichtet werden können. Der HTTP Code 101 verrät den Protokoll Wechsel. Das Protokoll ist  nun Verbindunglos und wir haben ein Risiko Nachrichten zu verlieren. Das sieht man im nächsten Teil einer Windows 8 (METRO, WinRT, Store App, modern UI : [SEO] ) ganz gut. Diese Anwendungen sind Systembedingt nicht dauernd im Vordergrund und bekommen gar nicht mit was alles läuft. In Windows 8 (vermutlich Windows 8.1) wird der Proxy in Zeile 7 generiert und eine Delegate Funktion um die ankommenden Nachrichten zu verarbeiten. Dabei kommt man aber aus dem Background Thread in den UI Thread und muss entsprechende Dispatchen (Codezeile 14) 1: Private Sub Button_Click(sender As Object, e As RoutedEventArgs) 2: hub.Invoke("hannes1", "5").Wait() 3: End Sub 4: Dim hub As IHubProxy 5: Private Async Sub MainPage_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded 6: Dim hubConn = New HubConnection("http://localhost:55542/") 7: hub = hubConn.CreateHubProxy("Hannes2") 8: hub.On(Of Integer)("Hannestatwas", AddressOf hannestatwas) 9: Await hubConn.Start() 10: text1.Text = 1 11: End Sub 12:   13: Private Async Function hannestatwas(i As Integer) As Task 14: Await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, Sub() text1.Text = i) 15: End Function .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Die Windows 8 App ist nicht ganz konsistent, weil sie nur die Nachricht “5” an den Hub schicken kann, im Button Click Event. Ich wollte hier den XAML Code sparen. Wie sieht's nun mit dem Traffic aus der generiert wird? Die Antwort  darauf kann Chrome liefern und beim Trouble Shooting helfen. Man erkennt, das JSON Format (erste Zeile 28 Bytes) das meines Wissens nach nicht standardisiert ist.   Abschließend kann ich noch nicht beurteilen welche Risiken im Einsatz des Signal R Frameworks liegen. Da die Softwerker aus Redmond nur auf bekannte Standards setzen und Fallback Mechanismen einsetzen sollte es dazu keinen Probleme kommen. Auch das Thema Sicherheit bzw. Verschlüsselung wurde angedacht. Das Microsoft keine 10 Jahre Support mehr anbietet (wie z.B. bei Silverlight) mussten sie wohl das ganze als Open Source auf Github bereit stellen. Zusätzlich ist aktuell ein Trend auf Portable Librarys und Self Hosting per Owin zu setzen. Mit der Version 2.0 ist dies möglich.