Silverlight Tutorial: Layout in XAML

Willkommen zum zweiten Silverlight Tutorial!

Diesmal werden wir die Grundlagen von XAML lernen, um ein einfaches Layout für eine Webseite zu erstellen. Los gehts!

Erstellen eines neuen Projekts

Startet das Visual Studio (bei euch wahrscheinlich die Visual Web Developer 2008 Express Edition) und klickt auf Datei -> Neues Projekt. Hier haben wir nun unter anderem die Wahl der Programmiersprache und des Projekttyps. Da wir eine Silverlight-Anwendung in C# schreiben wollen, wählt wie im folgenden Bild gezeigt die entsprechenden Einträge aus:

tut2.1

Der Projektname ist momentan unwichtig, wählt einfach etwas wie „SilverlightTutorial“ und bestätigt mit OK.

Beim nächsten Dialog kann man optional ein Web-Projekt anlegen lassen, was wir aber nicht wollen. Entfernt also das Häkchen und bestätigt erneut mit OK. Nun wurde ein neues Silverlight-Projekt angelegt.

Die IDE ist recht selbsterklärend aufgebaut:

In der Mitte befindet sich der Code-Editor, in dem wir den C# und XAML Code schreiben werden.

Unten ist unter anderem ein Fehler-Fenster, das uns bereits vor dem Ausführen des Programms auf Fehler und Probleme hinweist.

Rechts ist der Solution-Explorer, der alle zum Projekt gehörigen Dateien anzeigt.

Und hier müssen wir bevor wir weitermachen eine kleine Änderung vornehmen: Standardmäßig wird die GUI mit einem grafischen Editor „zusammengeklickt“. Da dieser aber alles andere als gut funktioniert (es gibt dafür bessere Tools wie Expression Blend) und wir naheliegenderweise lernen wollen, wie man programmiert, deaktivieren wir den Editor und lassen uns wie folgt nur noch Code anzeigen:

Klickt rechts auf die Datei MainPage.xaml im Solution-Explorer und wählt Öffnen mit aus, worauf folgender Dialog erscheint:

tut2.2

Hier markiert ihr Quelltext-Editor, klickt auf Als Standard und bestätigt mit OK.

Das war’s auch schon. Weiter geht es mit einem der wichtigsten Themen in der Silverlight-Entwicklung: XAML.

Eine Anmerkung noch: Ab hier setzte ich das Verständnis von Grundbegriffen wie Objekt, Klasse und Vererbung vorraus. Solltet ihr darüber nichts wissen, kann ich euch nur nochmals das Galileo C# ObenBook ans Herz legen.

Enter XAML

In Silverlight wird in der Regel die gesamte grafische Oberfläche (GUI) in XAML-Dateien beschrieben. XAML steht für Extensible Application Markup Language und ist eine deklarative auf XML basierende Beschreibungssprache.

Öffnet jetzt die Datei MainPage.xaml, die etwa so aussehen sollte:

<UserControl x:Class="SilverlightApplication1.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" d:DesignWidth="640" d:DesignHeight="480">
  <Grid x:Name="LayoutRoot">

  </Grid>
</UserControl>

Im XAML-Code werden GUI-Elemente wie Buttons, Listboxen, Bilder usw. platziert und ihre Eigenschaften festgelegt.
Lasst euch jedoch nicht vom HTML-artigen Aussehen des Codes täuschen: Man kann XAML nahezu eins zu eins in C# abbilden, wo der obige Codeausschnitt etwa so aussehen würde:

MainPage mainPage = new MainPage();
Grid grid = new Grid();
mainPage.Content = grid;

Das heißt, auch in XAML definierte GUI-Elemente sind nichts weiter als CLR-Objekte, die zur Laufzeit erzeugt und deren Eigenschaften belegt werden. XAML macht es nur benutzerfreundlicher, jene Objekte zu definieren und daraus eine grafische Benutzeroberfläche zu designen.

