zurück zum Artikel

Von der Datenbank bis zur OberflÀche mit .NET, Teil 5: Desktop- und Browseranwendung mit Silverlight

Dr. Holger Schwichtenberg

Im vierten Teil des .NET-Tutorials entstand eine mit Windows Presentation Foundation erstellte Desktop-Anwendung. Diese soll nun in eine Silverlight-Anwendung ĂŒberfĂŒhrt werden, die ebenso wie WPF die Auszeichnungssprache XAML verwendet. Silverlight-Programme laufen wahlweise in Webbrowsern oder auf dem Windows-Desktop.

Von der Datenbank bis zur OberflÀche mit .NET, Teil 5: Desktop- und Browseranwendung mit Silverlight

Im vierten Teil des .NET-Tutorials entstand eine mit Windows Presentation Foundation erstellte Desktop-Anwendung. Diese soll nun in eine Silverlight-Anwendung ĂŒberfĂŒhrt werden, die ebenso wie WPF die Auszeichnungssprache XAML verwendet. Silverlight-Programme laufen wahlweise in Webbrowsern oder auf dem Windows-Desktop.

WĂ€hrend die erste Version von Silverlight noch auf JavaScript beruhte, kommt seit Version 2.0 eine reduzierte Variante des .NET Framework zum Einsatz. Der Entwickler kann daher C# und Visual Basic .NET sowie viele der aus .NET bekannten Klassen verwenden. Silverlight ist in den Versionen 1.0 bis 5.1 in relativ kurzer Versionsfolge in den Jahren 2007 bis 2012 erschienen. Microsoft unterstĂŒtzt mit Silverlight nicht nur Internet Explorer (ab 7.0), sondern auch Firefox (ab 3.6), Chrome (ab 12.0) sowie Safari (ab 4.0) auf Mac OS X.

Mehr Infos

Von der Datenbank bis zur OberflÀche mit .NET

Um das Beispiel mitzuprogrammieren, benötigt der Entwickler Grundkenntnisse in der C#-Syntax und der Handhabung von Visual Studio 2010. Das Tutorial verwendet die englische Version von Visual Studio, weil es in der deutschen Ausgabe einen Fehler [5] gibt, durch den sich das Beispiel nicht ohne viel manuelle Arbeit zum Laufen bringen lÀsst.

Den Quellcode zum Artikel findet man hier [6].

In den letzten Monaten wurde wiederholt berichtet, dass Microsoft nun seine Begeisterung fĂŒr Silverlight verloren habe und nun OberflĂ€chen mit HTML5 und JavaScript favorisiere. Die Anwendergemeinde vermutet, dass Microsoft nach Silverlight 5 keine neue Hauptversion mit weiteren signifikanten neuen Features mehr produzieren werde. Das letzte Wort ist aus Redmond dazu aber noch nicht gesprochen, auch wenn es Anzeichen gibt, dass Microsoft sich selbst bei eigenen Produkten (z. B. dem Windows Azure Management Portal und Visual Studio LightSwitch) von Silverlight verabschiedet.

Dennoch hat Silverlight (zumindest vorerst) weiterhin einen Platz im Portfolio der Webentwickler, denn (noch) ist die Arbeit mit Silverlight wesentlich produktiver als mit HTML5 und JavaScript. Die hohe ProduktivitĂ€t von Silverlight beruht vor allem auf der Verwendung der klassenbasierten, streng typisierten Sprachen C# und Visual Basic, der reichhaltigen Silverlight-Klassenbibliothek sowie der mĂ€chtigen Entwicklungsumgebung Visual Studio mit viel EingabeunterstĂŒtzung und Validierung zur Kompilierzeit.

Zudem gibt es bei Silverlight keine Herausforderungen mit der BrowserkompatibilitĂ€t. In jedem Browser, in dem die Silverlight-Laufzeitumgebung als Plug-in installiert ist, lĂ€uft Silverlight gleich. Das Problem ist freilich, dass bei weitem weniger Benutzer die Silverlight- als die Flash-Laufzeitumgebung in ihrem Browser installiert haben [7]. Silverlight ist daher vor allem eine gute Wahl fĂŒr Intranets und gegebenenfalls Extranets, in denen man die Installation des Silverlight-Plug-ins durchfĂŒhren beziehungsweise durchsetzen kann, aber keine gute Wahl fĂŒr das öffentliche World Wide Web. Es gab eine Portierung von Silverlight auf Unix/Linux unter dem Namen Moonlight, die aber inzwischen eingestellt [8] ist.

Zum Entwickeln mit Silverlight 5.1 muss man neben Visual Studio (Variante Professional, Premium, Ultimate oder Visual Web Developer Express) zunĂ€chst die Silverlight Tools for Visual Studio und das Silverlight Toolkit installieren, das zusĂ€tzliche Steuerelemente enthĂ€lt. Es gibt andere Erweiterungspakete fĂŒr Silverlight, zum Beispiel Silverlight Contrib [9], die aber hier nicht zum Einsatz kommen.

Dann ist eine neue "Silverlight Application" mit Namen "WWWings_SL" in der bestehenden Projektmappe "WWWings.sln" anzulegen. Beim Anlegen erscheint der Dialog in Abbildung 1.

Optionsdialog beim Anlegen einer neuen Silverlight-Anwendung in Visual Studio (die Version 5.1 erscheint nicht explizit im Dialog) (Abb. 1).

Optionsdialog beim Anlegen einer neuen Silverlight-Anwendung in Visual Studio (die Version 5.1 erscheint nicht explizit im Dialog) (Abb. 1).

Darin gilt es, ein Webserverprojekt festzulegen, dass eine HTML-Seite bereitstellt, in die die Silverlight-Anwendung geladen wird. Man könnte dafĂŒr ein neues Webserverprojekt anlegen; hier soll aber WWWings_Web verwendet werden, das in Teil 3 des Tutorials [10] fĂŒr die ASP.NET-Anwendung entstanden ist. Silverlight und ASP.NET können in einem Webprojekt koexistieren und bei Bedarf ĂŒber AJAX auch interagieren. In WWWings_Web entsteht diese HTML-Seite in zwei Varianten: Als reine HTML-Seite (WWWings_SLTestPage.html) und als ASP.NET-Webseite (WWWings_SLTestPage.aspx). Einen funktionalen Unterschied gibt es zunĂ€chst nicht. Das Silverlight-Projekt wird in eine ZIP-Paket mit Dateinamenserweiterung .aspx (WWWings_SL.xap) verpackt und wĂ€hrend des Kompiliervorgangs in das Webprojekt WWWings_Web in den Ordner "ClientBin" kopiert. Die beiden Seiten WWWings_SLTestPage.html und WWWings_SLTestPage.aspx binden das .xap-Paket ĂŒber ein <object>-Tag ein:

<object data="data:application/x-silverlight-2," type="application/
x-silverlight-2" width="100%" height="100%">
<param name="source" value="ClientBin/WWWings_SL.xap"/>
<param name="onError" value="onSilverlightError" />
<param name="background" value="white" />
<param name="minRuntimeVersion" value="5.0.61118.0" />
<param name="autoUpgrade" value="true" />
<a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=5.0.61118.0"
style="text-decoration:none">
<img src="http://go.microsoft.com/fwlink/?LinkId=161376" alt="Get Microsoft
Silverlight" style="border-style:none"/>
</a>
</object>

Außerdem findet man in den beiden Seiten ein wenig JavaScript (Routine onSilverlightError), das Fehlermeldungen von Silverlight an den Browser weiterreicht.

Nach einem Kompilieren der Lösung und dem Starten der Testseite im Browser (KontextmenĂŒ View in Browser) sollte jeweils eine leere Webseite mit einem eingebetteten leeren Silverlight-Fenster erscheinen. Ein Klick des Anwenders auf die rechte Maustaste im Browserfenster wird durch den Eintrag "Silverlight" im KontextmenĂŒ beweisen, dass tatsĂ€chlich Silverlight im Spiel ist. Aus kosmetischen GrĂŒnden soll hier WWWings_SLTestPage.html in SilverlightApp.html umbenannt werden. Um das Debugging von Visual Studio zu nutzen, muss man WWWings_Web als Standardprojekt (Set as StartUp Project) und die Seite SilverlightApp.html als Startseite (Set as Start Page) festlegen.

Nun mĂŒsste man wie beim ASP.NET- (Teil 3 [11]) und WPF-Client (Teil 4 [12]) die gemeinsamen Objekte (WWWings_GO) und die Dienst-Proxies (WWWings_Dienstproxies) referenzieren im Silverlight-Projekt. Allerdings geht das in diesem Fall nicht so einfach, denn aus einem Silverlight-Projekt heraus kann man nicht Projekte einbinden, die fĂŒr das fertige .NET Framework kompiliert wurden. Möglich wĂ€re das nur, wenn man ursprĂŒnglich eine "Portable Library" angelegt hĂ€tte. Das hĂ€tte aber andere Herausforderungen mit sich gebracht.

Der Entwickler muss fĂŒr Silverlight nun zwei neue Bibliotheken anlegen: WWWings_SLGO und WWWings_SLDienstproxies. Vorlage ist dabei "Silverlight Class Library". Die erste Bibliothek kann dabei den gleichen Code enthalten wie WWWings_GO, allerdings nicht das Entity-Framework-Modell (.edmx-Datei). Namentlich zu ĂŒbernehmen sind also die generierten EntitĂ€tsklassendateien (Flug.cs, Passagier.cs, Person.cs und Pilot.cs), die manuellen Erweiterungen der EntitĂ€tsklassen (EntitaetsklassenErweiterungen.cs) und die Dienstdefinition (Dienstschnittstellen.cs und Nachrichtenklassen.cs). Statt die Dateien zu kopieren, sollte der Entwickler sie verknĂŒpfen, damit sie bei Änderungen auch automatisch wirken. Eine VerknĂŒpfung erstellt er in Visual Studio mit Add existing Item und dann Add as link. VerknĂŒpfte Dateien erkennt er an einem kleinen Pfeil im Dateisymbol im Projektmappen-Explorer von Visual Studio. Die WWWings_SLDienstproxies muss er neu erzeugen, denn die Dienst-Proxies des .NET Framework sind nicht kompatibel mit Silverlight. Der Entwickler legt also ein Projekt WWWings_SLDienstproxies an und erzeugt ĂŒber Add Service Reference die Proxies neu, so wie in Teil 2 des Tutorials [13] beschrieben. Anders als bei WWWings_Dienstproxies landet die XML-Konfiguration nicht in der Datei app.config, sondern in ServiceReferences.ClientConfig.

Nach dem Erstellen der beiden Bibliotheken legt der Entwickler aus dem Silverlight-Projekt WWWings_SL Verweise auf die Projekte WWWings_SLDienstproxies und WWWings_SLGO-Bibliothek sowie die System.ServiceModel-Bibliothek an. Zudem muss er die Datei ServiceReferences.ClientConfig aus dem Projekt WWWings_SLDienstproxies in das Projekt WWWings_SL kopieren, da dort Adresse und Konfigurationsinformationen fĂŒr den Zugriff auf die Webservices abgelegt sind. Die Datei ServiceReferences.ClientConfig entspricht dem in Teil 3 und 4 erfolgten Übernehmen der app.config-Datei.

Nun reicht das aber immer noch nicht. Man muss in das Projekt "WWWings_Server", das die Webservices bereitstellt, noch die Datei crossdomain.xml mit dem folgenden Inhalt im Wurzelverzeichnis ablegen.

<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/
dtds/cross-domain-policy.dtd">
<cross-domain-policy>
<allow-http-request-headers-from domain="*" headers="*"/>
</cross-domain-policy>

Der Grund dafĂŒr ist eine StandardsicherheitsbeschrĂ€nkung: Die Silverlight-Anwendung wird ĂŒber das Projekt WWWings_Web vom Port 82 geladen. Die Webservices werden ĂŒber WWWings_Server aber auf Port 81 gehostet. Wenn crossdomain.xml nicht erlaubt, dass die Webservices von fremden Websites aufgerufen werden, kommt es beim Aufruf zum Fehler: "Fehler beim Senden einer Anforderung an den URI "http://localhost:81/BuchungsService.svc". Ursache ist möglicherweise, dass ohne die entsprechende domĂ€nenĂŒbergreifende Richtlinie oder mit einer nicht fĂŒr SOAP-Dienste geeigneten Richtlinie domĂ€nenĂŒbergreifend auf einen Dienst zugegriffen wurde."

Endlich ist die Architektur fertig aufgebaut, und es lÀsst sich mit dem Realisieren der Fenster beginnen. Die Architektur stellt Abbildung 2 grafisch dar, wobei hier auch die Views und di e ViewModels eingezeichnet sind, die im Folgenden entstehen.

Gesamtarchitektur der Silverlight-Anwendung (Abb. 2)

Gesamtarchitektur der Silverlight-Anwendung (Abb. 2)

Es soll versucht werden, die WPF-OberflĂ€che mit Views und ViewModel aus Teil 4 nach Silverlight zu ĂŒbernehmen – mit möglichst wenig Änderungsaufwand. DafĂŒr werden zunĂ€chst der Ordner "Ansichten" und die Klasse ActionCommand.cs aus dem WPF- in das Siliverlight-Projekt kopiert. Beim Kompilieren des Projekts erhĂ€lt man dann zahlreiche Fehler, weil in Silverlight einige Dinge anders sind.

ZunĂ€chst einmal sollte man im kompletten Programmcode alle NamensrĂ€ume WWWings_WPF durch WWWings_SL sowie WWWings_Dienstproxies durch WWWings_SLDienstproxies ersetzen. Das dient einerseits der Namenskonsistenz, andererseits aber auch dem Auffinden der speziell fĂŒr Silverlight erstellten Client-Proxies.

Die erste große HĂŒrde sind nun die Steuerelemente: Label und DataGrid gehören nicht zum Standardlieferumfang von Silverlight 5.1, sondern zum optionalen Silverlight Toolkit. Das sollte zwar laut Anweisung eingangs des Beitrags installiert sein, aber die Steuerelemente werden dennoch nicht gefunden, da die Assemblies nicht automatisch in das Silverlight-Projekt eingebunden sind. Es gilt also, nun eine Referenz auf System.Windows.Controls.Data.Input (hier liegt das Label-Steuerelement) und System.Windows.Controls.Data (hier liegt das DataGrid-Steuerelement) zu setzen. Das reicht aber noch nicht, denn auch die NamensrĂ€ume der Steuerelemente sind anders. Der Namensraum des Silverlight Toolkit ist nun im Tag <UserControl> zu ergĂ€nzen:

<UserControl x:Class="WWWings_SL.Ansichten.BuchungView"
...
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/
xaml/presentation/sdk"
...>

Anschließend sind alle Vorkommnisse der beiden Steuerelemente mit dem PrĂ€fix sdk: zu versehen, aus <Label> wird also <sdk:Label> und aus <sdk:DataGrid> wird <DataGrid>. Gleiches gilt fĂŒr die Unterelemente von DataGrid, zum Beispiel DataGridTextColumn.

GrundsĂ€tzlich funktioniert das Pattern Model View ViewModel (MVVM) zur Trennung von Layout und Programmcode in Silverlight genauso wie in WPF, also ĂŒber Command-Objekte und Properties, die den INotifyPropertyChanged-Mechanismus bereitstellen (vgl. Teil 4 des Tutorials). Im Detail gibt es aber doch (syntaktische) Unterschiede zwischen WPF und Silverlight. Der XAML-Compiler in Visual Studio beschwert sich: "The property 'Text' was not found in type 'ComboBox'”. In Silverlight gibt es diese Eigenschaft bei der ComboBox nicht; man muss stattdessen auf SelectedValue zurĂŒckgreifen und eine Zwei-Wege Bindung verwenden:

<ComboBox x:Name="C_Zielort" Grid.Row="0" Grid.Column="7" 
ItemsSource="{Binding Flughaefen}" SelectedValue="
{Binding Zielort, Mode=TwoWay}" DisplayMemberPath=""
Margin="5,0,0,0" VerticalAlignment="Center"...>

Im Fall der Bindung einfacher Zeichenketten ließe sich alternativ zu SelectedValue auch SelectedItem synonym verwenden.

Eine Zwei-Wege-Bindung ist zudem notwendig fĂŒr alle anderen DatenbindungsausdrĂŒcke, bei denen die View Daten an das ViewModel liefern soll, zum Beispiel bei der Bindung der Eigenschaft Text im Textbox Steuerelement und der Eigenschaft SelectedItem im DataGrid-Steuerelement. WĂ€hrend in WPF hier ein SelectedItem="{Binding Path=Passagier"} reichte, braucht man in Silverlight unbedingt SelectedItem= {Binding Path=Passagier, Mode=TwoWay}", da sonst die Passagier-Property im ViewModel bei Änderungen der Auswahl im DataGrid nicht befĂŒllt wird.

Auch bei den FormatierungsausdrĂŒcken in der Datenbindung gibt es Ärger. Eine geschweifte Klammer leitet in XAML eine Markup Extension ein. Wenn man eine geschweifte Klammer in einem Formatierungsausdruck verwenden will, ist in WPF ein {} voranzustellen:

Binding="{Binding Datum, StringFormat={}{0:dd.MM.yyy}}"

Allerdings mag Silverlight das {} nicht und quittiert das mit "Unexpected Token after end of Markup Extension". Dort muss man schreiben:

Binding="{Binding Datum, StringFormat=\{0:dd.MM.yyy\}}"

Die Syntax wÀre auch in WPF möglich gewesen. Im konkreten Fall wÀre ein weiterer gemeinsamer Nenner einfach folgender Ausdruck:

Binding="{Binding Datum, StringFormat=dd.MM.yyy}" 

Damit scheinen alle XAML-Probleme gelöst, denn die Fehlerliste zeigt jetzt nur noch welche im ViewModel. Leider trĂŒgt der Schein: Wenn man die Anwendung jetzt starten wĂŒrde, kĂ€me es noch zu einem Laufzeitfehler, das der XAML-Compiler von Visual Studio nicht erkannt hat: "Das Festlegen von Eigenschaft 'System.Windows.Controls.ComboBox.IsEditable' hat eine Ausnahme ausgelöst." Ursache ist hier wieder die ComboBox, die zwar genau wie in Silverlight eine Eigenschaft IsEditable ist, diese steht aber immer auf "false" und darf man nicht setzen. IsEditable="True" ist also aus den <ComboBox>-Tags zu entfernen.

<ComboBox x:Name="C_Abflugort" Grid.Row="0"  Grid.Column="4" 
ItemsSource="{Binding Flughaefen}" DisplayMemberPath=""
Margin="5,0,0,0" VerticalAlignment="Center"
SelectedItem="{Binding Abflugort, Mode=TwoWay}" />

Weiterhin wird man einen funktionalen Unterschied bei der ComboBox in der View NeuerPassagier feststellen: Egal was man als Passagierstatus auswÀhlt, die Silverlight-Anwendung speichert im Gegensatz zur WPF-Anwendung nicht den gewÀhlten Status A, B oder C, sondern den Namen der Klasse System.Windows.Controls.ComboBoxItem im Passagierstatus. Das Problem liegt in der fehlenden Eigenschaft Text, die aus einem ComboxItem den Inhalt herauszieht. Sie wurde ja oben durch SelectedValue ersetzt. SelectedItem hingegen liefert ein Objekt vom Typ ComboxItem, da in XAML die Auswahloptionen der ComboBox so definiert sind:

  <ComboBox  Name="C_Passagierstatus" Grid.Row="3" Grid.Column="1" 
Height="23" HorizontalAlignment="Left" VerticalAlignment="Top"
Width="50" ItemsSource="{Binding}" SelectedValue="
{Binding Passagier.PassagierStatus}" >
<ComboBoxItem Content="A" />
<ComboBoxItem Content="B" />
<ComboBoxItem Content="C" />
</ComboBox>

Das ComboBoxItem konvertiert dann Silverlight durch Aufruf der Standardmethode ToString() in einen Text. Nun hat aber Microsoft sogar eine so primitive Funktion wie ComboxItem.ToString() unterschiedlich in WPF und Silverlight realisiert. Das sieht man auch an folgender C#-Befehlsfolge:

var i = new System.Windows.Controls.ComboBoxItem();
i.Content = "A";
var s = i.ToString();

Sie liefert in WPF s = "System.Windows.Controls.ComboBoxItem: A"; in Silverlight aber nur s = "System.Windows.Controls.ComboBoxItem". In Silverlight muss man also nun fĂŒr die Auswahl des Passagierstatus einen anderen Weg gehen. Eine Lösung besteht darin, im XAML in der NeuerPassagierView die ComboBox-Inhalte statt mit ComboBoxItem-Tags <ComboBoxItem Content="A" /> mit einfachen Zeichenketten in Form von <sys:String>A</sys:String> zu befĂŒllen:

<ComboBox  Name="C_Passagierstatus" Grid.Row="3" Grid.Column="1" 
Height="23" HorizontalAlignment="Left" VerticalAlignment="Top"
Width="50" SelectedItem="{Binding Passagier.PassagierStatus,
Mode=TwoWay}" >
<sys:String>A</sys:String>
<sys:String>B</sys:String>
<sys:String>C</sys:String>
</ComboBox>

Dann liefert SelectedItem direkt eine Zeichenkette, und alles lĂ€uft wieder. Voraussetzung fĂŒr <sys:String> ist, dass der Entwickler vorher den Namensraum sys auch im Wurzel-Tag der View unter Angabe der Assembly deklariert hat:

<UserControl x:Class="WWWings_SL.Ansichten.NeuerPassagierView"
xmlns:sys="clr-namespace:System;assembly=mscorlib" ... >

Das wÀre auch in WPF möglich gewesen. Listing 3 [14] zeigt die komplette Silverlight-Variante von NeuerPassagierView.

Nach diesen Änderungen auf XAML-Ebene sieht man aber immer noch eine lange Fehlerliste, in der sich Visual Studio beschwert, dass die in den ViewModels verwendete Dienstproxy-Klasse BuchungsServiceClient nicht die Methoden GetFlug(), GetPassagier(), CreateBuchung() etc. und nicht mal Close() kennt. Schaut man sich die Klasse BuchungsServiceClient an, findet man dort stattdessen GetFlugAsync(), GetPassagierAsync(), CreateBuchungAsync() etc., denn Silverlight unterstĂŒtzt nur asynchrone Aufrufe.

Zu jeder Async-Methode gibt es dann ein Completed-Ereignis. Das bedeutet: Beim Aufruf der Async-Methode werden die Kontrolle sofort an den Aufrufer zurĂŒckgegeben und die Webservice-Operation in einem Hintergrund-Thread gestartet. Wenn dieser fertig ist (erfolgreich oder mit Fehler), wird die Ereignisbehandlungsroutine aufgerufen, die dem Completed-Ereignis zugewiesen wurde. Wenn der Aufruf erfolgreich war, enthĂ€lt die Property Result des zweiten Ereignishandlungsroutinenparameters das RĂŒckgabeobjekt. Im Fehlerfall liefert das Property Error das Exception-Objekt.

Das bedeutet nun, dass die ViewModels erheblich umzustellen sind, denn es ist fĂŒr jeden Webservice-Aufruf eine Ereignisbehandlungsroutine als Methode zu realisieren. Diese Ereignisbehandlungsmethode kann explizit oder anonym sein. Listing 2 [15] zeigt zu Anschauungszwecken bewusst beide möglichen Syntaxformen. Wenn man in Visual Studio nach der Eingabe von += hinter einem Ereignis zweimal die Tabulator-Taste drĂŒckt, erstellt die Entwicklungsumgebung automatisch eine explizite Ereignisbehandlungsmethode. Ein Beispiel sieht man in Listing 1 [16] in der Methode FlugSuchen() beim Aufruf der Webservice-Operation GetFlugAsync(). Kurz darunter in FlugSuchen() erfolgt der Aufruf von GetFluegeAsync() mit einer anonymen Methode, die "inline" in geschweiften Klammern innerhalb von FlugSuchen() realisiert ist. Dieser Inline-Code, bei dessen Anlegen Visual Studio nicht explizit mithilft, wird beim Eintreten des Ereignisses abgearbeitet. Die umgebende Routine ist davon aber nicht mehr berĂŒhrt, sofern man nicht explizit auf eine Variable der umgebenden Routine zugreift. Das ist möglich, und man spricht dann von einer sogenannten Closure. Die anonymen Methoden sind also etwas mĂ€chtiger. FĂŒr welche der beiden Syntaxformen man sich im vorliegenden Fall entscheidet, ist aber reine Geschmackssache. Listing 4 [17] zeigt die Silverlight-Variante des NeuerPassagierViewModel, in dem ebenfalls die asynchronen Aufrufe realisiert sind.

Das Hauptfenster besteht in der WPF-Anwendung aus MainWindow.xaml, MainWindows.xaml.cs und MainWindowViewModel.cs. Die drei Dateien lassen sich aus dem WPF-Projekt ĂŒbernehmen; es sind aber auch hierbei einige Änderungen notwendig.

Die Startdatei in Silverlight heißt im Standard MainPage.xaml statt MainWindows.xaml. Möchte der Entwickler den gleichen Datei- und Klassennamen wie in WPF verwenden, muss er in der App.xaml.cs-Datei die Routine Application_Startup verĂ€ndern: MainPage ist zu ersetzen durch MainWindow.

  private void Application_Startup(object sender, StartupEventArgs e)
{
this.RootVisual = new MainWindow();
}

Nun sind erst mal die Änderungen in MainWindow.xaml an der Reihe. Das Wurzelelement fĂŒr eine Silverlight-Anwendung ist <UserControl>. <RibbonWindow>, das in WPF zum Einsatz kam, ist auszutauschen.

FĂŒr das Hauptfenster gilt es zu beachten, dass es in Silverlight kein offizielles Ribbon-Steuerelement von Microsoft gibt, dafĂŒr aber auf Codeplex ein Beispiel [18], das aber fĂŒr Silverlight 3 ist und seitdem nicht weiterentwickelt wurde. DarĂŒber hinaus gibt es kommerzielle Anbieter solcher Steuerelemente (z. B. Sandribbon von divelements [19], RibbonView von Telerik [20], xamribbon von Infragistics [21] und das Silverlight Ribbon von Devexpress [22]. Das Tutorial soll aber ohne weitere Zusatzkosten lauffĂ€hig sein, sodass die Silverlight-Lösung auf das Ribbon verzichtet. An dessen Stelle soll eine einfache Befehlsleiste mit den SchaltflĂ€chen "Neuer Passagier" und "Ende" treten (siehe Abb. 3 und 4). Die Befehlsleiste wird realisiert durch ein <StackPanel>-Element mit einem <Image>- und zwei <Button>-Elementen. Alle anderen Aktionen sind ja innerhalb der beiden Views "BuchungsView" und "NeuerPassagierView" bereits als SchaltflĂ€chen hinterlegt.

Buchungsmaske in der Silverlight-Lösung mit einfacher SchaltflÀchenleiste statt Ribbon-Steuerelement (Abb. 3)

Buchungsmaske in der Silverlight-Lösung mit einfacher SchaltflÀchenleiste statt Ribbon-Steuerelement (Abb. 3)
Neue Passagier-Ansicht in der Silverlight-Lösung (Abb. 4)

Neue Passagier-Ansicht in der Silverlight-Lösung (Abb. 4)

Als NĂ€chstes schlagen wieder syntaktische Unterschiede beim WPF- und Silverlight-XAML zu: Visual Studio beschwert sich in den Datenvorlagen ĂŒber <DataTemplate DataType="{x:Type Ansichten:BuchungViewModel}">. In Silverlight ist die Syntax anders und einfacher: <DataTemplate DataType="Ansichten:BuchungViewModel">. Leider wĂŒrde das in WPF nicht funktionieren. Listing 5 [23] zeigt MainWindows.xaml mit verĂ€ndertem Wurzelelement, Ersetzung des Ribbon-Steuerelement durch eine eigene SchaltflĂ€chenleisten und den Änderungen in den Datenvorlagen.

WĂ€hrend die MainWindow.xaml.cs so bleiben kann wie in WPF, muss das MainWindowViewModel.cs etwas anders aufgebaut sein (siehe Listing 6 [24]). Es gibt keine Ribbon-Registerkarten mehr, und folglich braucht man dafĂŒr auch keine Properties im ViewModel. Stattdessen wird zum Umschalten zwischen den beiden Views nun eine SchaltflĂ€che verwendet, die an das zusĂ€tzliche Command-Objekt NeuerPassagierCommand gebunden ist.

Bei der Realisierung von Ende() muss man etwas Ă€ndern, denn Application.Current.MainWindow.Close() ist nur verfĂŒgbar, wenn die Silverlight-Anwendung außerhalb des Browser lĂ€uft. Innerhalb des Browsers kann die Silverlight-Anwendung den Browser nicht eigenstĂ€ndig schließen, sondern nur das umgebende Browser-Fenster "höflich" dazu auffordern. Das Fenster wird aber typischerweise erst den Benutzer fragen, ob er damit einverstanden ist. Daher prĂŒft Ende(), ob Application.Current.IsRunningOutOfBrowser "true" oder "false" ist.

Eingangs des Beitrags wurde erwĂ€hnt, dass Silverlight-Anwendungen außerhalb des Webbrowsers laufen können. Die Standardeinstellung ist der Start im Webbrowser. Der Entwickler einer Silverlight-Anwendung kann aber festlegen, dass diese außerhalb des Browser betreibbar ist. Das stellt der Entwickler im Silverlight-Projekt in den Projekteigenschaft auf der Registerkarte "Silverlight" ein, indem er Enable running application out of browser wĂ€hlt und dann die "Out-of-Browser Settings" festlegt (siehe Abb. 5).

Einstellungen fĂŒr den Betrieb einer Silverlight-Anwendung außerhalb des Webbrowsers (Abb. 5).

Einstellungen fĂŒr den Betrieb einer Silverlight-Anwendung außerhalb des Webbrowsers (Abb. 5).

Mit den Einstellungen startet die Anwendung im Debugger nun als eigenstĂ€ndiges Windows-Fenster (siehe Abb. 6), wobei in Wirklichkeit hier Teile des Internet Explorer im Spiel sind; nur dessen MenĂŒleisten sind ausgeblendet. Die Funktionen sind weiterhin gewĂ€hrleistet, solange die Webservices verfĂŒgbar sind.

Silverlight-Anwendung außerhalb des Webbrowsers (Abb. 6)

Silverlight-Anwendung außerhalb des Webbrowsers (Abb. 6)

Die Anwendung kann man alternativ wie bisher ĂŒber die Webseite http://localhost:82/SilverlightApp.html aufrufen, und dort lĂ€uft sie weiterhin im Browser. Der Benutzer hat aber im KontextmenĂŒ der Silverlight Anwendung unter dem Punkt "Silverlight" die zusĂ€tzliche Option WWWings-Buchung auf diesem Computer installieren. Danach sieht der Anwender ein Installationsfenster, in dem er festlegen kann, ob er im StartmenĂŒ und/oder auf dem Windows-Desktop eine VerknĂŒpfung zur Anwendung möchte, um diese dann zukĂŒnftig direkt ohne den vorherigen Browseraufruf zu starten. Die OK-SchaltflĂ€che ist erst nach drei Sekunden Bedenkzeit verfĂŒgbar. Die Deinstallation der Anwendung ist spĂ€ter sowohl auf der Webseite im KontextmenĂŒ (Diese Anwendung entfernen) als auch in der Windows Systemsteuerung (Programme | Software) möglich.

Im Unternehmenseinsatz ist eine solche Selbstinstallation der Silverlight-Anwendung durch jeden einzelnen Benutzer nicht der gewĂŒnschte Weg. Dort ist eine Verteilung mit herkömmlichen Methoden wie Batch-Datei/Skript, Setup/MSI oder Click-Once-Deployment [25] auf Windows-Systemen geboten. Mit etwas Hintergrundwissen kann man diese WĂŒnsche realisieren. Das wichtigste Instrument ist SLLauncher.exe. Das Programm wird mit dem Silverlight-Plug-in unter Windows installiert. Wenn ein Benutzer eine Silverlight-Anwendung lokal installiert, entsteht eine VerknĂŒpfung zur Anwendung im StartmenĂŒ mit der App-ID der installierten Anwendung als Parameter. SLLauncher ermöglicht aber auch, mit dem Parameter /installer den Installationsvorgang anzustoßen. Der folgende Quellcode zeigt, wie man die vom Projekt WWWings_SL erzeugte WWWings_SL.xap-Datei per Windows Batch in das lokale Dateisystem kopieren, dort installiert und startet. Den Trick fĂŒr den Installationsweg ĂŒber das Click-Once-Deployment beschreibt ein Blog-Eintrag [26].

echo "Kopieren"
copy "\\Server\Install\WWWings_SL.xap" "c:\SLApps\WWWings_SL.xap"
echo "Installieren"
"C:\Program Files (x86)\Microsoft Silverlight\sllauncher.exe"
/install:'c:\ SLApps\WWWings_SL.xap' /origin:"http://localhost:82/
ClientBin/WWWings_SL.xap"
/shortcut:desktop+startmenu /overwrite
echo "Starten"
"C:\Program Files (x86)\Microsoft Silverlight\sllauncher.exe"
/emulate:"c:\ SLApps\WWWings_SL.xap" /origin:"http://localhost:82/
ClientBin/WWWings_SL.xap" /overwrite
echo "LĂ€uft!"

Silverlight-Anwendungen laufen im Standard in einer Sandbox, das heißt, sie sind vom Betriebssystem, Hardwareressourcen und anderen Anwendungen abgeschottet. Im Dateisystem kann jede Silverlight Anwendung nur in einem speziell ihr zugewiesenen Teil des Benutzerprofils Dateien erzeugen oder lesen (Isolated Storage). So können Silverlight-Anwendung nicht ausspionieren und keinen Schaden anrichten. Es gibt aber auch die Möglichkeit, Silverlight-Anwendungen unter erhöhten Rechten zu betreiben (man spricht von Elevated Trust oder Trusted Application). Dann können diese Silverlight-Anwendungen das ganze Dateisystem verwenden und ĂŒber das Component Object Model (COM) mit den Betriebssystemkomponenten (z. B. dem Active Directory) oder anderen Anwendungen (z. B. Office-Produkten) interagieren. Im konkreten Fall könnte die Flugbuchungs-Silverlight-Anwendung zum Beispiel die Flugliste nach Excel importieren, eine BuchungsbestĂ€tigung als Word-Dokument erzeugen und dem Passagier per E-Mail zustellen. Das soll im Tutorial aber nicht mehr behandelt werden, denn auch die ASP.NET- und WPF-Anwendung in Teil 3 und 4 des Tutorials hatten nicht die FĂ€higkeiten.

Die Umstellung der WPF-Anwendung auf Silverlight hat gezeigt: Die beiden OberflĂ€chenframeworks sind sich so nah und dann in Details doch so fern. Wenn man zu Beginn der Entwicklung weiß, dass man auch Silverlight bedienen will, kann man sicherlich an einigen Stellen auf dem kleinsten gemeinsamen Nenner programmieren. Aber an anderen Stellen gibt es diesen gemeinsamen Nenner nicht, sodass man mit einer XAML-Codebasis kaum erreichen kann, beide OberflĂ€chen gleichzeitig zu bedienen. Definitiv ist der Weg von WPF zu Silverlight ein Vielfaches leichter und schneller als von WPF zu HTML5/JavaScript, denn dort ist sowohl die OberflĂ€chenbeschreibungssprache als auch die Sprache der ViewModel ganz anders.

Dr. Holger Schwichtenberg
leitet das Expertenteam von www.IT-Visions.de [27], das Beratung und Schulungen im Umfeld von .NET und Microsofts Serverprodukten anbietet. Er hĂ€lt VortrĂ€ge auf Fachkonferenzen und ist Autor zahlreicher FachbĂŒcher.

Hinweis: Ein deutsches Buch zu Silverlight 5 ist bisher nicht erschienen.

<UserControl x:Class="WWWings_SL.Ansichten.BuchungView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WWWings_SL.Ansichten"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/
presentation/sdk"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="500">


<!--############# Grundaufteilung des Bildschirms-->
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="10" />
<RowDefinition Height="Auto" />
<RowDefinition Height="10" />
<RowDefinition Height="*" />
<RowDefinition Height="10" />
<RowDefinition Height="Auto" />
<RowDefinition Height="10" />
<RowDefinition Height="*" />
<RowDefinition Height="10" />
<RowDefinition Height="Auto" />
<RowDefinition Height="10" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="10"/>
</Grid.ColumnDefinitions>

<!-- ### Bereich 1: Flugsuche ######### -->
<Border Grid.Row="1" Grid.Column="1" Padding="10" BorderBrush="#FF3B5383"
BorderThickness="1" CornerRadius="4">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="42" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="20" />
<ColumnDefinition Width="74" />
<ColumnDefinition />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="74" />
<ColumnDefinition />
<ColumnDefinition Width="20" />
<ColumnDefinition Width="70" />
</Grid.ColumnDefinitions>

<TextBlock Grid.Row="0" Grid.Column="0" Text="Flug ID:"
VerticalAlignment="Center"/>
<TextBox x:Name="C_Flugnummer" Grid.Row="0" Grid.Column="1" Text="
{Binding FlugNummer, Mode=TwoWay}" Margin="5,0,0,0"
VerticalAlignment="Center"
/>

<TextBlock Grid.Row="0" Grid.Column="3" Text="Abflughafen:"
VerticalAlignment="Center"/>
<ComboBox x:Name="C_Abflugort" Grid.Row="0" Grid.Column="4"
ItemsSource="{Binding Flughaefen}" DisplayMemberPath=""
Margin="5,0,0,0" VerticalAlignment="Center"
SelectedItem="{Binding Abflugort, Mode=TwoWay}"
/>

<TextBlock Grid.Row="0" Grid.Column="6" Text="Zielflughafen:"
VerticalAlignment="Center"/>
<ComboBox x:Name="C_Zielort" Grid.Row="0" Grid.Column="7"
ItemsSource="{Binding Flughaefen}" SelectedItem="
{Binding Zielort, Mode=TwoWay}" DisplayMemberPath=""
Margin="5,0,0,0" VerticalAlignment="Center"
/>


<Button x:Name="C_FlugSuchen" Grid.Row="0" Grid.Column="9" Content="Suchen"
Command="{Binding Path=FlugSuchenCommand}" />
</Grid>
</Border>

<!-- ### Flugliste ########### -->
<sdk:DataGrid x:Name="C_GefundeneFluege" Grid.Row="3" Grid.Column="1"
AutoGenerateColumns="False" ItemsSource="{Binding Path=Fluege}"
SelectedItem="{Binding Path=Flug, Mode=TwoWay}">
<sdk:DataGrid.Columns>
<sdk:DataGridTextColumn Header="Abflugort" Binding="{Binding Abflugort}"/>
<sdk:DataGridTextColumn Header="Zielort" Binding="{Binding Zielort}"/>
<sdk:DataGridTextColumn Header="Datum" Binding="
{Binding Datum, StringFormat=dd.MM.yyy}"/>
<sdk:DataGridTextColumn Header="PlÀtze" Binding="{Binding Plaetze}"/>
<sdk:DataGridTextColumn Header="Freie PlÀtze"
Binding="{Binding FreiePlaetze}"/>
</sdk:DataGrid.Columns>
</sdk:DataGrid>

<!-- ### Bereich 2: Passagiersuche #########-->
<Border Grid.Row="5" Grid.Column="1" Padding="10" BorderBrush="#FF3B5383"
BorderThickness="1" CornerRadius="4">
<Grid x:Name="detailsGrid">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="4" />
<RowDefinition Height="Auto" />
<RowDefinition Height="4" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70" />
<ColumnDefinition />
<ColumnDefinition Width="20" />
<ColumnDefinition Width="70" />
<ColumnDefinition />
<ColumnDefinition Width="20" />
<ColumnDefinition Width="70" />
</Grid.ColumnDefinitions>

<TextBlock Grid.Row="2" Text="Passagier ID:" VerticalAlignment="Center"/>
<TextBox x:Name="C_PassagierID" Grid.Row="2" Grid.Column="1"
Text="{Binding PassagierID, Mode=TwoWay}" Margin="5,0,0,0"
VerticalAlignment="Center"
/>

<TextBlock Grid.Row="2" Grid.Column="3" Text="Nachname:"
VerticalAlignment="Center"/>
<TextBox x:Name="C_Passagiername" Grid.Row="2" Grid.Column="4"
Text="{Binding PassagierName, Mode=TwoWay}" Margin="5,0,0,0"
VerticalAlignment="Center"
/>

<Button x:Name="C_PassagierSuchen" Grid.Row="2" Grid.Column="6"
Content="Suchen" Command="{Binding PassagierSuchenCommand}" />


</Grid>
</Border>

<!-- ### Passagierliste ########### -->
<sdk:DataGrid x:Name="C_GefundenePassagiere" Grid.Row="7"
Grid.Column="1" AutoGenerateColumns="False"
ItemsSource="{Binding Path=Passagiere}" SelectedItem="
{Binding Path=Passagier, Mode=TwoWay}">
<sdk:DataGrid.Columns>
<sdk:DataGridTextColumn Header="ID" Binding="{Binding ID}"/>
<sdk:DataGridTextColumn Header="Vorname" Binding="{Binding Vorname}"/>
<sdk:DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
<sdk:DataGridTextColumn Header="Geburtsdatum" Binding="
{Binding Geburtsdatum, StringFormat=dd.MM.yyy}"/>
<sdk:DataGridTextColumn Header="Status" Binding="{Binding
PassagierStatus}"/>
</sdk:DataGrid.Columns>
</sdk:DataGrid>

<!-- ### Bereich 3: Buchen-Aktion ######################## -->
<Border Grid.Row="9" Grid.Column="1" Padding="10" BorderBrush="#FF3B5383"
BorderThickness="1" CornerRadius="4"
>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="20" />
<ColumnDefinition Width="70" />
</Grid.ColumnDefinitions>

<TextBlock Name="C_BuchenErgebnis" Grid.Row="0" Grid.Column="0"
Text="{Binding BuchenErgebnis}" Foreground="{Binding
BuchenErgebnisFarbe}" Margin="5,0,0,0"
VerticalAlignment="Center"/>

<Button Grid.Row="0" Grid.Column="2" Content="Buchen" x:Name="C_Buchen"
Command="{Binding BuchenCommand}" />

</Grid>
</Border>

</Grid>
</UserControl>
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows.Media;
using WWWings_SLDienstproxies.WWWingsServer;
using WWWings_GO;

namespace WWWings_SL.Ansichten
{
public partial class BuchungViewModel : INotifyPropertyChanged
{
/// ############ Befehle ################
public ActionCommand FlugSuchenCommand { get; set; }
public ActionCommand PassagierSuchenCommand { get; set; }
public ActionCommand BuchenCommand { get; set; }

/// ################ Daten (Properties) ################
ObservableCollection<string> flughaefen;
public ObservableCollection<string> Flughaefen // Liste der Orte fĂŒr
// Abflug- und Zielauswahl
{
get { return flughaefen; }
set { flughaefen = value; OnPropertyChanged("Flughaefen"); }
}

private ObservableCollection<Flug> fluege { get; set; }
public ObservableCollection<Flug> Fluege // gefundene FlĂŒge
{
get { return fluege; }
set { fluege = value; this.OnPropertyChanged("Fluege"); }
}

private ObservableCollection<Passagier> passagiere;
public ObservableCollection<Passagier> Passagiere // gefundene Passagiere
{
get { return passagiere; }
set { passagiere = value; this.OnPropertyChanged("Passagiere"); }
}

Flug flug;
public Flug Flug // GewÀhlter Flug
{
get { return flug; }
set
{
flug = value; OnPropertyChanged("Flug");
BuchenCommand.IsEnabled = (Flug != null && Passagier != null);
}
}

Passagier passagier;
public Passagier Passagier // gewÀhlter Passagier
{
get { return passagier; }
set
{
passagier = value; OnPropertyChanged("Passagier");
BuchenCommand.IsEnabled = (Flug != null && Passagier != null);
}
}

private string flugNummer;
public string FlugNummer // eingegebene Flugnummer
{
get { return flugNummer; }
set { flugNummer = value; OnPropertyChanged("FlugNummer"); }
}

private string abflugort;
public string Abflugort // ausgewÀhlter Abflugort
{
get { return abflugort; }
set { abflugort = value; OnPropertyChanged("Abflugort"); }
}

private string zielort;
public string Zielort // ausgewÀhlter Zielort
{
get { return zielort; }
set { zielort = value; OnPropertyChanged("Zielort"); }
}

private string passagierID;
public string PassagierID // eingegebene ID
{
get { return passagierID; }
set { passagierID = value; OnPropertyChanged("PassagierID"); }
}

private string passagiername;
public string PassagierName // eingegebener Name
{
get { return passagiername; }
set { passagiername = value; OnPropertyChanged("PassagierName"); }
}

private string buchenErgebnis;
public string BuchenErgebnis // Textausgabe nach Buchung
{
get { return buchenErgebnis; }
set { buchenErgebnis = value; OnPropertyChanged("BuchenErgebnis"); }
}

private SolidColorBrush buchenErgebnisFarbe;
public SolidColorBrush BuchenErgebnisFarbe // Textfarbe der Textausgabe
// nach Buchung
{
get { return buchenErgebnisFarbe; }
set { buchenErgebnisFarbe = value; OnPropertyChanged("BuchenErgebnisFarbe"); }
}

/// ################ Konstruktor ################

public BuchungViewModel(Passagier passagier = null)
{
FlugSuchenCommand = new ActionCommand(FlugSuchen);
PassagierSuchenCommand = new ActionCommand(PassagierSuchen);
BuchenCommand = new ActionCommand(Buchen);
BuchenCommand.IsEnabled = false;

BuchungsServiceClient client = new BuchungsServiceClient();
client.GetFlughaefenCompleted += delegate(object sender,
GetFlughaefenCompletedEventArgs e)
{ Flughaefen = e.Result; };
client.GetFlughaefenAsync();

if (passagier != null)
{
Passagiere = new ObservableCollection<WWWings_GO.Passagier>()
{ passagier };
Passagier = passagier;
}
// nur zum einfacheren Testen!
Abflugort = "Berlin";
PassagierName = "MĂŒller";
}

/// ################ Aktion 1 ################

private void FlugSuchen()
{
BuchungsServiceClient client = new BuchungsServiceClient();

int FNr;
if (Int32.TryParse(FlugNummer, out FNr))
{
client.GetFlugCompleted += new EventHandler<GetFlugCompletedEventArgs>
(client_GetFlugCompleted);
client.GetFlugAsync(FNr);
}
else
{
client.GetFluegeCompleted += new EventHandler<GetFluegeCompletedEventArgs>
(client_GetFluegeCompleted);
client.GetFluegeAsync(Abflugort, Zielort);
}
}

void client_GetFluegeCompleted(object sender, GetFluegeCompletedEventArgs e)
{
Fluege = e.Result;
}

void client_GetFlugCompleted(object sender, GetFlugCompletedEventArgs e)
{
var Flug = e.Result;
if (Flug != null) Fluege = new ObservableCollection<Flug> { Flug };
else Fluege = new ObservableCollection<Flug> { };
}

/// ################ Aktion 2 ################

private void PassagierSuchen()
{
BuchungsServiceClient client = new BuchungsServiceClient();
int PID;
if (Int32.TryParse(PassagierID, out PID))
{
client.GetPassagierCompleted += new EventHandler
<GetPassagierCompletedEventArgs>(client_GetPassagierCompleted);
client.GetPassagierAsync(PID);
}
else
{
client.GetPassagiereCompleted += new EventHandler
<GetPassagiereCompletedEventArgs>(client_GetPassagiereCompleted);
client.GetPassagiereAsync(PassagierName);
}
}


void client_GetPassagierCompleted(object sender,
GetPassagierCompletedEventArgs e)
{
var Passagier = e.Result;
if (Passagier != null) Passagiere = new ObservableCollection<Passagier>
{ Passagier };
else Passagiere = new ObservableCollection<Passagier>();
}

void client_GetPassagiereCompleted(object sender,
GetPassagiereCompletedEventArgs e)
{
passagiere = e.Result;

if (Passagiere != null) Passagiere = new
ObservableCollection<WWWings_GO.Passagier>(Passagiere.OrderBy(x
=> x.GanzerName));

}

/// ################ Aktion 3 ################

private void Buchen()
{
if (Flug == null || Passagier == null) return;
BuchungsServiceClient client = new BuchungsServiceClient();
client.CreateBuchungCompleted += new
EventHandler<CreateBuchungCompletedEventArgs>
(client_CreateBuchungCompleted);
client.CreateBuchungAsync(Flug.ID, Passagier.ID);
}

void client_CreateBuchungCompleted(object sender,
CreateBuchungCompletedEventArgs e)
{

if (e.Error == null)
{

string ergebnis = e.Result;

if (ergebnis == "OK")
{
BuchenErgebnis = "Buchung erfolgreich!";
BuchenErgebnisFarbe = new SolidColorBrush(Colors.Green);
}
else
{
BuchenErgebnis = ergebnis;
BuchenErgebnisFarbe = new SolidColorBrush(Colors.Red);
}

}
else
{
BuchenErgebnis = "Fehler beim Aufruf der Buchungsfunktion: "
+ e.Error.Message;
BuchenErgebnisFarbe = new SolidColorBrush(Colors.Red);
}

}

/// ################ INotifyPropertyChanged ################

#region INotifyPropertyChanged Members

public event PropertyChangedEventHandler PropertyChanged;


protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion

}
}
<UserControl x:Class="WWWings_SL.Ansichten.NeuerPassagierView"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/
markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/
presentation/sdk"

mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid>

<!--################ Grundaufteilung des Bildschirms-->

<Grid.RowDefinitions>
<RowDefinition Height="40" />
<RowDefinition Height="40"/>
<RowDefinition Height="40"/>
<RowDefinition Height="40"/>
<RowDefinition Height="40"/>
<RowDefinition Height="40"/>

</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120" />
<ColumnDefinition />
</Grid.ColumnDefinitions>


<!--################ Eingabebereich-->
<sdk:Label Name="C_LabelVorname" Grid.Row="0" Content="Vorname"
Height="28" HorizontalAlignment="Left" VerticalAlignment="Top" />
<TextBox Name="C_Vorname" Grid.Column="1" Height="23"
HorizontalAlignment="Left" VerticalAlignment="Top"
Width="200" Text="{Binding Passagier.Vorname, Mode=TwoWay}" />

<sdk:Label Name="C_LabelName" Grid.Row="1" Content="Name" Height="28"
HorizontalAlignment="Left" VerticalAlignment="Top" />
<TextBox Name="C_Name" Grid.Row="1" Grid.Column="1" Height="23"
HorizontalAlignment="Left" VerticalAlignment="Top"
Width="200" Text="{Binding Passagier.Name, Mode=TwoWay}" />

<sdk:Label Name="C_LabelGeburtsdatum" Grid.Row="2"
Content="Geburtsdatum" Height="28"
HorizontalAlignment="Left" VerticalAlignment="Top" />
<TextBox Name="C_Geburtsdatum" Grid.Row="2" Grid.Column="1"
Height="23" HorizontalAlignment="Left"
VerticalAlignment="Top" Width="200" Text="
{Binding Passagier.Geburtsdatum,
Mode=TwoWay,StringFormat=dd.MM.yyy}" />

