ASP.NET Web API mit winRT METRO App konsumieren

In einem früheren Blog Post habe ich gezeigt, wie man die neue ASP.NET Web API als REST basierte Service Schicht einsetzen kann, Dabei braucht man auch nicht das komplette ASP.NET MVC 4 Projekt. Mein VB.NET Beispiel erweitert eine bestehende ASP.NET Website. Die Datei Global.asax.vb imWeb Verezeichnis muss die Routing Regel implementieren und braucht zwei Namensräume. Imports System.Web.Routing Imports System.Web.Http Public Class Global_asax Inherits System.Web.HttpApplication Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs) RouteTable.Routes.MapHttpRoute( name:="DefaultApi", routeTemplate:="api/{controller}/{id}", defaults:=New With {.id = RouteParameter.Optional} ) 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; } Dann wird im Web Projekt eine neue Klasse Web API Controller angelegt. Der Name setzt sich zusammen aus [Daten}Controller, also hier kundencontroller.vb In diesem WInrt Beispiel wird allerdings nur die REST GET Methode implementiert. Die Datei mit der Liste von Vornamen wird im APP_Data Verzeichnis abgelegt. Da der Controller nicht direkt in der ASP.NET Pipline lauft, kann man Server.Mappath nicht verwenden. Mein Workaround ist die Appdomain und das DataDirectory. Public Class KundenController Inherits ApiController ' GET /api/<controller> Public Function GetValues() As IEnumerable(Of String) Return File.ReadAllLines(AppDomain.CurrentDomain.GetData("DataDirectory") + "\vorname.txt") 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; }   Für den Funktionstest einfach im Browser api/kunden aufrufen. METRO Windows 8 Anwendung Im VB.NET Client  wird per HTTP Client der Service aufgerufen und als Objekt im Json Format zurück geliefert. Per Parse wird daraus ein Array von JsonObject Elementen. Aus diesem wird per LINQ Abfrage der String extrahiert und eine generische Liste erzeugt. in diesem Fall von Strings.  q kann man dann ganz einfach an eine GridView binden. Private Async Function Button_Click_1(sender As Object, e As RoutedEventArgs) As Task Dim uri As Uri = New Uri("http://localhost:12933/api/kunden") Dim http As New HttpClient() Dim j As JsonArray j = JsonArray.Parse(Await http.GetStringAsync(uri)) Dim q = From item In j Select item.GetString Gridview1.ItemsSource = q 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; }

WinRT Listview customizen

Wie bei WPF und Silverlight üblich, kann man über die XAML  Templates das Layout des ListView Controls komplett verändern. Folgendes XAML Beispiel verändert die Ausrichtung der Liste auf Horizontal, <ListView x:Name="Listbox1" HorizontalAlignment="Left" VerticalAlignment="Top" Grid.Row="2" > <ListView.ItemsPanel> <ItemsPanelTemplate> <StackPanel Orientation="Horizontal"/> </ItemsPanelTemplate> </ListView.ItemsPanel> </ListView> .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; } Wenn man ca 4000 items in der Liste hat braucht meine Anwendung plötzlich 366 MB Arbeitsspeicher und ewig zum aufbauen. Der Grund ist seit langem bekannt und hat nichts mit WinRT oder C#/VB.net zu tun. In diesem Sample werden schlicht 4000 UI Controls erzeugt, auch für den nicht sichtbaren Bereich. Das das nicht gut sein kann, versteht sich von selbst. Das Standard Listview Control braucht mit geladenen Daten nur 19,5 MB (ohne Daten 12 MB). Das liegt daran, das intern das Virtualisierte Stackpanel angewendet wird. <ListView x:Name="Listbox1" HorizontalAlignment="Left" VerticalAlignment="Top" Grid.Row="2" > <ListView.ItemsPanel> <ItemsPanelTemplate> <VirtualizingStackPanel Orientation="Horizontal"/> </ItemsPanelTemplate> </ListView.ItemsPanel> </ListView> .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; } .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; }   Wenn man die Einträge mit Umbruch erzeugen möchte, kennt man aus XAML vermutlich das WrapPanel. Das gibt es so nicht, dafür das VariableSizedWrapGrid. Seicherverbrauch ähnlich wie Stackpanel, also Vorsicht mit großen Datenmengen. <VariableSizedWrapGrid Orientation="Horizontal"></VariableSizedWrapGrid> .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 Ergebnis mit der Namensliste. Wenn man letzteres vor hat sollte man besser gleich ein Gridview verwenden.

Filetype APP Activation in Windows 8

Windows 8 METRO Styled APPS können auf viele Arten gestartet werden. Der Benutzer clickt die Kachel (Tile) und startet diese neu. Durch wipen (touch streichen) von Links nach Rechts wir eine schlafende APP (suspended) wieder in den Zustand aktiv versetzt. Eine weitere Möglichkeit ist es die Anwendung per Contract zu starten. So kann einen Suche direkt in einer Anwendung ausgeführt werden ohne diese per Hand zu starten. Über die sogenannte Live Tiles Funktion kann die Anwendung auch Logik ausführen die per Push (Windows Push Notifications -WNS) oder Timer gesteuert wird. Doch die überraschende Neuerung ist über die Datei Erweiterung, die sogenannte File Type Association. Es gibt auch eine Protokoll Type Association. In beiden Fällen wird Ihre Anwendung gestartet wenn ein Ereignis eintritt. Eben wenn eine Datei mit einer bestimmten Erweiterung im Explorer oder per Code geöffnet wird. Dies ist eigentlich auch die einzige Möglichkeit in WinRT aus einer Anwendung heraus eine andere APP zu starten. Folgender Code ruft bei mir Notepad auf und öffnet die Datei. Die Datei muss im Verzeichnis eigen Dokumente liegen. Private Async Function Button_Click_2(sender As Object, e As RoutedEventArgs) As Task Dim file = Await Windows.Storage.KnownFolders.DocumentsLibrary.GetFileAsync("vorname.txt") Await Launcher.LaunchFileAsync(file) 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; } Wenn das nicht klappt, sollte man in Visual Studio 11 Output Window nachsehen. A first chance exception of type 'System.UnauthorizedAccessException' occurred in mscorlib.dll Weniger gute verstecke Exception. Jede Datei Erweiterung muss in der Anwendung bzw. Manifest erst frei gegeben werden (Package.appxmanifest), sonsts krachts. Der VB.NET Sample Code ist nun Dim file = Await Windows.Storage.KnownFolders.DocumentsLibrary.GetFileAsync("hannes.hannes") Await Launcher.LaunchFileAsync(file) .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 Erweiterung .Hannes wird in der Anwendung erlaubt. Nun läuft das Winrt Beispiel. Allerdings brauche ich nun noch eine zweite APP die auf den Click reagiert. (man kann das auch in einer lösen). Also eine neues Visual Studio Projekt anlegen. Dort wird ebenfalls eine Filetype Association angelegt für .hannes. Auf der XAML Seite BlankPagewird eine Textbox angelegt, die dann den Pfad und Namen der geclickten Datei anzeigen soll. die ersten vier Zeilen sind einfach aus dem allgemeinem OnLauched Event kopiert. Der Typ Cast ist nötig um auf die Eigenschaften des Objektes BlankPage zu kommen. Die args liefern die Datei(en) und wir nehmen einfach die erste. In Files gibt es die passende Eigenschaft Path. Es gibt auch weitere Propertys die Dateitypisch sind wie Datecreated oder Name. Protected Overrides Sub OnFileActivated(args As FileActivatedEventArgs) Dim rootFrame As New Frame() rootFrame.Navigate(GetType(BlankPage)) Window.Current.Content = rootFrame Window.Current.Activate() Try Dim b As BlankPage = DirectCast(Window.Current.Content, Windows.UI.Xaml.Controls.Frame).Content b.text1 = args.Files(0).Path Catch End Try 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; } In der Code Datei BlankPage.xaml.vb ist hilft ein Setter des Propertys Text1 die private Textbox nach außen zu öffnen. NotInheritable Public Class BlankPage Inherits Page Private _text1 As String Public WriteOnly Property text1() As String Set(ByVal value As String) textbox1.Text = value End Set 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; }   Wenn wir nun in APP1 den Button clicken, konnte man Daten in einer Datei ablegen und diese automatisch (ohne Contract!) in APP2 anzeigen lasse, ohne diese er Hand zu starten.