Das erste, was wir auf diesem Gebiet lernen werden ist, wie man mit Hilfe von Panels das Grundlayout einer Anwendung festlegt.

Definieren eines Layouts in XAML

In Silverlight gibt es im wesentlichen drei Panel-Klassen, mit denen man auf verschiedene Weisen die Anordnung der GUI-Elemente steuern kann.

Zuerst schauen wir uns die StackPanel-Klasse an. Elemente in einem StackPanel werden einfach vertikal über- oder nebeneinander angeordnet (oder „gestapelt“, was im englischen to stack heißt). Dieses Panel sollte nur für kleinere Gruppen von Elementen und nicht für das grundlegende Seitenlayout eingesetzt werden, da es gerade für Anfänger oft ein unerwartetes Verhalten zeigt.

Schreibt nun folgendes in das Grid-Tag und startet die Anwendung, indem ihr die F5-Taste drückt.

<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
      <Button Content="Ich bin ein Button!" Margin="8" />
      <TextBlock Text="Ich bin ein TextBlock!" Margin="8" />
      <TextBox Text="Ich bin eine TextBox!" Margin="8" />
</StackPanel>

Die Webseite sollte nun so aussehen:

tut2.4

Was genau haben wir also dort oben definiert?

Viele Elemente, die wir in XAML definieren, haben die Eigenschaften HorizontalAlignment und VerticalAlignment, die bestimmten, wie ein Element relativ zum übergeordneten Eltern-Element (bei uns ist das ein Grid) positioniert wird.

Da in unserem Beispiel beide Eigenschaften auf Center stehen, ist das StackPanel horizontal und vertikal im Grid zentriert. Nebenbei hat die StackPanel-Klasse auch eine Orientation-Eigenschaft. Sie legt fest, ob die Elemente im Panel vertikal (Orientation ist mit dem Wert Vertical belegt, das ist die Standardeinstellung) oder horizontal (mit dem Wert Horizontal) angeordnet werden.

Elemente die man in einem Panel definiert, werden implizit seiner Children-Eigenschaft hinzugefügt. Um das anschaulicher zu machen: Man könnte den obigen Code auch so schreiben:

<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
    <StackPanel.Children>
          <Button Content="Ich bin ein Button!" Margin="8" />
          <TextBlock Text="Ich bin ein TextBlock!" Margin="8" />
          <TextBox Text="Ich bin eine TextBox!" Margin="8" />
    </StackPanel.Children>
</StackPanel>

Hier sehen wir eine weitere Art, Eigenschaften eines Elements festzulegen. Zum einen geht das direkt im Element-Tag (wie bei Horizontal- und VerticalAlignment), zum anderen auch direkt im Element mit ElementName.Eigenschaft. Beispielsweise würde der Codeausschnitt so aussehen, wenn wir die Alignments ebenfalls mit der zweiten Variante setzen:

<StackPanel>
      <StackPanel.HorizontalAlignment>
        Center
      </StackPanel.HorizontalAlignment>
      <StackPanel.VerticalAlignment>
        Center
      </StackPanel.VerticalAlignment>

      <StackPanel.Children>
        <Button Content="Ich bin ein Button!" Margin="8" />
        <TextBlock Text="Ich bin ein TextBlock!" Margin="8" />
        <TextBox Text="Ich bin eine TextBox!" Margin="8" />
      </StackPanel.Children>
 </StackPanel>

Es ist wichtig, sich die beiden Varianten zu merken, da wir später einige Eigenschaften nur noch mit der zweiten Variante setzen werden.

Was genau die drei Elemente im StackPanel sind, sollte selbsterklärend sein. In diesem Beispiel haben wir, anstatt Start- und End-Tags zu verwenden, die End-Tags mit „/>“ abgekürzt, was oftmals die Übersichtlichkeit des XAML-Codes steigern kann.

