Silverlight Tutorial: Transformationen und DataBinding

Willkommen zum vierten Silverlight Tutorial!

Diesmal werden wir uns gleich zwei kleineren und einem größeren Thema widmen:
Zum einen werden wir eine externe Grafik in unsere Silverlight-Webseite einfügen und anhand dessen lernen, wie man GUI-Elemente transformiert.
Zum anderen werden wir uns ein weiteres sehr fundamentales Thema der Silverlight-Entwicklung anschauen: DataBinding.

Ein externes Bild einbinden…

Externe Dateien, die ihr in eure Silverlight-Webseite einbinden wollt, seien es Bilder, Videos oder MP3s, lassen sich auf zwei Arten zu einem Projekt hinzufügen.

Ihr könnt die entsprechende Datei entweder direkt per Drag and Drop in den Solution-Explorer ziehen und dort fallen lassen, oder ihr rechtsklickt auf das Projekt (der oberste Eintrag im Solution-Explorer mit dem grünen Icon) und wählt Hinzufügen -> Vorhandenes Element aus.
Im selben Kontextmenü könnt ihr auch Unterordner für eure Solution erstellen, was praktisch ist, um bei vielen Dateien den Überblick zu behalten.

Erstellt also ein neues Silverlight-Projekt und fügt wie oben beschrieben eine beliebige Grafik ein. Ihr solltet jedoch beachten, dass Silverlight nur die Bildformate JPG und PNG anzeigen kann.

Wenn man externe Dateien ins Projekt einbindet, hat man mehrere Auswahlmöglichkeiten, wie genau sie eingebunden werden, wovon für uns aber momentan nur eine von Bedeutung ist. Sobald ihr die Datei im Solution-Explorer markiert, sehr ihr wie im folgenden Bild deren Eigenschaften:

tut4.3

Dort steht der Buildvorgang auf Resource und Ins Ausgabeverzeichnis kopieren auf Nicht kopieren. Falls die Einstellungen bei euch abweichen, müsst ihr sie wie auf dem Bild gezeigt anpassen.

Ohne ins Detail zu gehen, haben wir damit festgelegt, dass unser Bild zusammen mit der Anwendung direkt beim Start in einem Rutsch geladen wird, und nicht erst wenn es in der GUI angezeigt werden soll.

…und anzeigen

Um das Bild anzuzeigen, werden wir im XAML-Code ein Image-Objekt verwenden. Öffnet dazu die MainPage.xaml und schreibt folgendes:

<Grid x:Name="LayoutRoot">
	<Image Source="EuerBildName.png" />
</Grid>

Ziemlich einfach, oder? Sollte euer Bild in einem Solution-Ordner liegen (also nicht auf der obersten Projektebene wie die anderen Dateien), müsst ihr den Pfad, den ihr der Source-Eigenschaft zuweist, anpassen (z.B. zu ”Images/Bild.png” wenn euer Bild im Ordner Images liegt).

Die Image-Klasse hat neben den üblichen Eigenschaften wie Width und Height die interessante Eigenschaft Stretch.

Sie gibt an, wie das Bild gestreckt wird, falls das umgebende Element (der Container) kleiner als das Bild sein sollte. Ihr könnt beispielsweise dem Grid, in dem unser Image liegt, explizit verschiedene Werte für Width und Height zuweisen und mit der Stretch-Eigenschaft experimentieren, um zu sehen, wie sich das Bild verhält.

None bewirkt, dass das Bild seine Originalgröße behält, auch wenn der Container kleiner ist. Das Bild wird dabei beschnitten, um in den Container zu passen.

Fill bewirkt, dass das Bild den Container immer komplett ausfüllt und auch seine Seitenverhältnisse annimmt, also eventuell verzerrt dargestellt wird.

Uniform bewirkt, dass das Bild sich so weit wie möglich (also nicht unbedingt über die gesamte Breite und Höhe!) im Container ausdehnt, dabei aber seine Seitenverhältnisse beibehält. Dies ist der Standardwert von Stretch.

UniformToFill bewirkt, dass das Bild den gesamten Container ausfüllt und dabei seine Seitenverhältnisse beibehält. Sollte der Container ein anderes Seitenverhältnis haben, wird das Bild dabei eventuell an den Seiten beschnitten.

Elemente transformieren

Anhand unseres Images wollen wir jetzt einige Transformationen ausprobieren, wodurch man interessante visuelle Effekte erreichen kann.

Silverlight stellt fünf verschiedene Transformationen für GUI-Elemente zur Verfügung, von denen wir uns drei ansehen werden:

TranslateTransform verschiebt das Element unabhängig vom Layout beliebig weit in X- und Y-Richtung.

RotateTransform rotiert das Element um den angegebenen Winkel.

ScaleTransform skaliert die Höhe und Breite des Elements. Man könnte also beispielsweise das Element unabhängig von seiner realen Größe damit nur halb so hoch und doppelt so breit machen.

In der Regel beeinflussen Transformationen das Layout System in Silverlight nicht, d.h. verschiebt man zum Beispiel ein Element in einem StackPanel mit einem TranslateTransform, schiebt es dabei nicht seine Nachbarelemente weiter.

Um Transformationen auf ein Element anzuwenden, muss man seine RenderTransform-Eigenschaft mit einem der Transformations-Objekte belegen. Hier ein Beispiel, in dem wir ein RotateTransform auf das Image anwenden und es um 45 Grad rotieren:

<Grid x:Name="LayoutRoot">
<Image Source="EuerBildName.png">
	<Image.RenderTransform>
		<RotateTransform Angle="45" />
	</Image.RenderTransform>
</Image>
</Grid>

Wenn ihr das Programm jetzt ausführt, ist das Bild gedreht – jedoch um seinen Koordinatenursprung (0,0) links oben.

Oftmals möchte man aber, dass Transformationen vom Mittelpunkt des Objektes ausgehen. Hierfür kann man der Eigenschaft RenderTransformOrigin des Elements den relativen Koordinatenursprung zuweisen (zwischen 0,0 oben links und 1,1 unten rechts) , der das Zentrum für die Transformationen sein soll.

Wenn wir den obigen Code folgendermaßen erweitern, sollte sich das Bild um seinen Mittelpunkt drehen:

<Grid x:Name="LayoutRoot">
<Image Source="EuerBildName.png" RenderTransformOrigin="0.5, 0.5">
	<Image.RenderTransform>
		<RotateTransform Angle="45" />
	</Image.RenderTransform>
</Image>
</Grid>

Es ist auch möglich, einem Element mehrere Transformationen zuzuweisen. Dazu muss das Element, das wir als RenderTransform angeben, eine TransformGroup sein. Das ist quasi ein StackPanel für Transformations-Objekte, das wir mit beliebig vielen Transformationen füttern können.

Im folgenden Beispiel rotieren wir das Bild um 45 Grad (RotateTransform), verschieben es um 50 Pixel nach unten und nach rechts (TranslateTransform) und machen es halb so hoch und doppelt so breit (ScaleTransform):

<Grid x:Name="LayoutRoot">
<Image Source="EuerBildName.png" RenderTransformOrigin="0.5, 0.5">
	<Image.RenderTransform>
		<TransformGroup>
			<RotateTransform Angle="45" />
			<ScaleTransform ScaleX="2" ScaleY="0.5" />
			<TranslateTransform X="50" Y="50" />
		</TransformGroup>
	</Image.RenderTransform>
</Image>
</Grid>

Übrigens ist die Reihenfolge der Transformationen wichtig, wenn man sie bei einer TransformGroup angibt. Zuerst eine Rotation und dann eine Translation hat also einen anderen Effekt, als erst eine Translation und dann eine Rotation.

Elemente perspektivisch verzerren

Dieses Thema werde ich hier nur kurz ansprechen, aber ihr findet im herunterladbaren Projekt weiter unten ein ausführliches Beispiel der Anwendung von Transformationen und der perspektivischen Projektion.

Neben den oben genannten Transformationen gibt es in Silverlight noch eine weitere Art, Elemente visuell zu verzerren.

Dies funktioniert ähnlich wie bei der RenderTransform-Eigenschaft: Weist man der Projection-Eigenschaft eines Elements ein PlaneProjection-Objekt zu, kann man visuell einen 3D-Effekt erzeugen, wo das Bild beispielsweise im 3D-Raum gedreht wird. Dazu hat die PlaneProjection-Klasse einige Eigenschaften, die verschiedene Arten von Effekten bewirken.

Eigenschaften verbinden: Das DataBinding

DataBinding ist zusammen mit ein, zwei anderen Themen eines der wichtigsten Konzepte in Silverlight.

Es ermöglicht eine Entkopplung der eigentlichen Applikationslogik von der Benutzeroberfläche und sorgt damit in der Praxis für einen optimalen „Developer-Designer-Workflow“. Das bedeutet, während ein Programmierer C#-Code mit der Applikationslogik schreibt, kann unabhängig davon parallel ein GUI-Designer mit XAML eine hübsche Benutzeroberfläche kreieren. Er muss nur ein Minimum an Wissen haben, welche Daten später an die GUI geliefert werden.

Das Prinzip von DataBinding ist ziemlich schnell erklärt. Man „bindet“ zwei Eigenschaften zweier Objekte zusammen, sodass bei Änderungen der einen Eigenschaft sich auch die zweite Eigenschaft ändert. Die beiden Eigenschaften haben also immer (je nach Binding-Modus jedoch nicht zwingend) den selben Wert, sie sind sozusagen permanent synchron.

Man kann die Eigenschaften zweier GUI-Elemente, die man im XAML definiert hat verbinden, aber auch ein GUI-Element mit einem Objekt aus dem Code-Behind, das Applikationslogik beinhaltet.

In diesem Tutorial werden wir erstmal nur das Binding zwischen GUI-Elementen behandeln.

Ein typisches Beispiel:

Man hat einen Videoplayer in der Oberfläche, dessen aktuelle Abspielposition und Lautstärke man mit zwei Reglern ändern möchte.

In der „konventionellen“ Programmierung würde man sich beispielsweise Event-Handler für den Fall, dass sich die Positionen der Regler ändern, erstellen und in diesen Handlern die Reglerwerte abrufen und zu den entsprechenden Videoplayer-Eigenschaften weiterreichen.

Mit DataBinding verbindet man einfach direkt die „Aktueller-Wert“-Eigenschaft des einen Reglers mit der Lautstärke-Eigenschaft des Videoplayers, und die des anderen mit der Positions-Eigenschaft. Man braucht dazu nur zwei kurze Zeilen XAML-Code.

Hier ein erstes Beispiel, in dem wir den aktuellen Wert eines Reglers in einem TextBlock anzeigen wollen:

<StackPanel Orientation="Horizontal">
	<Slider x:Name="mySlider" Width="128" Minimum="0" Maximum="100" />
	<TextBlock Text="{Binding ElementName=mySlider, Path=Value}" />
<StackPanel>

Wenn ihr diesen Code in die MainPage.xaml schreibt und das Programm ausführt, werdet ihr sehen, wie der aktuelle Wert des Reglers bei Veränderungen automatisch im TextBlock angezeigt wird.

Grafisch sieht das obige Binding in etwa so aus:

tut4.2

Das Binding-Ziel ist die Text-Eigenschaft des TextBlocks.

Die Binding-Quelle ist die Value-Eigenschaft des Sliders, die seinen aktuellen Wert enthält.

Bei Änderungen der Binding-Quelle wird automatisch auch das Binding-Ziel aktuallisiert.

Wie man im Code sieht, erzeugt man ein Binding, in dem man in die Ziel-Eigenschaft die sogenannte Markup-Extension Binding einträgt. Diese hat folgende Form:

ZielEigenschaft=“{Binding ElementName=QuellObjekt, Path=QuellEigenschaft}

Zu beachten ist, dass das Binding via ElementName nur eine Art von DataBinding ist. In späteren Tutorials werden wir eine weitere Art kennenlernen, um Elemente im XAML auch mit Code-Behind Objekten zu verbinden.

Im Normalfall wird also die Ziel-Eigenschaft aktuallisiert, wenn sich die Quell-Eigenschaft ändert. Was ist aber, wenn man auch Änderungen der Ziel-Eigenschaft zurück an die Quell-Eigenschaft weiterreichen möchte?

Hierzu gibt es beim Binding den optionalen Parameter Mode, den man wiefolgt festlegt:

ZielEigenschaft=“{Binding ElementName=QuellObjekt, Path=QuellEigenschaft, Mode=TwoWay}“

Er kann folgende Werte annehmen:

OneWay: Das ist die Standardbelegung und reicht Änderungen nur von der Quelle zum Ziel weiter.

TwoWay: So werden auch Änderungen am Ziel zurück zur Quelle weitergereicht. Die Aktuallisierung der Eigenschaften funktioniert dann in beide Richtungen.

OneTime: Hierbei wird die Ziel-Eigenschaft nur ein einziges mal beim Erzeugen des Bindings aktuallisiert.

Ein kleiner Fallstrick bei der Aktuallisierung des Bindings ist die TextBox. Hier wird es nämlich nicht aktuallisiert, wenn man in ihr herumtippt, sondern erst, wenn sie den (Eingabe-)Fokus verliert.

