-
Notifications
You must be signed in to change notification settings - Fork 5
06 Advanced Views
In this section we are going to have a look at some more advanced view features. These are not any more difficult to use that what we have learnt so far, but they are more powerful and help create better apps and games.
As pages get more complex, we may want to refactor some controls out. Refactoring is very useful as it helps break up larger chunks of work. Just like the fact that we could have a single method that does all the work of an app, we could have a single page that contains all the controls on the screen. Just as it is better to move chunks of code out of a method, it is better to move chunks of UI out of the page.
In our game, it will be much better to move the game board into a custom control. This way, we can just add a single element to our page, and then a board appears. Also, because we have separated the board for the other elements, such as back buttons or score labels, we can focus the work in each view.
To make this happen, we need a new view for our board:
- Right-click the .NET Standard project
- Select
Add|New Item... - Select
Content View - Enter a page name:
BoardView.xaml
This will create a XAML file, just like when we created a new page. In fact, the only difference between a page and a view is the base type. A typical page derives from ContentPage and a typical view derives from ContentView.
We can go ahead and delete the default template contents so we are left with an empty view:
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:TicTacToe"
x:Class="TicTacToe.BoardView">
</ContentView>Now that we have our view, we can start building our board. The first thing we want to do is decide on a layout for the board. This is easy. Since we are wanting to create a grid of buttons, we can use the <Grid> layout.
We can define the grid rows and grid columns using the RowDefinitions and ColumnDefinitions properties. The way we do this is to add these properties as property elements, since the values will be complex objects and not plain strings. For tic-tac-toe, there are three rows and three columns of buttons. But, in between each row and column, we want to place a thin grid line.
This brings us to a total of five rows and five columns: three for the buttons and two for the lines. For each row or column, we add a new <RowDefinition> and <ColumnDefinition> element respectively:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="2" />
<RowDefinition Height="*" />
<RowDefinition Height="2" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="2" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="2" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
</Grid>For each row or column definition, we specify a size. This could be one of three options:
-
*(star) - expands to fill as much space as it can -
Auto- shrinks to be as small as possible -
<number>- stays at the specified size
In our example, we have *, 2, *, 2, * for both the rows and columns. This will make the first, third and fifth row/column expand to fill as much space as possible. This means that each row/column will be a third of the available space.
The next part of the board is the buttons. This is pretty straight forward: just add nine buttons to the grid.
If we just do that, then all the buttons will be in the first cell. This is obviously not how tic-tac-toe works. To move the buttons into specific cells, we can make use of the grid's attached properties.
Attached properties are similar to normal properties in that we can set values on objects. However, the difference is in where these properties are defined: attached properties are somewhere other than the object.
For example, we need to set the grid row for the button. Because we are changing the button's row, we might look at the button object. However, there is no Row property on a button. This is because it exists as an attached property on the Grid type.
Setting these properties in XAML is similar to normal properties, except that we have to qualify them with the name of attached property container. In the case of the Row property, the container is Grid:
<Button Grid.Row="2" />You can read more about attached properties in the docs.
This adds a button to the third row of the grid that contains it. So, for our game, we added nine buttons:
<!-- row 1 -->
<Button Grid.Row="0" Grid.Column="0" />
<Button Grid.Row="0" Grid.Column="2" />
<Button Grid.Row="0" Grid.Column="4" />
<!-- row 2 -->
<Button Grid.Row="2" Grid.Column="0" />
<Button Grid.Row="2" Grid.Column="2" />
<Button Grid.Row="2" Grid.Column="4" />
<!-- row 3 -->
<Button Grid.Row="4" Grid.Column="0" />
<Button Grid.Row="4" Grid.Column="2" />
<Button Grid.Row="4" Grid.Column="4" />Now, we should have nice big buttons in our grid. The last thing to add is the grid lines. This we can do with the simple <BoxView> control. This control is just a plain, colored box.
If we add the box, they will only be able to fit into one cell by default. To make the line reach across the entire board, we can use the RowSpan and ColumnSpan attached properties:
<BoxView Grid.Row="1" Grid.ColumnSpan="5" />
<BoxView Grid.Row="3" Grid.ColumnSpan="5" />
<BoxView Grid.Column="1" Grid.RowSpan="5" />
<BoxView Grid.Column="3" Grid.RowSpan="5" />If we were to look at this view now, we will see that we have huge blue buttons and no lines. This is because the buttons are using the global style we set, and the lines have no size or color yet.
To turn this grid into a tic-tac-toe board, we can use view-specific styles. Just like when we added styles to the app resources, we can add resource to this view.
Because resources are scoped, we can control who can see the styles. When we added styles to the app, the entire app and all the pages could see them. We are now going to add styles to the view, so only the view can see them.
The ContentView type also has a Resources property that we use to set a new <ResourceDictionary>:
<ContentView>
<ContentView.Resources>
<ResourceDictionary>
</ResourceDictionary>
</ContentView.Resources>
<ContentView>Now that we have our dictionary, we can use implicit styles to style all the buttons and lines. We will need to change the button's colors and the lines' color and size:
<Style TargetType="BoxView">
<Setter Property="Color" Value="{StaticResource AlmostBlack}" />
<Setter Property="VerticalOptions" Value="FillAndExpand" />
<Setter Property="HorizontalOptions" Value="FillAndExpand" />
</Style>
<Style TargetType="Button">
<Setter Property="BackgroundColor" Value="{StaticResource AlmostWhite}" />
<Setter Property="TextColor" Value="{StaticResource AlmostBlack}" />
<Setter Property="VerticalOptions" Value="FillAndExpand" />
<Setter Property="HorizontalOptions" Value="FillAndExpand" />
</Style>
When working with the Xamarin.Forms Previewer, we often need some data to see what the final screen will look like. In the case of our app, the grid is all stretched and we still don't know what the buttons will look like when they have text in them.
This is where design-time data comes in handy. All we have to do is add a few namespaces to our view or page:
<ContentView xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
mc:Ignorable="d" />Then, we can use the d prefix to add properties that are only relevant at design time. These properties are set as they normally would, but because they have the d prefix, they are ignored at runtime.
So, the first this we can do is add some text to a few of the buttons:
<Button d:Text="X" />And, to make the grid square, we can set the layout properties of the grid:
<Grid d:WidthRequest="200" d:HeightRequest="200"
d:HorizontalOptions="Center"
d:VerticalOptions="Center">When we do this, we can see that the text is a bit small on the buttons. Because we are using styles, we can just bump up the FontSize:
<Setter Property="FontSize" Value="40" />The Xamarin.Forms Previewer now has a useful preview of what the control will look like at runtime: