Silverlight Canvas

Das Canvas-Element (Canvas=Leinwand) ermöglicht eine freie Anordnung von Elementen. Das bedeutet, dass die Elemente über entsprechende X- und Y-Koordinaten zu positionieren sind, da das Canvas über keinerlei Layoutlogik verfügt. Per Vorgabe hat ein Element auf einem Canvas die Position 0,0 im Elternelement.

In dem nachfolgenden Codeabschnitt wird ein Button innerhalb eines Canvas positioniert

<Grid x:Name=“MyGrid“ Width=“300″ Height=“100″ Background=“Azure“>
<
Canvas x:Name=“LayoutRoot“ Background=“Bisque“ Width=“250″ Height=“75″>
<
Button x:Name=“Btn1″ Content=“Klick mich an“></Button>
</
Canvas>
</
Grid>

Wie zu erkennen ist, wurden für den Button keine Angaben zur Position oder Größe angegeben. Damit erhält der Button die Position 0,0 während Höhe und Breite in Abhängigkeit vom Content (dem Text „Klick mich an“) eingerichtet wird. Dabei enthalten die Eigenschaften Width und Height den Wert NaN (Not a Number). Dies hat den Vorteil, dass bei Änderungen des Content eine Größenänderung automatisch passiert. Dazu aber später mehr.

Fügt man nun einen weiteren Button hinzu – auch diesmal ohne Angabe von Position und Größe – erkennt man, dass der neu hinzugefügte Schalter den anderen überlagert.

<Grid x:Name=“MyGrid“ Width=“300″ Height=“100″ Background=“Azure“>
<Canvas x:Name=“LayoutRoot“ Background=“Bisque“ Width=“250″ Height=“75″>
<
Button x:Name=“Btn1″ Content=“Klick mich an“></Button>
<
Button x:Name=“Btn2″ Content=“Button 2″ Foreground=“Chocolate“></Button>
</
Canvas>
</
Grid>


Das bedeutet also, dass Positionsangaben verwendet werden müssen, da ansonsten alle Elemente an Position 0,0 liegen und sich dadurch überlagern. Allerdings gibt es für den Button keine Eigenschaft Left oder Top – wie kann man nun die Position festlegen? Die Antwort: Im Canvas. Mittels Attached Properties (angehängte Eigenschaften) lässt sich die Position der Buttons festlegen.

<Button x:Name=“Btn2″ Canvas.Top=“27″ Content=“Button 2″ Foreground=“Chocolate“></Button>

Damit wird für den zweiten Button festgelegt, dass sich sein oberer Rand 27 Pixel unterhalb des Canvas-Außenrahmen befindet und somit nicht mehr den ersten Schalter überdeckt. Im nachfolgenden Beispiel wird mit Canvas.Left der linke Ursprung festgelegt und damit der zweite Schalter gegenüber dem ersten um 15 Pixel eingerückt.

<Button x:Name=“Btn2″ Canvas.Top=“27″ Canvas.Left=“15″ Content=“Button 2″ Foreground=“Chocolate“></Button>

Unter Umständen kann es aber auch beabsichtigt sein, dass Elemente durch andere Elemente überlagert werden sollen. Beispielsweise, wenn man einen Text auf einem Kreis wie im nachfolgenden Beispiel ausgeben möchte.

Mit dem nachfolgenden XAML-Code wird dies erreicht. Was hier auch auffällt ist, dass die Ellipse außerhalb des Canvas weiter gezeichnet wird.

<Canvas x:Name=“LayoutRoot“ Background=“Bisque“ Width=“250″ Height=“75″>
<
Button x:Name=“Btn1″ Canvas.Top=“2″ Content=“Klick mich an“></Button>
<
Button x:Name=“Btn2″ Canvas.Top=“27″ Canvas.Left=“15″ Content=“Button 2″ Foreground=“Chocolate“></Button>
<
Ellipse x:Name=“Ellipse“ Canvas.Top=“35″ Canvas.Left=“100″ Width=“50″ Height=“50″ Fill=“Black“></Ellipse>
<
TextBlock x:Name=“Tb1″ Canvas.Left=“112″ Canvas.Top=“50″ Foreground=“White“>Text</TextBlock>
</
Canvas>

In diesem Beispiel wird der Text über dem Kreis ausgegeben – so war es ja auch beabsichtigt. Durch die Angabe der Reihenfolge im XAML-Code wird auch die Reihenfolge bei der Ausgabe festgelegt. Dies gilt es auch zu beachten, denn wenn beispielsweise erst der Text und erst dann der Kreis in XAML definiert wird, überlagert der Kreis den Text und dieser ist damit auch nicht sichtbar.

Damit man nun nicht jedes Mal den kompletten XAML-Code komplett umschmeißen muss, wenn sich irgendwas ändert, kann man auch mittels Canvas.ZIndex die Reihenfolge ändern. Dies ist insbesondere dann sinnvoll, wenn programmatische Änderungen vorgenommen werden.

<TextBlock x:Name=“Tb1″ Canvas.Left=“112″ Canvas.Top=“50″ Foreground=“White“ Canvas.ZIndex=“2″>Text</TextBlock>
<
Ellipse x:Name=“Ellipse“ Canvas.Top=“35″ Canvas.Left=“100″ Width=“50″ Height=“50″ Canvas.ZIndex=“1″ Fill=“Black“></Ellipse>

Damit wird der Text wieder über der Ellipse positioniert und ist damit sichtbar.

Alle anderen Elemente besitzen den ZIndex 0, d.h. hier wird über die Reihenfolge der XAML-Definitionen die Darstellung der restlichen Elemente festgelegt.

Jetzt möchte ich noch mal kurz auf den schwarzen Kreis – oder besser gesagt Ellipse – zu sprechen kommen. Wie bereits weiter oben schon mal angemerkt, sieht man, dass über die Ränder des Canvas hinaus weiter gezeichnet wird. Das kann beabsichtigt sein. Was aber nun, wenn nicht über die Ränder des Canvas hinaus gezeichnet, sondern ein Element entsprechend beschnitten werden soll. Auch dies lässt sich mit einer kurzen Anweisung in XAML realisieren.

<Canvas.Clip>
<
RectangleGeometry Rect=“0,0,250,75″/>
</
Canvas.Clip>

Damit erreicht man, dass die Ellipse an den Rändern des Canvas abgeschnitten wird.

Dies betrifft aber nicht nur die Ellipse. Vielmehr werden alle Elemente entsprechend beschnitten, wenn über den Rand des Canvas hinaus gezeichnet wird.

<Grid x:Name=“MyGrid“ Width=“300″ Height=“100″ Background=“Azure“>
<
Canvas x:Name=“LayoutRoot“ Background=“Bisque“ Width=“250″ Height=“75″>
<
Canvas.Clip>
<
RectangleGeometry Rect=“0,0,250,75″/>
</
Canvas.Clip>
<
Button x:Name=“Btn1″ Canvas.Top=“-10″ Content=“Klick mich an“></Button>
<
Button x:Name=“Btn2″ Canvas.Top=“27″ Canvas.Left=“-15″ Content=“Button 2″ Foreground=“Chocolate“></Button>
<
TextBlock x:Name=“Tb1″ Canvas.Left=“225″ Canvas.Top=“64″ Foreground=“Black“ Canvas.ZIndex=“2″>Text</TextBlock>
<
Ellipse x:Name=“Ellipse“ Canvas.Top=“-10″ Canvas.Left=“210″ Width=“50″ Height=“50″ Canvas.ZIndex=“1″ Fill=“Black“></Ellipse>
</
Canvas>
</
Grid>

Möchte man beispielsweise erlauben, dass bis zu 5 Pixel über die Ränder gezeichnet werden darf, lässt sich das Clipping auch dementsprechend verändern.

<Canvas x:Name=“LayoutRoot“ Background=“Bisque“ Width=“250″ Height=“75″>
<
Canvas.Clip>
<
RectangleGeometry Rect=“-5,-5,260,85″/>
</
Canvas.Clip>
<
Button x:Name=“Btn1″ Canvas.Top=“-10″ Content=“Klick mich an“></Button>
<
Button x:Name=“Btn2″ Canvas.Top=“27″ Canvas.Left=“-15″ Content=“Button 2″ Foreground=“Chocolate“></Button>
<
TextBlock x:Name=“Tb1″ Canvas.Left=“225″ Canvas.Top=“64″ Foreground=“Black“ Canvas.ZIndex=“2″>Text</TextBlock>
<
Ellipse x:Name=“Ellipse“ Canvas.Top=“-10″ Canvas.Left=“210″ Width=“50″ Height=“50″ Canvas.ZIndex=“1″ Fill=“Black“></Ellipse>
</
Canvas>

Kommen wir nochmal auf die Eigenschaften Width und Height der Buttons zurück. Ziemlich am Anfang wurde bereits erklärt, dass diese Eigenschaften den Wert NaN enthalten, wenn keine Größenangabe gemacht wurde. Im nachfolgenden XAML-Code wird für den Schalter Btn1 eine Methode festgelegt, die beim Anklicken des Schalters aufgerufen werden soll.

