BUILDING METROBONES

Much of the work of building an application such as MetroBones is in creating and arranging its controls. To save space, I won’t discuss where every control is positioned for MetroBones. You can look at Figure 21-1 to see the general arrangement and you can download the example program to see the details.

While I don’t want to cover the code in complete detail, there are a few points worth mentioning.

Control Layout

The program’s left side holds a stack of Image controls holding skeleton images in various states. The program hides and displays these controls to show the game’s current state.

The program’s right side contains a vertical StackPanel that holds a series of other controls. Those controls include:

  • A horizontal StackPanel holding the current word’s letters
  • A TextBlock that can indicate when the user wins or loses
  • Three horizontal StackPanels holding the letter buttons
  • The New Game button

The current word’s letters are displayed as Border controls that hold TextBlocks. They are created at run time when the program picks a word.

Those controls and the program’s buttons use styles defined in the program’s resources to give them their sizes and appearance. The following section describes the program’s styles.

XAML Code

The most interesting part of XAML code is the definition of the resources that determine the appearance of the program’s buttons. The following code shows the application’s resources. Those resources are available to the later XAML code to set the controls’ appearance.

<Page.Resources>
    <LinearGradientBrush x:Key="EnabledBorderBrush"
     StartPoint="0,0" EndPoint="0,1">
        <GradientStop Color="LightGreen" Offset="0"/>
        <GradientStop Color="DarkGreen" Offset="1"/>
    </LinearGradientBrush>
    <LinearGradientBrush x:Key="EnabledFillBrush"
     StartPoint="0,0" EndPoint="0,1">
        <GradientStop Color="DarkGreen" Offset="0"/>
        <GradientStop Color="LightGreen" Offset="1"/>
    </LinearGradientBrush>
    <LinearGradientBrush x:Key="DisabledFillBrush"
     StartPoint="0,0" EndPoint="0,1">
        <GradientStop Color="DarkGray" Offset="0"/>
        <GradientStop Color="LightGray" Offset="1"/>
    </LinearGradientBrush>
    <LinearGradientBrush x:Key="DisabledBorderBrush"
     StartPoint="0,0" EndPoint="0,1">
        <GradientStop Color="LightGray" Offset="0"/>
        <GradientStop Color="DarkGray" Offset="1"/>
    </LinearGradientBrush>
 
    <Style x:Key="BasicButton" TargetType="Button">
        <Setter Property="Foreground" Value="Black"/>
        <Setter Property="Width" Value="45"/>
        <Setter Property="Height" Value="45"/>
        <Setter Property="Margin" Value="3"/>
        <Setter Property="FontFamily" Value="Verdana"/>
        <Setter Property="FontSize" Value="16"/>
        <Setter Property="FontWeight" Value="Bold"/>
        <Setter Property="Background" Value="{StaticResource EnabledFillBrush}"/>
        <Setter Property="BorderBrush"
            Value="{StaticResource EnabledBorderBrush}"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="Button">
                    <Border
                        BorderThickness="3"
                        CornerRadius="7"
                        Background="{TemplateBinding Background}"
                     BorderBrush="{TemplateBinding BorderBrush}">
                        <Grid>
                            <ContentPresenter HorizontalAlignment="Center" 
                             VerticalAlignment="Center" Name="content"/>
                        </Grid>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
 
    <Style x:Key="EnabledButton" TargetType="Button"
     BasedOn="{StaticResource BasicButton}"/>
    <Style x:Key="DisabledButton" TargetType="Button"
     BasedOn="{StaticResource BasicButton}">
        <Setter Property="Background" Value="{StaticResource DisabledFillBrush}"/>
        <Setter Property="BorderBrush"
            Value="{StaticResource DisabledBorderBrush}"/>
        <Setter Property="Foreground" Value="Gray"/>
        <Setter Property="IsEnabled" Value="False"/>
    </Style>
 
    <Style x:Key="LetterBorder" TargetType="Border">
        <Setter Property="BorderBrush"
            Value="{StaticResource EnabledBorderBrush}"/>
        <Setter Property="BorderThickness" Value="3"/>
        <Setter Property="CornerRadius" Value="7"/>
        <Setter Property="Width" Value="45"/>
        <Setter Property="Height" Value="45"/>
        <Setter Property="Margin" Value="3"/>
        <Setter Property="Background"
            Value="{StaticResource EnabledFillBrush}"/>
    </Style>
    <Style x:Key="LetterTextBlock" TargetType="TextBlock">
        <Setter Property="FontFamily" Value="Verdana"/>
        <Setter Property="FontSize" Value="16"/>
        <Setter Property="FontWeight" Value="Bold"/>
        <Setter Property="Foreground" Value="Black"/>
        <Setter Property="HorizontalAlignment" Value="Center"/>
        <Setter Property="VerticalAlignment" Value="Center"/>
    </Style>