Bevor wir mit einem größeren DataBinding-Beispiel weitermachen, noch zwei Sachen, die man sich merken sollte:
Erstens: Die Ziel-Eigenschaft muss ein
DependencyProperty sein.

Das sind spezielle Eigenschaften, die nach außen hin wie normale Eigenschaften auftreten und uns erstmal nicht weiter interessieren sollen. Die meisten für das Binding relevante Eigenschaften (Value, Text, Background, …) sind DependencyProperties. Solltet ihr jedoch mal einen Binding-Fehler haben, informiert euch (z.B. auf www.msdn.com), ob die Ziel-Eigenschaft tatsächlich ein DP ist.

Für die Quell-Eigenschaft gibt es keine solche Beschränkung, wie folgende verallgemeinerte Illustration zeigt:

tut4.1

Zweitens: In der Regel müssen die Datentypen der beiden verbundenen Eigenschaften übereinstimmen.

Wenn wir wie oben eine Double-Eigenschaft (Value) an eine String-Eigenschaft (Text) verbinden, konvertiert Silverlight automatisch von double nach string.

Sollte man jedoch auf die Idee kommen, den Value eines Sliders an die Background-Eigenschaft (Typ: Brush) einer Border zu binden, wird das scheitern. Wie sollte auch aus einer Zahl eine Farbe erzeugt werden?

Es gibt in diesem Fall zwar die Möglichkeit, einen sogenannten Converter zu verwenden, um bei verschiedenen Eigenschafts-Datentypen die Werte in den jeweils anderen Typ zu konvertieren, aber das schauen wir uns ebenfalls erst in späteren Tutorials an.

Bis dahin gilt: Binding-Ziel und -Quelle müssen (abgesehen von Zahlen und Strings) den selben Datentyp haben!

Von Slidern, Transformationen und der Magie des DataBindings

Weiter geht’s mit einem DataBinding-Beispiel, in dem wir die Werte einiger Slider an verschiedene Transformationen binden, die dann ein Bild skalieren und rotieren. Hier der komplette Code:

<Grid x:Name="LayoutRoot">
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="*" />
      <ColumnDefinition Width="Auto" />
    </Grid.ColumnDefinitions>

    <Image Grid.Column="0" Source="bild.png"
           Width="640" Height="480"
           RenderTransformOrigin="0.5, 0.5"
           HorizontalAlignment="Center" VerticalAlignment="Center">
      <Image.RenderTransform>
        <TransformGroup>
          <ScaleTransform x:Name="scale" />
          <RotateTransform x:Name="rotation" />
        </TransformGroup>
      </Image.RenderTransform>
    </Image>

    <StackPanel Grid.Column="1"
                VerticalAlignment="Center" Margin="8">
      <Slider Minimum="0.5" Maximum="2" Width="128" Margin="8"
              Value="{Binding ElementName=scale, Path=ScaleX, Mode=TwoWay}" />
      <Slider Minimum="0.5" Maximum="2" Width="128"
              Value="{Binding ElementName=scale, Path=ScaleY, Mode=TwoWay}" />
      <Slider Minimum="0" Maximum="360" Width="128"
              Value="{Binding ElementName=rotation, Path=Angle, Mode=TwoWay}" />
    </StackPanel>

  </Grid>

Wir definieren ein einfaches Grid-Layout mit zwei Spalten:
In der linken Spalte befindet sich ein Image-Objekt, das wir transformieren wollen (passt hier eventuell den Dateinamen und den Pfad an!).In der rechten Spalte ist ein StackPanel, das drei Slider enthält, die die Intensität der Transformationen steuern.

Die Slider-Klasse hat neben der Value-Eigenschaft, die den aktuellen Reglerwert speichert, noch eine Minimum und Maximum Eigenschaft. Die beiden geben (überraschenderweise) den Minimal- und Maximalwert an, die Value annehmen kann.

Die Slider haben wir wiefolgt an die beiden Transformationen des Images gebunden:

Der erste Slider steuert die ScaleX-Eigenschaft des ScaleTransforms, die das Bild horizontal streckt.

Der zweite Slider steuert die ScaleY-Eigenschaft des ScaleTransforms, die das Bild vertikal streckt.

Der dritte Slider steuert die Angle-Eigenschaft des RotateTransforms, die das Bild um den angegebenen Winkel rotiert.