WinRT lokale Dateien lesen und schreiben

Für ein weiteres VB.NET Beispiel brauche ich viele Daten. Diese liegen in einem Text File und diese Datei muss ich öffnen. Das ist in einem Sandboxed System nicht ganz einfach. Der Zugriff auf Benutzerdaten unter Windows 8  der lokalen Festplatte ist auf die Bibliotheken Dokumente, Bilder, Musik und Videos beschränkt. Also müsste folgender Code funktionieren um eine Datei zu erzeugen. Try Dim sf1 As StorageFolder = KnownFolders.DocumentsLibrary Dim sampleFile = Await sf1.CreateFileAsync("sample.txt", CreationCollisionOption.ReplaceExisting ) Catch ex As Exception Dim msg As MessageDialog = New MessageDialog(ex.Message) msg.ShowAsync().AsTask() End Try .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; } Tut es aber nicht. Es kommt eine Acces denied Exception. Man muss im Manifest der Anwendung erst die Rechte auf die Dokument Library gewähren. Das reicht aber noch immer nicht. Als nächste muss laut meinen Tests noch eine File Typ Assoziation für die Erweiterung txt angelegt werden. Dann funktioniert auch das einlesen meiner txt Datei aus myDocuments. Dim sf = Await Windows.Storage.KnownFolders.DocumentsLibrary.GetFileAsync("vorname.txt") Dim s As String = Await FileIO.ReadTextAsync(sf) .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; } Der kleine Nebeneffekt ist, das nun die APP für die Erweiterung txt im Explorer registriert ist.

Windows 8 METRO Screen Shot machen

Banal aber doch eine Problem, Wie mache ich mit WIndows 8 von einer METRO Styled App ein Bild?. WinKey + PrintScreen (Druck) erzeugt den Screenshot als png im Librarys/Pictures (Bibliotheken/Bilder) Ordner. Für Screenshots per Code gibt es wie in Silverlight die (Winrt) WriteableBitmap Klasse. Anders als in Silverlight hat diese aber in der Beta keine Render Methode mehr und auch keinen Konstruktor der ein UIElement als Parameter nimmt. Also aktuell keine Screenshots per Code.

Use Case neuer Datensatz in Winrt METRO styled APP anlegen

Für das nächste Beispiel nehme ich einen WCF Service und versuche einen neuen Datensatz zu erzeugen. Dabei geht es nicht um die technischen Details, sondern um die Gestaltung der Oberfläche und der Benuzerexperience. Die Winrt Samples geben dazu weder in Javascript, c++ oder .net wirklich was her. Meist wird ein Grid genommen und ild un d Text in einem Flow Layout von links nach Rechts angezeigt. Wenn man nun aber einen neuen Record braucht, was macht man dann? Soweit ich das heute überblicke kennt kein Steuerelement einen Insert Modus. Ich gehe also mal von einer Liste aus (Grid geht auch). Empfohlen wird Interaktionen und damit Schaltflächen in die Appbar zu legen. Es gibt oben und unten eine. Der Benutzer kann diese durch Touch Geste von oben oder unterem Rand einblenden, per Maus Rechtsclick oder in der Tat auch per Shortcut WinKey + Z. Die Appbar kann unterschiedlich hoch sein und komplett unterschiedliche Art von Elementen enthalten. weil Tablet meist mit beiden Händen gehalten werden sollen die wichtigen Buttons im Bereich liegen der mit den Daumen leicht zu erreichen ist. Das ist also jeweils rechts und links. IM XAML wird das mittels zweier Stackpanel realisiert. Außerdem sollte die Anordnung der Buttons in der Appbar einer gewissen Logik und nach Vollziehbarkeit folgen.  Der Back oder Undo Button ist bei üblichen Anwendungen immer oben Links. Deswegen ist der im METRO IE10… genau – genau –unten links. Um die Buttons nicht immer neu erfinden zu müssen, bietet Microsoft Default Styles an. Falls nicht vorhanden ienfach dei Datei Styles.xaml und/oder StandardStyes.xaml. diese werden in der app.xaml als Resource Dictionarz eingebunden und stehen dann Anwendungsweit bereit, <Application.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="Common/StandardStyles.xaml"/> <ResourceDictionary Source="Styles.xaml"/> </ResourceDictionary.MergedDictionaries> .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 meinem ersten Versuch wollte ich so einen Add und einen Save Button einbauen. Diese werden per Staticresource an die Stylevorlagen gebunden <Page.BottomAppBar> <AppBar x:Name="GlobalAppBar" Padding="10,0,10,0"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="50*"/> <ColumnDefinition Width="50*"/> </Grid.ColumnDefinitions> <StackPanel x:Name="LeftCommands" Orientation="Horizontal" Grid.Column="0" HorizontalAlignment="Left"> <Button x:Name="Back" Tag="Back" Style="{StaticResource BackAppBarButtonStyle}" HorizontalAlignment="Left" Click="Back_Click"/> </StackPanel> <StackPanel x:Name="RightCommands" Orientation="Horizontal" Grid.Column="1" HorizontalAlignment="Right"> <Button x:Name="add1" Tag="add" Style="{StaticResource AddAppBarButtonStyle}" HorizontalAlignment="Left" Click="add_Click"/> <Button x:Name="save1" Tag="save" Style="{StaticResource SaveAppBarButtonStyle}" HorizontalAlignment="Left" Click="save_Click" Visibility="Collapsed" /> </StackPanel> </Grid> </AppBar></Page.BottomAppBar> .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; } Diese Buttons haben dann das typische runde, flache, einfarbige WIndows METRO Layout. Ehrlich gesagt ist mir noch nicht klar, warum die Dinger nicht auch runde Ecken haben sollen, aber fürs erste kann ich damit leben. Der Beschriftungstext kommt aus dem Tag Attribut und lässt sich so auch per Binding internationalisieren. Der Plan war das abwechselnd Add oder Save angezeigt werden. In einem anderen Blog beschreibe ich warum ich damit gescheitert bin. Das Hauptproblem ist, das es in Windows 8 keinen modalen Dialog gibt und sich man mit einem PopUp Control aus der Situation retten kann. Dann muss der Save Button aber auch im Popup sein um das click Ereignis zu erhalten, wenn isLightDismiss aktiviert ist. Ohne dieses Attribut bleibt das Popup stehen. Ein Lösungsansatz ist, das eigentlich verpönte X zum Schließen des Popups wieder einzuführen. Ganz generell sollte Benutzereingaben in Windows 8 immer automatisch ohen den typischen Save Button gespeichert werden. Wie soll das in einem Daten Dialog Fenster funktionieren? Der Benutzer möchte ja seine Eingaben oder Änderungen bestätigen. Automatisches speichern würde viel Traffic und Datenfehler erzeugen. Die Return taste sozusagen als Form submit zu missbrauchen erscheint mir auch wenig zeitgemäß. So bin ich am Ende wieder beim X für schließen oder speichern Schaltfläche gelandet. Das Verhalten Lightdismiss finde ich praktisch. So kann der Benutzer jederzeit durch clicken irgendwo in die UI den Popup  Dialog verlassen und schließen. Wenn er ihn wieder öffnet sind die vorigen Eingaben vorhanden. Der folgende Screenshot zeigt, das die Appbar nach oben wandert, wenn das On Screen Touch Keyboard eingeblendet wird. und erhebt keine Anspruch auf Schönheit, Das Popup ist blau dargestellt und erscheint nur wenn das PLUS in der Appbar gedrückt wurde. Der Vollständigkeit halber noch der Code basierend auf VBNET und Winrt um das speichern im WCF Service durchzuführen, Private Sub add_Click(sender As Object, e As RoutedEventArgs) popup1.IsOpen = True End Sub Private Sub Close_Click_2(sender As Object, e As RoutedEventArgs) popup1.IsOpen = False End Sub Private Async Sub Save_Click(sender As Object, e As RoutedEventArgs) Dim c As New ServiceReference2.Customers c.Customer_ID = txtKey.Text c.Company_Name = txtFirma.Text TextBox1.Text = Await svc.addCustomerAsync(c) txtKey.Text = "" txtFirma.Text = "" popup1.IsOpen = False 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; }

Flyout popup und IsLightDismissEnabled="true"

Gestern Nacht wieder Stunden um die Ohren geschlagen um ein minimales Detail in Windows 8 METRO Anwendungen zu klären. Es gibt in HTML ein Flyout Control, das es in XAML nicht gibt. Microsoft (hier Tim Heuer) empfiehlt alternativ das PopUp Steuerelement, das ich schon zu Silverlight Zeiten wenig geschätzt habe, statt dessen zu verwenden. Die Grundproblematik kommt aus dem single Window Ansatz der WinRT. Es gibt ja kein Childwindow oder anderen modalen Dialog (außer MessageDialog der aber nicht XAML ist). Es gibt aber Anwendungsfälle in denen das eben doch nützlich ist. In meinem Fall einen Datensatz hinzufügen. Man findet in Windows 8 METRO Anwendungen seit der Customer Preview auch Beispiele dafür. Da Menüs in die Appbar gelegt werden sollen, habe ich einen Neu und Save Button erzeugt, dessen Sichtbarkeit gewechselt wird. <Page.BottomAppBar> <AppBar x:Name="GlobalAppBar" Padding="10,0,10,0"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="50*"/> <ColumnDefinition Width="50*"/> </Grid.ColumnDefinitions> <StackPanel x:Name="LeftCommands" Orientation="Horizontal" Grid.Column="0" HorizontalAlignment="Left"> <Button x:Name="Back" Tag="Back" Style="{StaticResource BackAppBarButtonStyle}" HorizontalAlignment="Left" Click="Back_Click"/> </StackPanel> <StackPanel x:Name="RightCommands" Orientation="Horizontal" Grid.Column="1" HorizontalAlignment="Right"> <Button x:Name="add1" Tag="add" Style="{StaticResource AddAppBarButtonStyle}" HorizontalAlignment="Left" Click="add_Click"/> <Button x:Name="save1" Tag="save" Style="{StaticResource SaveAppBarButtonStyle}" HorizontalAlignment="Left" Click="save_Click1" /> </StackPanel> </Grid> </AppBar> </Page.BottomAppBar> .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; } Wenn nun “Neu” gewählt wird, wird das Popup per popup1.Isopen=true angezeigt und der Neu Button deaktiviert. <Popup x:Name="popup1" HorizontalAlignment="Right" VerticalAlignment="Top" Width="200" Margin="0,0,100,0" IsLightDismissEnabled="true" Closed="popup1_Closed_1"> <Grid Background="#FFE5E5E5" Height="334" Width="302"> <TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Text="5Code" VerticalAlignment="Top" Margin="10,18,0,0" Foreground="#FF0F50DA"/> <TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Text="fullname" VerticalAlignment="Top" Margin="10,65,0,0" Foreground="#FF1355E2"/> <TextBox x:Name="txtKey" HorizontalAlignment="Left" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Margin="76,10,0,0"/> <TextBox x:Name="txtFirma" HorizontalAlignment="Left" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Margin="76,54,0,0" Width="150"/> </Grid> </Popup> .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; } Wenn der Benutzer nun irgendwo hin klickt, verschwindet das Popup. Es behält aber seinen Status und Inhalt. Das Problem ist, wenn der Benutzer auf den Save Button in der Appbar clickt, verschwindet das Popup auch, aber das Save Event wird nicht gefeuert. Ich habe mich eine Zeitlang bemüht das zu umschiffen, leider erfolglos. Die einzig sinnvolle Kombination ist, den Save Button in das Popup zu legen. Am Ende keine befriedigende Lösung, da die Style Guides Command Buttons im Daumenbereich der Appbar vorsehen. Man könnte auch automatisch speichern im Closing Event des Popups. Das entspricht auch den Guidelines, immer alles automatisch zu speichern. Das iPhone macht das letztendlich auch schon immer so. Die Settings Dialoge bei Windows 8 tun das auch. Bei klassischen Formularen mit Dateneingabe erwartet der Benutzer aber heute eine OK CANCEL Situation. Schließlich könnte er etwas aus versehen verändert haben. Das ist für mich aktuell ein unbefriedigend gelöstes UI Problem.