</Page.Resources>

The resources start by defining several LinearGradientBrushes. The program uses the EnabledBorderBrush and EnabledFillBrush to determine how the buttons are outlined and filled when they are enabled. Similarly the DisabledBorderBrush and DisabledFillBrush determine the buttons’ appearance when they are disabled.

The BasicButton style defines the basic characteristics of the program’s buttons. It sets the buttons’ default text color, size, and font. It sets the border and fill colors to the EnabledBorderBrush and EnabledFillBrush.

The BasicButton style also sets the buttons’ template to determine the controls that make up the button. This template uses a Border control with rounded corners that holds a Grid that contains the buttons’ content. I created this template because the normal button template includes a lot of interior space and this program didn’t have enough room to fit in all of the letter buttons when using that template.

Unfortunately, when you use a new template the buttons forfeit all of the features of the default template. In particular, they lose the ability to respond to events so, for example, they don’t flash when the user taps them. I could have added new behaviors to the new template to make the buttons behave in that way but it would have been more work and didn’t seem necessary for this example.

After defining the BasicButton style, the code then defines two styles named EnabledButton and DisabledButton that are based on the BasicButton. EnabledButton simply copies the features of BasicButton.

DisabledButton copies the features of BasicButton and then overrides some of its properties to make the button look disabled. It sets the buttons’ border and fill brushes to DisabledBorderBrush and DisabledFillBrush. It also sets the buttons’ foreground color (which is used to draw text) to Gray and sets the IsEnabled property to False so the buttons won’t respond to user taps.

The resources finish by defining styles for the controls that display the current word’s letters. The LetterBorder and LetterTextBlock styles define the appearances of the Border and TextBlock controls that display the letters.

The later XAML code uses some of these styles to determine the controls’ appearance. For example, the following code shows how the program defines the first row of letter buttons:

<StackPanel Orientation="Horizontal">
    <Button Style="{StaticResource DisabledButton}" Name="btnQ" Content="Q"/>
    <Button Style="{StaticResource DisabledButton}" Name="btnW" Content="W"/>
    <Button Style="{StaticResource DisabledButton}" Name="btnE" Content="E"/>
    <Button Style="{StaticResource DisabledButton}" Name="btnR" Content="R"/>
    <Button Style="{StaticResource DisabledButton}" Name="btnT" Content="T"/>
    <Button Style="{StaticResource DisabledButton}" Name="btnY" Content="Y"/>
    <Button Style="{StaticResource DisabledButton}" Name="btnU" Content="U"/>
    <Button Style="{StaticResource DisabledButton}" Name="btnI" Content="I"/>
    <Button Style="{StaticResource DisabledButton}" Name="btnO" Content="O"/>
    <Button Style="{StaticResource DisabledButton}" Name="btnP" Content="P"/>
</StackPanel>

The buttons are contained in a horizontal StackPanel. Each button uses the DisabledButton style so they are initially disabled. The program’s Visual Basic code enables the buttons when they are needed. Each button has a name that is used by the Visual Basic code and displays a single letter.

Zooming in on the Controls

The XAML code that arranges the MetroBones controls create a Grid that is 728 pixels wide and 480 pixels tall. I picked that size because it fits nicely on a Windows phone. A Windows Phone device is required to provide an 800 × 480 pixel screen so that made sense for those devices. (There have long been rumors of new devices with different screen sizes coming, but they haven’t appeared yet.)