<sdk:Label Name="C_LabelPassagierstatus" Grid.Row="3"
Content="Passagier-status" Height="28" HorizontalAlignment="Left"
VerticalAlignment="Top" />
<ComboBox Name="C_Passagierstatus" Grid.Row="3" Grid.Column="1"
Height="23" HorizontalAlignment="Left"
VerticalAlignment="Top" Width="50" SelectedItem="
{Binding Passagier.PassagierStatus, Mode=TwoWay}" >
<sys:String>A</sys:String>
<sys:String>B</sys:String>
<sys:String>C</sys:String>
</ComboBox>

<!--################ Aktionsbereich mit StackPanel-Anordnung -->
<StackPanel Grid.Column="1" Grid.Row="5" Orientation="Horizontal">
<Button Name="C_Speichern" Content="Speichern" Height="23"
HorizontalAlignment="Left" VerticalAlignment="Top" Width="75"
Command="{Binding SpeichernCommand}" />
<Button Name="C_Abbrechen" Content="Abbrechen" Height="23"
HorizontalAlignment="Left" VerticalAlignment="Top" Width="75"
Command="{Binding AbbrechenCommand}"/>
<sdk:Label Name="C_FehlerMeldung" Grid.Row="5" Grid.ColumnSpan="2"
Height="28" HorizontalAlignment="Left" VerticalAlignment="Top"
Width="400" Foreground="Red" Content="{Binding
FehlerMeldung}" />
</StackPanel>
</Grid>
</UserControl>
using System.ComponentModel;
using System.Collections.ObjectModel;
using WWWings_GO;
using WWWings_SLDienstproxies.WWWingsServer;
using System;