METRO APP mit WCF verbinden

Um dieses Beispiel nachvollziehen zu können, empfiehlt sich die Lektüre des How to “erzeuge einen WCF Service mit der Nordwind Datenbank”. Darüber hinaus habe ich vor einiger Zeit einen Artikel geschrieben wie man Select Update, Delete und Insert mit einem WCF Service und Client abbildet. In diesem Blog wird ganz rudimentär  eine Liste von Kunden angezeigt. Wie immer mit einem METRO VB.NET Client. Persönlich glaube ich, das dies in Zukunft immer seltener mit WCF als Service Schicht geschehen wird. SOAP ist einfach schwergewichtig. Aber wir haben sie in den letzten Jahren geschaffen und so sollte man auch wissen wie man einen einbindet. Wenn sich der WCF Service in der gleichen Solution befindet, reicht es im METRO Projekt. Add Service Reference per Context Menü auszuwählen. Nach einem Click auf Discover muss der Service sichtbar sein. Daran ist auch nichts wirklich Neues. Glaubt man. Ein erster Blick hinter die Kulissen offenbart, es gibt kein config File mehr.  Jetzt geschieht Konfiguration im Code. Die Datei Reference.vb (.cs) sieht man nur wenn man im Solution Explorer show all files ausgewählt wurde.   Die Service Methoden werden asynchron und Task basierend angelegt. Der Code Schnipsel stammt aus dem Interface. <System.ServiceModel.OperationContractAttribute(Action:=http://tempuri.org/IService1/GetCustomer, ReplyAction:="http://tempuri.org/IService1/GetCustomerResponse")> _ Function GetCustomerAsync() As System.Threading.Tasks.Task(Of System.Collections.ObjectModel.ObservableCollection(Of ServiceReference2.Customers)) .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 Konfiguration wird dann im Code an passender Stelle durchgeführt, Für mein Beispiel lasse ich alles so wie es ist. ''' <summary> ''' Implement this partial method to configure the service endpoint. ''' </summary> ''' <param name="serviceEndpoint">The endpoint to configure</param> ''' <param name="clientCredentials">The client credentials</param> Partial Private Shared Sub ConfigureEndpoint(ByVal serviceEndpoint As System.ServiceModel.Description.ServiceEndpoint, ByVal clientCredentials As System.ServiceModel.Description.ClientCredentials) 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; } Wer partout Änderungen vornehmen will dem empfehle ich die MSDN Doku WCF und WinRT. Mein Beispiel braucht jetzt nur mehr den ASYNC und AWAIT Trick. Der Service wird instanziiert und dann GetCustomers asynchron angestoßen und die Rückgabe an das ListView  übergeben. Private Async Sub UserControl10_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded Dim svc As New ServiceReference2.Service1Client Dim d = Await svc.GetCustomerAsync listview1.ItemsSource = d listview1.DisplayMemberPath = "Company_Name" Textbox1.text = d.Count 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; } Alle MVP, MVC oder MVVM Fans werden mich für obige VB.NET Code Zeilen hassen. Aber es funktioniert, ist lesbar und der Rest kommt später.

WCF 1x1 Northwind Beispiel

Es ist ehrlich schon lange her, das ich eine WCF Service geschrieben habe, Mit Silverlight  kommt ja immer RIA Services zum Einsatz. Diese werden aber von WinRT nicht unterstützt. Jedenfalls keine Proxy Generierung und andere Annehmlichkeiten. Um also nicht alles per Hand machen zu müssen, wird sich mein nächstes Beispiel um WCF als METRO Client kümmern. Leider habe ich auf dem Gerat grad keinen WCF Service zur Hand. Also Step by Step einen WCF Service auf Basis der Nordwind Datenbank und der Kunden Tabelle gebaut. Es kommt Visual Studio 2012 und Entity Framework zum Einsatz. Zunächst erst mal die Datenbank. Ich habe mich für SQLCompact entschieden. Sollte eigentlich mitinstalliert werden, Mit dem Setup der Datei  SSCEVSTools-ENU.msi wird dann im Verzeichnis C:\Program Files (x86)\Microsoft SQL Server Compact Edition\v4.0\Samples die Northwind.sdf erzeugt. Windows 8 METRO Anwendungen kennen kein ADO.NET und auch kein WCF direkt. Datenbank Server Zugriffe müssen immer über einen Service Layer hindurch. Der METRO Solution wird dann ein Web Projekt hinzugefügt. Hier habe ich ein MVC Projekt gewählt. In Web  Projekt wird dann ein Verzeichnis APP_DATA erzeugt in das die Datei Northwind.SDF Datei kopiert wird, Als nächstes wird ein Entity Framework Modell aus der Datenbank erzeugt. Das vor allem um Zeit zu sparen. Für das  Modell wird die Customer Tabelle ausgewählt. Wer sich mit WCF beschäftigt hat, weis das es jetzt an den Contract geht, eigentlich ein einfaches Interface. Silverlight Enabled WCF Services brauchen das Interface nicht codiert. Der Service liefert eine Liste der Kunden haben vom Typ Customers mit der Methode GetCustomers. Und die Funktion AddCustomer, fügt einen neuen Kunden ein. Rückgabe ist die Anzahl aller Kunden. <ServiceContract()> Public Interface IService1 <OperationContract()> Function GetCustomer() As List(Of Customers) <OperationContract()> Function addCustomer(ByVal cust As Customers) 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; } Man mag über Interfaces denken was man will, wenn man eines hat ist es sehr einfach zu implementieren. Im Service (scv) einfach implements IService1 tippen und schon steht der Funktionsrumpf im Code. Dank Enties muss im WCF Service kaum noch VB.NET Code getippt werden. Public Class Service1 Implements IService1 Public Sub New() End Sub Public Function addCustomer(cust As Customers) As Integer Implements IService1.addCustomer Dim ctx As New NorthwindEntities ctx.Customers.AddObject(cust) ctx.SaveChanges() Return ctx.Customers.ToList.Count End Function Public Function GetCustomers() As List(Of Customers) Implements IService1.GetCustomer Dim ctx As New NorthwindEntities Return ctx.Customers.ToList End Function 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 Liste der Kunden wird mit einem zwei Zeiler aus dem EF Modell geholt. Man könnte natürlich auch per SQLConnection die Daten holen und in einem eigenen definierten Kunden Objekt ablegen. Braucht eben nur rund 30-50 Zeilen Code, mindestens. Den WCF Service kann man im Browser aufrufen und schon mal schauen ob das Hosting und die WSDL passt. http://localhost:12933/Service1.svc Man kann auch den in Visual Studio 11 enthaltenen WCF Test Client verwenden. Dieser scheint aber mit dem Datentyp Customers aus dem EF Model Probleme zu haben, so das der Funktionsaufruf GetCustomer nicht möglich ist.