In contrast, a Windows 8 system running Metro applications could have a large number of screen sizes with varying pixel densities. Anticipated sizes range from 10.1” to 27” screens with pixel densities ranging from 96 dpi to 291 dpi. (See “Scaling to Different Screens” at http://blogs.msdn.com/b/b8/archive/2012/03/21/scaling-to-different-screens.aspx for a discussion of screen size issues.)

You could simply use this 800 × 480 pixel size. The application would fit on most Windows 8 devices but the result would be a relatively small program centered in the middle of a large screen for many devices.

A better approach is to use Silverlight’s arranging controls such as Grid, StackPanel, and WrapGrid to make the application take best advantage of whatever space is available.

For MetroBones I used a simpler approach. Because the program doesn’t really need more space to display more information, I just enlarged it. I put the program’s main Grid control inside a Viewbox. The Viewbox enlarges its contents to fill the space occupied by the Viewbox.

One of the nice things about WPF controls, including those used by Silverlight, is that they use the DirectX graphic library to make themselves scalable. That means you can enlarge the controls as much as you like and they will remain smooth and not become grainy like a greatly enlarged bitmap does. The result in MetroBones is that the image of the skeleton and all of the buttons simply get bigger to fill the available space.

The following code shows the part of the MetroBones program’s XAML code that contains the Viewbox. I’ve omitted most of the code to save space.

<!-- The Viewbox enlarges the basic layout to fill the available area. -->
<Viewbox Margin="30">
    <!-- Content area -->
    <Grid Width="728" Height="480">
    ...
    </Grid> <!-- End of content area -->
</Viewbox>

The following section explains how the Visual Basic code uses the styles defined in the XAML code at run time.

Visual Basic Code

The MetroBones program’s Visual Basic code is relatively straightforward but it does demonstrate a few useful tricks. The code includes only three event handlers, which are described in the following sections.

The Page_Loaded Event Handler

The Page_Loaded event handler executes when the page is loaded. The following code shows how this event handler initializes the application so it is ready for use:

' Array of letter buttons.
Private LetterButtons() As Button
 
' Array of skeleton Image controls.
Private SkeletonImages() As Image
 
' The index of the current skeleton picture.
Private CurrentPictureIndex As Integer = 0
 
' Words.
Private Words() As String
 
' The current word.
Private CurrentWord As String = ""
 
' Controls used to display letters.
Private LetterTextBlocks As New List(Of TextBlock)()
 
' Prepare the program for use.
Private Async Sub Page_Loaded(sender As Object, e As RoutedEventArgs) _
 Handles MyBase.Loaded
    ' Make the array of letter Buttons.
    LetterButtons = New Button() _
    {
        btnQ, btnW, btnE, btnR, btnT, btnY, btnU, btnI, btnO, btnP,
        btnA, btnS, btnD, btnF, btnG, btnH, btnJ, btnK, btnL,
        btnZ, btnX, btnC, btnV, btnB, btnN, btnM
    }
 
    ' Make the array of skeleton Images.
    SkeletonImages = New Image() _
    {
         img0, img1, img2, img3, img4, img5, img6
    }
 
    ' Prepare the letter buttons.
    For Each btn As Button In LetterButtons
        AddHandler btn.Click, AddressOf btnLetter_Click
        btn.Tag = btn.Content.ToString()
    Next btn
 
    ' Load the words.
    Words = Await ReadAssetFileLinesAsync("Words.txt")
End Sub

The code starts by declaring several class-level variables that are used to keep track of the program’s state. These variables include:

  • LetterButtons — The Button controls that the user can click to guess a letter
  • SkeletonImages — The Image controls that represent the skeleton in various stages of completion
  • CurrentPictureIndex — The index of the currently visible skeleton Image control
  • Words — An array of words from which the game randomly picks
  • CurrentWord — The currently selected word
  • LetterTextBlocks — The TextBlock controls that hold the current word’s letters

The Page_Loaded event handler initializes some of these variables. It starts by creating the LetterButtons and SkeletonImages arrays, filling them with references to the appropriate controls.

Next the code prepares the letter buttons. It loops over the buttons in the LetterButtons array to add the btnLetter_Click event handler to each button and to set each button’s Tag property to the letter it displays.

The code then loads the dictionary file Words.txt. Loading a file in a Metro-style application is somewhat complicated so it’s described separately in the following section.

Loading Files

Loading a file is a bit more complicated in a Metro-style application than it is in a desktop application. A Metro-style application doesn’t have access to the full filesystem. It also doesn’t have access to the very convenient My namespace so it can’t use My.Computer.FileSystem.ReadAllText to easily read the file. Even the System.IO.File class that is provided for Windows Phone applications doesn’t include the ReadAllText or ReadAllLines methods.

Metro-style applications also assume you will often be downloading files over the Internet. To make that kind of download more efficient, the Metro-style tools assume downloads will be made asynchronously, and that complicates the program’s code.

You can make it a little easier to handle the asynchronous download of Words.txt in the MetroBones program by using the Async and Await keywords described in the section “Using Async and Await” in Chapter 16. When the program should wait for an asynchronous operation to complete, it adds the Await keyword. Any method that uses Await must be declared with the Async keyword so code that calls it can use Await to wait for it to complete. Notice that the Page_Loaded event handler shown in the code in the previous section is declared with the Async keyword.

To load the dictionary file, the MetroBones application uses a resource file. At design time, I used the Project menu’s Add Existing Item command to add the file Words.txt to the project. I selected the file in Solution Explorer and set its Builds Action property to Resource.

The following code shows how the Page_Loaded event handler loads the dictionary file:

Words = Await ReadAssetFileLinesAsync("Words.txt")

This code simply calls the ReadAssetFileLinesAsync method and uses the Await keyword to wait for that method to return. (By convention, the names of asynchronous methods end with “Async.”)

The following code shows the ReadAssetFileLinesAsync method:

' Return the file's contents as an array of lines.
Public Async Function ReadAssetFileLinesAsync(filename As String) _ 
 As Task(Of String())
    Dim txt As String = Await ReadAssetFileAsync(filename)
    Return txt.Replace(vbCrLf, vbCr).Split(vbCr)
End Function

The ReadAssetFileLinesAsync method also uses the Await keyword so it is declared Async. It returns a Task(Of String()). That means it returns a task that returns an array of strings. The calling code (the Page_Loaded event handler) uses Await to wait for the task to finish so it can collect the resulting string array.

The ReadAssetFileLinesAsync method calls the ReadAssetFileAsync method described next to get the contents of an asset file, using the Await keyword to wait for ReadAssetFileAsync to finish. It then splits the file into lines as before.

The following code shows the ReadAssetFileAsync method:

' Return the file's contents as a string.
Public Async Function ReadAssetFileAsync(filename As String) As Task(Of String)
    ' Get the installed location.
    Dim storage_folder As StorageFolder =
        ApplicationModel.Package.Current.InstalledLocation
    storage_folder = Await storage_folder.GetFolderAsync("Assets")
 
    ' Get the file.
    Dim storage_file As StorageFile =
        Await storage_folder.GetFileAsync(filename)
 
    ' Use a StreamReader to read the file.
    Using stream_reader As
     New StreamReader(Await storage_file.OpenStreamForReadAsync())
        Return stream_reader.ReadToEnd()
    End Using
End Function

This method also uses the Await keyword so it is declared Async. It returns a Task(Of String): a task that returns a string.

The code creates a StorageFolder object that represents the program’s installed location. It uses the GetFolderAsync method to get the installation folder’s Assets subfolder, which is where the Words.txt file is stored. It uses Await to wait for GetFolderAsync to finish.

Next the code uses the folder object’s GetFileAsync method to get a StorageFile object representing the file, again using the Await keyword to wait for the operation to finish.

The code then creates a StreamReader object associated with the file, passing the StreamReader’s constructor the result of the StorageFile object’s OpenStreamForReadAsync method, using the Await keyword yet again.

Finally, the code uses the StreamReader’s ReadToEnd method to read the file’s contents into a string and returns the result.

This is a very roundabout method for loading a file that is installed with the application. In an application that loaded large files over the Internet, handling files asynchronously would be much more important.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.119.136