UserControl in Silverlight for WinPhone 7

Heute habe ich mal versucht, das Brettspiel „Solitär“ (Peg Solitaire, Hi-Q) für WinPhone 7 zu programmieren. Bislang habe ich nur wenig Erfahrung in Silverlight oder WPF gesammelt, so dass ich das Terrain erst noch erforschen musste. Als Entwicklungsumgebung habe ich das frei verfügbare VisualStudio 2010 Express for WinPhone 7 inklusive Emulator gewählt.

Das Spielbrett war sehr einfach zu entwerfen: Ein UserControl erstellen mit DesignWidth/-Height = 480 x 480.  Wichtig: DesignWidth, NICHT echte Width und Height – denn nur so kann sich das Control in seinem Container frei skalieren. Das Spielbrett besteht aus einem Grid, das aus insgesamt sieben Rows und Columns besteht. Auch diese haben keine explizite Breite / Höhe zugewiesen bekommen, so dass die einzelnen Zellen jeweils genau 1/7 der Gesamtbreite/-höhe einnehmen. Hintergrundfarbe schwarz, bzw. vom übergeordneten Element „geerbt“  (also der Applikation bzw. MainPage.xaml).

Ein weiteres UserControl sollte ein leeres Spielfeld darstellen. Das Design dafür war simpel:  Ein quadratisches Grid (bzw. eigentlich wieder eines ohne explizite Breiten- und Höhenangabe). Es soll sich einfach in ein Spielbrett-Grid-Element einfügen. Deshalb wieder nur Designheight und -width angegeben. Tut man das nicht, schrumpft das Usercontrol auf Größe null in der Design-Vorschau.

Innerhalb des Spielfelds soll eine weißer Kreis (Ellipse) mit schwarzer Füllung eine Spielstein-Position anzeigen. Der erste Ansatz war also, eine Ellipse mit
– Breite und Höhe 50
– Strokewidth von 10
– Farbe weiß und Füllung schwarz
– Alignments = „Center“
zu erstellen. So weit so gut: Auf dem normalen Spielfeld (480 x 480) hat es prima ausgesehen. Verändert man jedoch die Größe des Spielbretts, skaliert diese Ellipse nicht mit. Kann eigentlich kein großes Problem, dachte ich mir – schließlich ist Silverlight laut Microsoft auf vektororientierte Darstellung ausgerichtet. Mein Fehler war, dass die Breite und Höhe in absoluten Größen angegeben waren. Aber wie ändern? Höhe und Breite auf „Auto“ einzustellen ließ die Ellipse auf null schrumpfen. Dummerweise kann man auch nicht „0.8*“ – also eine relative Breite oder Höhe einstellen. Das ist offensichtlich nur den Grids vorbehalten. Nächster Versuch war, die Alignments auf „Stretch“ einzustellen (Height und Width-Attribute entfernen). Das funktioniert – jedoch stieß die Ellipse am Rand an, was ich nicht wollte. Ein weiteres – größeres Problem – war, dass die Strokewidth auch nicht skalierte. Recherchen im Netz zeigten mir, dass auch andere Leute mit ähnlichen Problemen kämpften – Antworten jedoch rar waren. Doch eigentlich sollte es kein exotischer Wunsch sein, ein skaliebares UserControl zu erstellen. Also weiterprobieren:

In der MSDN-Library bin ich dann auf das ViewBox-Control gestoßen. Das kann zwar nur genau EIN Element aufnehmen, dieses wird dann aber auf die Größe des übergeordneten Elements skaliert. Genau was ich wollte. Aber leider ist das ViewBox-Control nur im „normalen“ Silverlight verfügbar – nicht in der WinPhone 7-Edition.

Als nächstes kreisten meine Gedanken um die Idee, dieses Problem per  Programmierung in der Code-Behind-Datei anzugehen. Immerhin kann man da ein Resize-Ereignis (oder so) abfangen. Richtig gut gefallen hat mir die Idee jedoch nicht, da meines Erachtens alles was mit Design zu tun hat, in XAML (am besten von einem Designer) erledigt werden sollte und nicht im Programmcode.