WinRT HttpClient erster Versuch

Viele Wege führen nach Rom. Im vorigen Blog habe ich gezeigt wie man klassische RSS oder Atom Feeds konsumiert.  Dafür waren ganze 5 Zeilen Code nötig. Im nächsten Versuch verwende ich den eingebauten HTTP Stack manuell und ergänze das Beispiel um die Möglichkeit einen Hyperlink zu öffnen. Hier zeigen sich die Unterschiede zu Silverlight und .NET sehr deutlich. Zunächst lautet der Namensraum System.Net.Http in dem sich die HTTPClient Klasse befindet, statt WebClient oder HTTPWebRequest. Dafür kann die Windows 8 managed Klasse viel mehr z.B. Header manipulieren oder in Klassen in die Request oder Response Pipeline hängen. Wenn man folgende Fehlermeldung erhält hat man das Response Limit entdeckt. Cannot write more bytes to the buffer than the configured maximum buffer size: 65536. Also den MaxResponseContentBufferSize auf irgendeinen absurd hohen Wert setzen (schön ist das nicht). Die Rückgabe Zeichenkette muss ebenfalls Async abgearbeitet werden und dann in ein XElement geparst werden. Offensichtlich geht das parsing auch synchron. Dann ganz einfach per Xlinq eine schön Query absetzen auf RSS Item und link.  Und jetzt ACHTUNG: mit anonymen Typ und Datenbindung geht trotzdem. Also besser als in Silverlight, einmal ) Dim uri As Uri = New Uri("http://www.heise-marktplatz.de/system/RSS/category/7") Dim http As New HttpClient() http.MaxResponseContentBufferSize = 20000 Dim ixml = XElement.Parse(Await http.GetStringAsync(uri)) Dim q = From x In ixml.Descendants("item") Select New With {.Title = x.Element("title").Value, .Link = x.Elements("link").FirstOrDefault.Value} Gridview1.ItemsSource = q .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; } Kein ganz sauberer Code, weil mit der Struktur des Feeds die Anwendung steht und fällt. Mit 8 Zeilen Code etwas länger, dafür im das Binding im XAML um Ecken schöner. <GridView HorizontalAlignment="Left" Margin="0,0,0,0" x:Name="Gridview1" > <GridView.ItemTemplate> <DataTemplate> <Canvas Width="200" Height="200" Background="DarkBlue"> <HyperlinkButton Tag="{Binding Link}" Click="hyperlinkclick"> <TextBlock Text="{Binding Title}" Width="180" Canvas.Top="10" Canvas.Left="10" TextWrapping="Wrap"/> </HyperlinkButton> </Canvas> </DataTemplate> </GridView.ItemTemplate> </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; } Das Windows 8 METRO Beispiel ist wirklich einfach. Bisher fast zum verlieben. Wie sieht's nun aus wenn man den Internet Explorer öffnen möchte. Ganz generell gibt es keine Funktion wie CreateProcess oder ProcessStart. Das einzige was geht ist die Verbindung der Dateierweiterung mit einem Programm. Das muss der Benutzer aber per Hand erledigen. Folglich startet ein Aufruf eines Hyperlinks  per LaunchUri auch den IE 10 (METRO). Private Async Function hyperlinkclick(sender As Object, e As RoutedEventArgs) As Task Await Launcher.LaunchUriAsync(New Uri(e.OriginalSource.Tag)) 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; } Das muss man auch wieder asynchron tun. So ganz nebenbei. Der HyperlinkButton hat in WinRT anders als in Silverlight kein NavigateUri Attribut. Man muss also Tricksen und per Binding des Tag die URL mitgeben und dann im Event behandeln. Im Simulator dann folgendes Bild mit auswählbaren Elementen und clickbaren Text.