Von jedem Element wurde die Margin-Eigenschaft belegt. Sie gibt an, wieviel Platz ein Element relativ zu seinen Nachbarelementen und zu seinem Eltern-Element haben möchte. Wird hier nur eine Zahl angegeben, hat das Element nach links, oben, rechts und unten denselben Abstand zu den umgebenden Elementen. Man kann hier auch durch Komma getrennt vier Zahlen angeben:

<Button Content="Ich bin ein Button!" Margin="2, 4, 6, 8" />

Sie geben in genau dieser Reihenfolge explizit die Abstände zu allen vier Seiten an: Links (2), oben (4), rechts (6), unten (8).

An dieser Stelle möchte ich kurz auf die Einheiten von Angaben wie Abstände oder Positionen in Silverlight eingehen.

Obwohl es scheint, dass ein Element mit einer Margin von 8, eben 8 Pixel Abstand erhält, ist dies nicht der Fall. Silverlight verwendet ein DPI- und Auflösungsunabhängiges Grafikmodell, das sicherstellt, dass Anwendungen mit verschiedenen Bildschirmkonfigurationen immer (relativ) gleich aussehen. Zwar sind Angaben in Pixel und in den Einheiten von Silverlight ähnlich, aber nicht identisch. Eine Linie der Länge 32 könnte also in Silverlight in Wirklichkeit 34 Pixel lang sein.

Da die Unterschiede sehr marginal sind, kann man dies meistens vernachlässigen. Wer aber beispielsweise Anwendungen schreibt, die pixelgenau arbeiten sollen, sollte das oben beschriebene im Hinterkopf behalten.

Und weiter geht’s mit der nächsten Panel-Klasse.

Die Grid-Klasse wird unter anderem für das Makro-Layout verwendet und ist vergleichbar mit HTML-Frames oder Tabellen. Man definiert im Grid Spalten und Zeilen, die feste oder relative Größen haben können, und weist den Elementen darin die Spalte und Zeile zu, in denen sie sich befinden sollen.

Ihr könnt jetzt das StackPanel aus dem Grid löschen, und stattdessen in das Grid-Tag (am besten ganz oben) folgendes schreiben:

<Grid.RowDefinitions>
      <RowDefinition Height="128" />
      <RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
      <ColumnDefinition Width="Auto" />
      <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>

Die Definition der Zeilen und Spalten erfolgt durch Belegung der RowDefinitions und ColumnDefinitions Eigenschaften des Grids.

Ihr habt für das Setzen der Höhe (Height-Eigenschaft einer RowDefinition) bzw. der Breite (Width-Eigenschaft einer ColumnDefinition) einer Spalte oder Zeile drei Möglichkeiten:

Es ist auch möglich, folgendes zu definieren:

<Grid.RowDefinitions>
      <RowDefinition Height="1*" />
      <RowDefinition Height="3*" />
</Grid.RowDefinitions>

Hier würde, auch wenn man zum Beispiel das Browserfenster vergrößert oder verkleinert, die zweite Zeile immer 3 mal so groß bleiben wie die erste. Die erste Zeile bekäme also ¼ des Platzes und die zweite ¾ .

Damit die Elemente in einem Grid wissen, in welcher Spalte und Zeile sie stehen sollen, gibt man diese Informationen wiefolgt im Element-Tag an:

<Button Grid.Row="0" Grid.Column="0" />

Dies würde bedeuten, dass der Button oben links in der ersten Spalte und in der ersten Zeile (für Informatiker ist ja bekanntermaßen das erste Element immer das 0te ;)) platziert wird.

Zu beachten ist hier, dass Grid.Row und Grid.Column keine Eigenschaften der Button-Klasse sind, obwohl es den Anschein hat. Diese beiden Eigenschaften sind sog. Attached Properties, eine spezielle Art von Eigenschaft, auf die ich in späteren Tutorials eingehen werde. Momentan reicht es sich vorzustellen, dass sich ein Element solche Eigenschaften von anderen Klassen nur „ausleiht“.  Hier nun der komplette Code eines Beispiel-Grids mit zwei Elementen:

<Grid x:Name="LayoutRoot" ShowGridLines="True">
    <Grid.RowDefinitions>
      <RowDefinition Height="64" />
      <RowDefinition Height="*" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="Auto" />
      <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>

    <Button Grid.Row="0" Grid.Column="0"
            Content="Ein Button"
            Margin="32" />

    <Border Grid.Row="1" Grid.Column="1"
            Background="Orange"
            BorderBrush="Black" BorderThickness="4"
            CornerRadius="8"
            Margin="32">
        <TextBlock Text="Hier kann genau ein Element rein!" />
    </Border>

</Grid>

Oben im Grid-Tag haben wir die Eigenschaft ShowGridLines belegt, die uns zum Testen die Spalten- und Zeilenbegrenzungen durch Linien anzeigt.

Wenn ihr das Programm nun ausführt, liegt oben links ein Button, während der untere linke Platz von einer Border eingenommen wird.

Die Border-Klasse repräsentiert einen Rahmen mit einer Hintergrundfarbe (Background), einer Rahmenfarbe (BorderBrush) und einer Rahmendicke (BorderThickness).

Die CornerRadius-Eigenschaft gibt an, wie stark die Rundungen an den Ecken sind. Wie bei der Margin-Eigenschaft kann man auch hier vier durch Komma getrennte Werte angeben, um für jede Ecke eine andere Rundung zu erreichen.

Eine Border hat im Gegensatz zur Children-Eigenschaft der Panels, die mehrere Elemente aufnehmen kann, eine Content-Eigenschaft, die nur ein Element in der Border erlaubt.

Schreibt man ein Element in das Border-Tag, wird (analog zu den Panels) implizit die Content-Eigenschaft mit diesem Element (in unserem Beispiel ein TextBlock) belegt.

Um mehr als ein Element in einer Border zu platzieren, kann man einfach ein Panel als Content festlegen, in dem man dann wieder beliebig viele Elemente setzen kann.

Die dritte Panel-Klasse ist das Canvas. Hier werden Elemente nicht wie in den anderen Panels relativ, sondern absolut durch Angabe von X- und Y-Koordinaten platziert. Dieses Panel sollte nicht für Layout-Zwecke verwendet werden. Es gibt jedoch Fälle, wo das absolute Positionieren von Elementen notwendig ist. Hier ein Beispiel:

<Grid x:Name="LayoutRoot">
    <Canvas>

      <Ellipse Canvas.Top="128" Canvas.Left="256"
               Width="64" Height="64"
               Fill="Orange"
               Stroke="Black" StrokeThickness="4" />

    </Canvas>
</Grid>

Ähnlich wie beim Grid, erfolgt die Angabe der Position eines Elements durch das Setzen der Attached Properties Canvas.Top und Canvas.Left. Der Koordinatenursprung (der Punkt 0,0) befindet sich in Silverlight nebenbei immer links oben.

Die Ellipse-Klasse ist ein Beispiel eines Shapes, wozu auch Klassen wie Rectangle und Line gehören. Shapes (genauer: von der Klasse Shape abgeleitete Klassen) repräsentieren einfache geometrische Formen und können keine weiteren Elemente als Inhalt zugewiesen bekommen.

Analog zur Border haben auch Shapes eine Hintergrundfarbe (Fill), eine Rahmenfarbe (Stroke) und eine Rahmendicke (StrokeThickness).

Hier endet unser Einstieg in XAML. Ihr müsstet nun in der Lage sein, Panel-Klassen zu verwenden, um einfache Layouts in XAML zu erstellen.

Im nächsten Tutorial werden wir uns ansehen, wie man XAML- und C#-Code verbindet und so eine interaktive Silverlight-Anwendung erstellt.

Beispielcode herunterladen

Kommentare

Eine Nachricht zu “Silverlight Tutorial: Layout in XAML”

  1. philippe on 12. November 2009 14:54

    wooow klasse einführung. vielen dank. silverlight macht einfach spass. weiter so