Die endgültige Lösung war, auf die Strokewidth zu verzichten und stattdessen zwei gefüllte Ellipsen zu verwenden. Eine größere, weiße und darauf eine kleinere schwarze. Die Skalierung der beiden Ellipsen geschieht über ein Grid-Konstrukt: Zunächst ein Grid mit drei Rows und drei Columns jeweils mit der Breite 0.1*, 0.8*, 0.1*. Im mittleren Feld wurde eine weiße Ellipse erzeugt. Außerdem wurde innerhalb des mittleren Felds ein weiteres, ähnliches Grid erstellt (0.2*, 0.6*, 0.2*), dessen mittleres Element eine schwarze Ellipse aufgenommen hat. Die Ellipsen wurden mit Alignments = „Stretch“ und jeweils ohne Höhe und Breite  angelegt. Und voilà – endlich hat das Spielfeld-UserControl wie gewünscht funktioniert und konnte beliebig skalieren.

Eine allgemeine Lösung für Skalierung von UserControls stellt diese Lösung leider nicht dar. Wird z.B. Text innerhalb eines UserControls verwendet, müsste die Fontgröße verändert werden. Aber es muss ja noch Aufgaben für die Zukunft übrigbleiben…

Spielbrett:

<UserControl x:Class="Complete_Peg_Solitaire.BoardPegSolitaire"
 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"
 xmlns:BoardHole="clr-namespace:Complete_Peg_Solitaire"
 mc:Ignorable="d"
 d:DesignHeight="480" d:DesignWidth="480">

 <Grid x:Name="LayoutRoot" Loaded="LayoutRoot_Loaded">
 <Grid.ColumnDefinitions>
 <ColumnDefinition />
 <ColumnDefinition />
 <ColumnDefinition />
 <ColumnDefinition />
 <ColumnDefinition />
 <ColumnDefinition />
 <ColumnDefinition />
 </Grid.ColumnDefinitions>
 <Grid.RowDefinitions>
 <RowDefinition />
 <RowDefinition />
 <RowDefinition />
 <RowDefinition />
 <RowDefinition />
 <RowDefinition />
 <RowDefinition />
 </Grid.RowDefinitions>
 </Grid>
</UserControl>

Spielfeld:

<UserControl x:Class="Complete_Peg_Solitaire.BoardHole"
 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:DesignHeight="60" d:DesignWidth="60" UseLayoutRounding="False">
 
 <Grid x:Name="LayoutRoot" Background="#FF000000" UseLayoutRounding="False">
 <Grid.RowDefinitions>
 <RowDefinition Height="0.1*"/>
 <RowDefinition Height="0.8*"/>
 <RowDefinition Height="0.1*"/>
 </Grid.RowDefinitions>
 <Grid.ColumnDefinitions>
 <ColumnDefinition Width="0.1*"/>
 <ColumnDefinition Width="0.8*"/>
 <ColumnDefinition Width="0.1*"/>
 </Grid.ColumnDefinitions>
 <Ellipse Grid.Row="1" Grid.Column="1" HorizontalAlignment="Stretch" Stroke="White" VerticalAlignment="Stretch" Fill="White"/>
 <Grid Grid.Row="1" Grid.Column="1" ShowGridLines="False" UseLayoutRounding="False">
 <Grid.RowDefinitions>
 <RowDefinition Height="0.2*"/>
 <RowDefinition Height="0.6*"/>
 <RowDefinition Height="0.2*"/>
 </Grid.RowDefinitions>
 <Grid.ColumnDefinitions>
 <ColumnDefinition Width="0.2*"/>
 <ColumnDefinition Width="0.6*"/>
 <ColumnDefinition Width="0.2*"/>
 </Grid.ColumnDefinitions>
 <Ellipse Grid.Row="1" Grid.Column="1" HorizontalAlignment="Stretch" Stroke="White" VerticalAlignment="Stretch" Fill="Black"/>
 </Grid>
 </Grid>
</UserControl>
Advertisements