Damit alle Transformationen vom Bild-Mittelpunkt ausgehen, haben wir die RenderTransformOrigin-Eigenschaft des Images auf seinen relativen Mittelpunkt gesetzt. Ihr könnt hier gerne mit verschiedenen Werten zwischen 0 und 1 experimentieren, um den Effekt der Eigenschaft zu sehen.

Bei den Bindings gilt es noch zu beachten, dass wir Mode auf TwoWay gesetzt haben.

Da bei den Bindings die Slider die Binding-Ziele und die Transformationen die Binding-Quellen sind, würden normalerweise die Änderungen bei den Slidern nicht zurück in die Transformationen geschrieben werden. Mit dem Binding-Modus TwoWay ändern wir dieses Verhalten, sodass auch Änderungen beim Binding-Ziel zur Binding-Quelle weitergereicht werden.

Das war’s soweit zum Thema DataBinding und Transformationen.
Unten findet ihr ein ausführliches Beispielprojekt.
Solltet ihr noch Fragen haben, postet ins eality Forum.

Im nächsten Tutorial wird es wieder etwas anschaulicher und bunter zu gehen, wenn wir uns Resourcen, Brushes und Styles ansehen.

Beispielprojekt herunterladen

Kommentare

7 Nachrichten zu “Silverlight Tutorial: Transformationen und DataBinding”

  1. Stephan on 2. Februar 2010 12:43

    schönes tutorial …..
    ich habe gerade eine ähnliches problem …
    wie kann ich per code die zieleigenschaft value eines sliders an die scale x und scal y property binden, damit beide achsen zeitgleich skaliert werden wenn ich den slider bewege ….
    in xaml ist das kein problem, aber in code schaffe ich es nur das er das letzt erstellte binding ausführt

  2. Andrej on 2. Februar 2010 15:21

    Das ist in SL leider etwas problematisch, da man im Gegensatz zur WPF nicht ScaleX und -Y als Zieleigenschaften des Bindings verwenden kann.

    Eine Lösung wäre es, den Slider an Width und Height des Objektes zu binden: Width=“{Binding ElementName=slider, Path=Value}“ Height=“{Binding ElementName=slider, Path=Value}“, falls es lediglich ums Vergrößern geht.

    Eine andere wäre, das ValueChanged-Event des Sliders im Code-Behind zu behandeln, der ScaleTransform einen Namen zu geben und dann ScaleX und ScaleY im Code-Behind direkt auf den Sliderwert zu setzen (also ohne Binding).

    Die „richtige“ Lösung dafür ist ein sog. Attached Behavior zu verwenden, aber das ist leider zu Umfangreich um das hier als Comment zu beschreiben.

    Falls Du noch weitere Fragen hast, poste besser ins Forum. 😉

  3. Ecki on 29. März 2010 21:15

    Viel Dank für die Grundlagen-Tutorials – haben mir beim Einstieg in Silverlight sehr geholfen

  4. Johannes on 23. Juni 2010 16:54

    Danke für die Tutorials, bin echt begeistert, was man mit Silverlight tolles anstellen kann…

  5. MasterSam on 23. Juli 2010 23:16

    Respekt, sehr anschaulich erklärt
    Was ich mich aber frage, lohnt es sich noch Silverlight zu lernen?

  6. Andrej on 24. Juli 2010 17:46

    @MasterSam:

    Auf jeden Fall. Im Gegensatz zu Flash ist zwar die Verbreitung von SL noch nicht so hoch, was aber wohl daran liegt, dass SL erst seit knapp 3 Jahre existiert, Flash hingegen viel länger.

    Man sollte deswegen (momentan) davon absehen, komplette Webseiten in SL zu schreiben. Onlineapplikationen in SL stehen den Desktop-Pendants aber um nichts nach, man kann auf der visuellen und technischen Seite unglaubliche Anwendungen schreiben.

    Aus Programmierersicht möchte ich gar meinen, dass die Kombo aus C#, XAML und SL was Spaßfaktor und Produktivität angeht momentan ungeschlagen ist.

    Wenn man erstmal hinter einige Sachen wie Lookless Control Model, ViewModel-Pattern, Templating, Styling, DataBinding steigt merkt man, wie genial das Framework designt ist und wie einfach es ist, eindrucksvolle Programme zu entwickeln.

  7. passsy on 16. September 2010 15:29

    Vielen Dank für das Tutorial. Hat sicher viel Zeit gekostet, die war es aber Wert. Würde mich freuen mehr zu lesen.

    Gruß passsy