namespace WWWings_SL.Ansichten
{
public partial class NeuerPassagierViewModel : INotifyPropertyChanged
{

/// ################ Befehle ################
public ActionCommand SpeichernCommand { get; set; }
public ActionCommand AbbrechenCommand { get; set; }

/// ################ Ereignisse ################
public event Action<Passagier> NeuerPassagierViewModelExit;

/// ################ Daten (Properties) ################
public Passagier passagier;
public Passagier Passagier // Das neue Passagier-Objekt
{
get { return passagier; }
set { passagier = value; OnPropertyChanged("Passagier"); }
}

public string fehlerMeldung;
public string FehlerMeldung // StatusMeldung nach Speichern
{
get { return fehlerMeldung; }
set { fehlerMeldung = value; OnPropertyChanged("FehlerMeldung"); }
}

/// ################ Konstruktor ################

public NeuerPassagierViewModel()
{
SpeichernCommand = new ActionCommand(Speichern);
AbbrechenCommand = new ActionCommand(Abbrechen);

// Passagier instanziieren
Passagier = new Passagier();

// Nur zum Test
Passagier.Vorname = "Max";
Passagier.Name = "Mustermann";
Passagier.Geburtsdatum = DateTime.Now.Date;
Passagier.PassagierStatus = "A";
}

/// ################ Aktion 1 ################

private void Speichern()
{

// Neuen Passagier speichern
ObservableCollection<Passagier> GeÀndertePassagiere =
new ObservableCollection<Passagier>() { Passagier };
string Statistik;

BuchungsServiceClient client = new BuchungsServiceClient();
client.SavePassagierSetCompleted +=
new EventHandler<SavePassagierSetCompletedEventArgs>
(client_SavePassagierSetCompleted);
client.SavePassagierSetAsync(GeÀndertePassagiere);
}

/// ################ Aktion 2 ################


private void client_SavePassagierSetCompleted(object sender,
SavePassagierSetCompletedEventArgs e)
{

if (e.Error == null)
{
var antwort = e.Result;

if (antwort.Count == 1)
{
// Der erste neue Passagier muss der angelegte sein,
// der nun auch die ID enthÀlt!
Passagier = antwort[0];
FehlerMeldung = "";
if (NeuerPassagierViewModelExit != null)
NeuerPassagierViewModelExit(Passagier);
}
}
else
{
FehlerMeldung = e.Error.Message;
if (NeuerPassagierViewModelExit != null)
NeuerPassagierViewModelExit(null);
}
}


void Abbrechen()
{
if (NeuerPassagierViewModelExit != null)
NeuerPassagierViewModelExit(null);
}

/// ################ INotifyPropertyChanged ################

#region INotifyPropertyChanged Members

public event PropertyChangedEventHandler PropertyChanged;


protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
<UserControl x:Class="WWWings_SL.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Name="Main"
xmlns:Ansichten="clr-namespace:WWWings_SL.Ansichten"
d:DesignHeight="300" d:DesignWidth="400"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/
xaml/presentation/sdk">


<!--################ Hier werden die Views und die ViewModel durch
ein DataTemplate zusammengebunden-->
<UserControl.Resources>
<DataTemplate DataType="Ansichten:BuchungViewModel">
<Ansichten:BuchungView></Ansichten:BuchungView>
</DataTemplate>
<DataTemplate DataType="Ansichten:NeuerPassagierViewModel">
<Ansichten:NeuerPassagierView></Ansichten:NeuerPassagierView>
</DataTemplate>
</UserControl.Resources>

<!--################ Grundaufteilung des Bildschirms-->
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="60"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>

<!--################ Bereich 1 des Grid: Befehlsleiste -->
<StackPanel Orientation="Horizontal">
<Image Name="image1" Stretch="Fill" Height="30" Width="100"
Source="/WWWings_SL;component/Images
/WorldWideWings_Logo_klein.jpg" Margin="20,0,20,0" />
<Button Name="NeuerPassagier" Height="30" Width="100"
Command="{Binding NeuerPassagierCommand}"
Margin="0,0,20,0" Content="Neuer Passagier"></Button>
<Button Name="Ende" Height="30" Width="100" Command="{Binding
EndeCommand}" Content="Ende"></Button>

</StackPanel>
<!-- ################ Bereich 2 des Grid: Platzhalter fĂŒr User Control -->
<ContentControl Content="{Binding Path=ActiveAnsichtViewModel}"
Grid.Row="2"></ContentControl>
</Grid>


</UserControl>
using System.ComponentModel;
using System.Windows;
using WWWings_GO;

namespace WWWings_SL
{
public partial class MainWindowViewModel : INotifyPropertyChanged
{
/// ################ Daten ################
private INotifyPropertyChanged activeAnsichtViewModel;

/// <summary>
/// Welche ist das aktive ViewModel
/// </summary>
public INotifyPropertyChanged ActiveAnsichtViewModel
{
get { return activeAnsichtViewModel; }
set { activeAnsichtViewModel = value;
OnPropertyChanged("ActiveAnsichtViewModel"); }
}

/// ################ Befehle ################
public ActionCommand EndeCommand { get; set; }

private void Ende()
{
if (Application.Current.IsRunningOutOfBrowser)
{
Application.Current.MainWindow.Close();
}
else
{
System.Windows.Browser.HtmlPage.Window.Invoke("close");
}
}

/// ################ Befehle ################
public ActionCommand NeuerPassagierCommand { get; set; }

private void NeuerPassagier()
{
var pvm = new WWWings_SL.Ansichten.NeuerPassagierViewModel();
this.ActiveAnsichtViewModel = pvm;
pvm.NeuerPassagierViewModelExit += delegate(Passagier passagier)
{
this.ActiveAnsichtViewModel = new
WWWings_SL.Ansichten.BuchungViewModel(passagier);
// ViewModel umschalten und Passagier ĂŒbergeben
};
}


/// ################ Konstruktur ################
public MainWindowViewModel()
{
NeuerPassagierCommand = new ActionCommand(NeuerPassagier);
EndeCommand = new ActionCommand(Ende);
// Erste Viewmodel
this.ActiveAnsichtViewModel = new
WWWings_SL.Ansichten.BuchungViewModel();

}


/// ################ INotifyPropertyChanged ################
#region INotifyPropertyChanged Members

public event PropertyChangedEventHandler PropertyChanged;


protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}

#endregion

}
} (ane [28])

URL dieses Artikels:
https://www.heise.de/-1671358

Links in diesem Artikel:
[1] https://www.heise.de/ratgeber/Von-der-Datenbank-bis-zur-Oberflaeche-mit-NET-Teil-1-Datenzugriff-und-Logik-1418915.html
[2] https://www.heise.de/ratgeber/Von-der-Datenbank-bis-zur-Oberflaeche-mit-NET-Teil-2-Application-Server-und-Webservices-1446415.html
[3] https://www.heise.de/ratgeber/Von-der-Datenbank-bis-zur-Oberflaeche-mit-NET-Teil-3-Eine-Weboberflaeche-mit-ASP-NET-1520357.html
[4] https://www.heise.de/ratgeber/Von-der-Datenbank-bis-zur-Oberflaeche-mit-NET-Teil-4-Desktop-Entwicklung-mit-WPF-und-MVVM-1615881.html
[5] https://connect.microsoft.com/VisualStudio/feedback/details/557939/t4selftrackingcodegentemplatecs-german-localized-ressources-contained-quotation-marks
[6] ftp://ftp.heise.de/pub/ix/developer/schwichtenberg_nettutorial_5.rar
[7] http://www.riastats.com
[8] https://www.heise.de/news/Freier-Silverlight-Klon-Moonlight-eingestellt-1586174.html
[9] http://silverlightcontrib.codeplex.com/
[10] https://www.heise.de/ratgeber/Von-der-Datenbank-bis-zur-Oberflaeche-mit-NET-Teil-3-Eine-Weboberflaeche-mit-ASP-NET-1520357.html
[11] https://www.heise.de/ratgeber/Von-der-Datenbank-bis-zur-Oberflaeche-mit-NET-Teil-3-Eine-Weboberflaeche-mit-ASP-NET-1520357.html
[12] https://www.heise.de/ratgeber/Von-der-Datenbank-bis-zur-Oberflaeche-mit-NET-Teil-4-Desktop-Entwicklung-mit-WPF-und-MVVM-1615881.html
[13] https://www.heise.de/ratgeber/Von-der-Datenbank-bis-zur-Oberflaeche-mit-NET-Teil-2-Application-Server-und-Webservices-1446415.html
[14] http://www.heise.de/developer/artikel/Von-der-Datenbank-bis-zur-Oberflaeche-mit-NET-Teil-5-Desktop-und-Browseranwendung-mit-Silverlight-1671358.html?artikelseite=7
[15] http://www.heise.de/developer/artikel/Von-der-Datenbank-bis-zur-Oberflaeche-mit-NET-Teil-5-Desktop-und-Browseranwendung-mit-Silverlight-1671358.html?artikelseite=6
[16] http://www.heise.de/developer/artikel/Von-der-Datenbank-bis-zur-Oberflaeche-mit-NET-Teil-5-Desktop-und-Browseranwendung-mit-Silverlight-1671358.html?artikelseite=5
[17] http://www.heise.de/developer/artikel/Von-der-Datenbank-bis-zur-Oberflaeche-mit-NET-Teil-5-Desktop-und-Browseranwendung-mit-Silverlight-1671358.html?artikelseite=8
[18] http://slribbon.codeplex.com/
[19] http://www.divelements.co.uk/net/controls/sandribbonsl/
[20] http://www.telerik.com/products/silverlight/controls/ribbonview.aspx
[21] http://www.infragistics.com/dotnet/netadvantage/silverlight/xam-web-ribbon.aspx#Overview
[22] http://www.devexpress.com/Products/NET/Controls/Silverlight/Ribbon/
[23] http://www.heise.de/developer/artikel/Von-der-Datenbank-bis-zur-Oberflaeche-mit-NET-Teil-5-Desktop-und-Browseranwendung-mit-Silverlight-1671358.html?artikelseite=9
[24] http://www.heise.de/developer/artikel/Von-der-Datenbank-bis-zur-Oberflaeche-mit-NET-Teil-5-Desktop-und-Browseranwendung-mit-Silverlight-1671358.html?artikelseite=10
[25] http://www.it-visions.de/glossar/alle/692/692.aspx
[26] https://www.heise.de/blog/Silverlight-Anwendungen-ausserhalb-des-Browsers-per-Batch-Setup-MSI-oder-Click-Once-verteilen-1077886.html
[27] http://www.it-visions.de/start.aspx
[28] mailto:ane@heise.de