<Canvas x:Name=“LayoutRoot“ Background=“Bisque“ Width=“250″ Height=“75″>
<
Canvas.Clip>
<
RectangleGeometry Rect=“-5,-5,260,85″/>
</
Canvas.Clip>
<
Button x:Name=“Btn1″ Canvas.Top=“5″ Content=“Klick mich an“ Canvas.Left=“6″ Click=“Btn1_Click“></Button>
<
Button x:Name=“Btn2″ Canvas.Top=“33″ Canvas.Left=“6″ Content=“Button 2″ Foreground=“Chocolate“></Button>
<
TextBlock x:Name=“Tb1″ Canvas.Left=“206″ Canvas.Top=“23″ Foreground=“Yellow“ Canvas.ZIndex=“2″>Text</TextBlock>
<
Ellipse x:Name=“Ellipse“ Canvas.Top=“5″ Canvas.Left=“194″ Width=“50″ Height=“50″ Canvas.ZIndex=“1″ Fill=“Black“></Ellipse>
</
Canvas>

Die Methode Btn1_Click ändert lediglich den Content des Schalters

private void Btn1_Click(object sender, RoutedEventArgs e)
{
Btn1.Content = „Du hast mich angeklickt!“;
}

Da für den Schalter keine Größenangaben getätigt wurden, ändert sich die Größe des Schalters automatisch nachdem darauf geklickt wurde.

Bei Angabe einer Größe für den Schalter bleibt diese nach Änderung des Contents erhalten. Verwendet man im XAML-Code beispielsweise eine Breite von 80 Pixeln wie in nachfolgendem XAML-Code, wird der programmatisch geänderte Text nicht vollständig angezeigt.

<Button x:Name=“Btn1″ Width=“80″ Canvas.Top=“5″ Content=“Klick mich an“ Canvas.Left=“6″ Click=“Btn1_Click“></Button>

Die automatische Anpassung der Größe hat Vor- und Nachteile. Da das Canvas keinerlei Layout-Logik umfasst, obliegt die Anordnung der Elemente dem Ersteller des XAML-Codes. Zudem können programmatische Änderungen zu unerwünschten Nebeneffekten führen. Zur Veranschaulichung habe ich die Elemente mittels des XAML-Codes ein wenig anders angeordnet.

<Canvas x:Name=“LayoutRoot“ Background=“Bisque“ Width=“250″ Height=“75″>
<
Canvas.Clip>
<
RectangleGeometry Rect=“-5,-5,260,85″/>
</
Canvas.Clip>
<
Button x:Name=“Btn1″ Canvas.Top=“5″ Content=“Klick mich an“ Canvas.Left=“6″ Click=“Btn1_Click“></Button>
<
Button x:Name=“Btn2″ Canvas.Top=“33″ Canvas.Left=“6″ Content=“Button 2″ Foreground=“Chocolate“></Button>
<
TextBlock x:Name=“Tb1″ Canvas.Left=“115″ Canvas.Top=“21″ Foreground=“Yellow“ Canvas.ZIndex=“2″>Text</TextBlock>
<
Ellipse x:Name=“Ellipse“ Canvas.Top=“5″ Canvas.Left=“102″ Width=“50″ Height=“50″ Canvas.ZIndex=“1″ Fill=“Black“></Ellipse>
</
Canvas>

Klickt der Anwender nun während der Ausführung auf den Schalter, wird dieser vergrößert und ein Teil davon wird von der Ellipse verdeckt. Die Ursache dafür liegt darin begründet, dass für die Ellipse und den Text mit Canvas.ZIndex die Reihenfolge festgelegt wurde, die anderen Elemente aber den ZIndex 0 haben und somit hinter der Ellipse und dem Text zum liegen kommen.

Damit der Schalter wieder drüber liegt, genügt es für diesen ebenfalls über Canvas.ZIndex eine Reihenfolge festzulegen.

<Button x:Name=“Btn1″ Canvas.Top=“5″ Content=“Klick mich an“ Canvas.Left=“6″ Click=“Btn1_Click“ Canvas.ZIndex=“3″></Button>

Sofern man die Reihenfolge der Elemente korrekt über den XAML-Code festlegt, ist die Verwendung von Canvas.ZIndex nicht erforderlich. Sobald aber programmatische Veränderungen ins Spiel kommen, ist die Verwendung von Canvas.ZIndex sehr hilfreich. Allerdings sollte man dann für alle Elemente den ZIndex festlegen um unerwünschte Nebeneffekte zu vermeiden.