Skip to content
Home » Basics of .NET MAUI – Part 10 – Styles in XAML

Basics of .NET MAUI – Part 10 – Styles in XAML

Spread the love

In the previous part of the series we created all the major pages and content views. But we didn’t care so much about how the particular elements look. We’ll fix this in this part by adding styles.

The source code for this article is available on Github.

When you run your app, all the views already have some default styles applied. These styles are defined in the Styles.xaml file in the Resources -> Styles folder. But usually you will want to define your own styles, so let’s see how to do it.

About Styles

If you look at the screenshots from the previous part of the series, you will notice two things. On one hand, there’s a lot of repetitive code that determines how the particular views should look. Here’s an example from the GameOverPage:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage ...>
    <Grid
        ...
            <Button
                Text="Play Again"
                FontSize="30"
                WidthRequest="300"
                Margin="0, 0, 50, 20" />
            <Button
                Text="Quit"
                FontSize="30"
                WidthRequest="300"
                Margin="0, 0, 50, 20" />
        </FlexLayout>
    ...

The two buttons have some properties set to the same values, like FontSize, WidthRequest or Margin. And there are lots of examples like this throughout the application. This means repetitive code. Lots of repetitive code. Imagine you decide to change the FontSize property to 40 on all buttons. You would have to do it for every button individually, which is time-consuming and error-prone. What if there were 75 buttons altogether?

On the other hand, if we’re at buttons, have a look at how they differ across the pages:

Different sizes, different font sizes, etc. Maybe they would look better if they had a more uniform look and feel.

These two issues can be easily addressed by defining styles in one place and applying them to all the views that should share them. Naturally, sometimes you may want to make one or two views look different than all the others. It’s also possible, because styles in XAML can be defined for particular views, layouts, pages or globally, for the entire app.

Style Object and ResourceDictionary

To create a style in XAML we use the Style object. The Style object contains a collection of property values. You can assign the object to any number of visual elements (which are objects that inherit from VisualElement, so the views, layouts, and so on). Let’s have a look at the buttons in the GameOverPage again:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage ...>
    <Grid
        ...
            <Button
                Text="Play Again"
                FontSize="30"
                WidthRequest="300"
                Margin="0, 0, 50, 20" />
            <Button
                Text="Quit"
                FontSize="30"
                WidthRequest="300"
                Margin="0, 0, 50, 20" />
        </FlexLayout>
    ...

Now, which property values do they share? They have the same FontSize, WidthRequest and Margin. So, instead of duplicating the code, let’s create a style and apply it to them. In order to do that, we have to add a ResourceDictionary to the page, so let’s see how to do that first.

A ResourceDictionary is an object in which we define one or more styles. It’s the content property of the ContentPage.Resources object, so it may be omitted.

Each style contains one or more Setter objects. Each Setter object has two properties: Property and Value. The Property property (sounds a bit awkward) is set to the name of the property of the object to which the style is to be applied. The Value property is, yes, you guessed it, the value we want that property to be set to. If this sounds complicated to you, then don’t worry. Let’s create a ResourceDictionary in the GameOverPage with one style that we will apply to the buttons. Here’s the code:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage ...>

    <ContentPage.Resources>
        <ResourceDictionary>
            <Style TargetType="Button">
                <Setter Property="FontSize" Value="30" />
                <Setter Property="WidthRequest" Value="300" />
                <Setter Property="Margin" Value="0, 0, 50, 20" />
            </Style>
        </ResourceDictionary>
    </ContentPage.Resources>
    
    <Grid
        ...
        <FlexLayout
            Grid.Row="3"
            JustifyContent="Center">
            <Button
                Text="Play Again" />
            <Button
                Text="Quit" />
        </FlexLayout>
    ...

As I mentioned before, we can omit the ResourceDictionary because it’s the content property of ContentPage.Resources. The following code will work just as fine:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage ...>

    <ContentPage.Resources>
        <Style TargetType="Button">
            <Setter Property="FontSize" Value="30" />
            <Setter Property="WidthRequest" Value="300" />
            <Setter Property="Margin" Value="0, 0, 50, 20" />
        </Style>
    </ContentPage.Resources>
    
    <Grid
        ...

Anyway, here we have one style. One thing to keep in mind is that we always have to set the TargetType property of the Style object to the name of the VisualElement that the style will be applied to. Here we’re going to apply it to the Button objects.

Have a look at the two buttons inside the FlexLayout again. We removed all the properties that are defined in the style and only left those that differ for each instance. So, the style we defined in the ResourceDictionary applies to both the buttons and if there were more buttons, it would apply to all of them. Let’s now change the style to look like so:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage ...>

    <ContentPage.Resources>
        <Style TargetType="Button">
            <Setter Property="FontSize" Value="40" />
            <Setter Property="WidthRequest" Value="300" />
            <Setter Property="Margin" Value="0, 0, 50, 20" />
            <Setter Property="HeightRequest" Value="100" />
            <Setter Property="BackgroundColor" Value="Black" />
            <Setter Property="CornerRadius" Value="50" />            
        </Style>
    </ContentPage.Resources>
    
    <Grid
        ... 

So, we changed the value of the FontSize property from 30 to 40 and we added some more properties. You have to write this code only once, right here in the ResourceDictionary, and the style will be applied to all the buttons on the page. If you run the app (with the GameOverPage set in AppShell.xaml), the two buttons will look like so:

Fine, but what if we wanted to apply the style only to some elements of a particular type and another style to the others? Well, this is where implicit and explicit styles come into play.

Implicit vs Explicit Styles

The styles defined above are so-called implicit styles. Implicit styles are applied to all elements of the type defined in the TargetType property.

We can also define explicit types and then apply them only to those instances of the type defined in the TargetType property that we want to. To do that, we must specify an x:Key attribute in the Style object and then explicitly apply the styles to the elements we want using the value assigned to that attribute.

Again, things will get clearer when you see it in action. Still in the GameOverPage, have a look at the three labels:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage ...>
...
    <Grid
        RowDefinitions="2*, 2*, 2*, *">
        <Label 
            Text="Game Over"
            FontSize="120"
            FontAttributes="Bold"
            HorizontalOptions="Center" />
        <Label 
            Grid.Row="1"
            Text="There's only one player with any money left."
            FontSize="40"
            FontAttributes="Bold"
            HorizontalOptions="Center" />
        <Label 
            Grid.Row="2"
            Text="The winner is Player 2, having started at $1000, winning at $396."
            FontSize="40"
            FontAttributes="Bold"
            HorizontalOptions="Center" />
        <FlexLayout
            ...

They all share the values of the FontAttributes and HorizontalOptions properties, but the FontSize property of the first label is set to 120, whereas the FontSize of the other two is set to 40. So, let’s create two styles for the labels. These are going to be explicit styles, so we need to specify the x:Key attribute for them. We can use any value for that attribute we want. A descriptive one wouldn’t be that bad. So, let’s add the two styles to the ResourceDictionary and then apply them to the particular labels:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage ...>

    <ContentPage.Resources>
        <Style TargetType="Button">
            ...
        </Style>

        <Style x:Key="labelSmallStyle" TargetType="Label">
            <Setter Property="FontAttributes" Value="Bold" />
            <Setter Property="HorizontalOptions" Value="Center" />
            <Setter Property="FontSize" Value="40" />
        </Style>

        <Style x:Key="labelLargeStyle" TargetType="Label">
            <Setter Property="FontAttributes" Value="Bold" />
            <Setter Property="HorizontalOptions" Value="Center" />
            <Setter Property="FontSize" Value="120" />
        </Style>
    </ContentPage.Resources>
    
    <Grid
        RowDefinitions="2*, 2*, 2*, *">
        <Label 
            Text="Game Over"
            Style="{StaticResource labelLargeStyle}" />
        <Label 
            Grid.Row="1"
            Text="There's only one player with any money left."
            Style="{StaticResource labelSmallStyle}" />
        <Label 
            Grid.Row="2"
            Text="The winner is Player 2, having started at $1000, winning at $396."
            Style="{StaticResource labelSmallStyle}" />
        <FlexLayout
            ...

The labelLargeStyle is applied only to the first label. The labelSmallStyle is applied to the other two. Watch the syntax that we use to apply style to an object. We use the Style property and set it to something that looks weird. You will run into syntax like that quite often in XAML. The curly braces mean that we have a so-called markup extension here. We’re not going to go into details here because markup extensions will be the topic of one of the following parts of the series. Just remember that we use the StaticResource (for styles that remain unchanged for the duration of the application) or DynamicResource (if the style may change) markup extension with the key specified in the ResourceDictionary to apply a style.  

You may have noticed that the two label styles in the ResourceDictionary still share some values. They both have the same values of FontAttributes and HorizontalOptions. This can be simplified even further. Styles can inherit from other styles.

Style Inheritance

In order to reduce code duplication, you can inherit styles from other styles that share some of the property values. For example, you could create a base style for all labels that will only contain the properties shared by all labels and then derive other styles from it. To derive a style from another style, we use the BasedOn property and set it to a StaticResource markup extension that references the base style. Let’s see how it works on the example of our three labels. We’ll rename labelSmallSize to labelBaseStyle, but this step isn’t necessary. The name labelSmallSize just seems too specific for me and could be used for other labels that are even smaller than the base one. The labelLargeStyle will then inherit from labelBaseStyle and only define those properties that were not defined in the base style:

<?xml version="1.0" encoding="utf-8" ?>
<...>

    <ContentPage.Resources>
        <Style TargetType="Button">
            ...            
        </Style>

        <Style x:Key="labelBaseStyle" TargetType="Label">
            <Setter Property="FontAttributes" Value="Bold" />
            <Setter Property="HorizontalOptions" Value="Center" />
            <Setter Property="FontSize" Value="40" />
        </Style>

        <Style x:Key="labelLargeStyle" 
               TargetType="Label"
               BasedOn="{StaticResource labelBaseStyle}">
            <Setter Property="FontAttributes" Value="Bold" />
            <Setter Property="HorizontalOptions" Value="Center" />
            <Setter Property="FontSize" Value="120" />
        </Style>
    </ContentPage.Resources>
    
    <Grid
        RowDefinitions="2*, 2*, 2*, *">
        <Label 
            Text="Game Over"
            Style="{StaticResource labelLargeStyle}" />
        <Label 
            Grid.Row="1"
            Text="There's only one player with any money left."
            Style="{StaticResource labelBaseStyle}" />
        <Label 
            Grid.Row="2"
            Text="The winner is Player 2, having started at $1000, winning at $396."
            Style="{StaticResource labelBaseStyle}" />
        <FlexLayout
            ...

That’s much more concise. OK, but what if we wanted to create a style that can be applied to both labels and buttons. Let’s say we want the labels and buttons to share the same value of the Rotation property.

Styles Applicable to Multiple Types

You can apply a style to objects of different types like Label and Button if they inherit from a common type. Let’s create a style that will define the Rotation property and then use it for both the labels and the buttons. To do that, we set the TargetType property to a type that both Label and Button derive from. This could be View or VisualElement. Label and Button inherit from View and View inherits from VisualElement. Have a look:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage ...>

    <ContentPage.Resources>
        <Style x:Key="baseStyle" TargetType="View">
            <Setter Property="Rotation" Value="5" />
        </Style>
        
        <Style TargetType="Button"
               BasedOn="{StaticResource baseStyle}">
            <Setter Property="FontSize" Value="40" />
            ...            
        </Style>
        
        <Style x:Key="labelBaseStyle" TargetType="Label"
               BasedOn="{StaticResource baseStyle}">
            <Setter Property="FontAttributes" Value="Bold" />
            ...
        </Style>
        
        <Style x:Key="labelLargeStyle" 
               TargetType="Label"
               BasedOn="{StaticResource labelBaseStyle}">
            <Setter Property="FontAttributes" Value="Bold" />
            <Setter Property="HorizontalOptions" Value="Center" />
            <Setter Property="FontSize" Value="120" />
        </Style>
    </ContentPage.Resources>
    
    <Grid
        ...

Now all View instances will be rotated by 5 degrees:

It would work the same if you set the TargetType property of baseStyle to VisualElement.

By the way, in the code above you can see style inheritance on multiple levels: labelLargeStyle inherits from labelBaseStyle, which, in turn, inherits from baseStyle.

Well, we know how to define styles. All the styles we’ve created so far were defined on page level, but is this the only place where you can define styles? And what does it have to do with style scopes?

Style scopes

We defined all our styles on page level. This means they are available everywhere within that page. This is where we usually define styles if we need them on that page only. But this isn’t the only place where you can define styles. You can define styles anywhere in the hierarchy. Where you define them, determines their scope. They are always available for the element they are defined on and all its children. Let’s set the ContentTemplate property in AppShell.xaml to SettingsPage so that we can see this page when we launch the app. Also, open the SettingsPage.xaml file so that we define some styles in it.

This time we’ll create a style on a layout level, not on the page level. Find the Grid layout where the header labels are defined. It looks like so:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage ...>
    <VerticalStackLayout
        ...>
        
        ...
        <!--the Players panel-->
        <Border
            ...
                <Grid
                    RowDefinitions="*"
                    ColumnDefinitions="100, 3*, 2*"
                    >
                    <Label
                        Grid.Column="1"
                        Text="Name (max 10 characters)" />
                    <Label
                        Grid.Column="2"
                        Text="Initial Money ($10 - $5000)" />                    
                </Grid>                

                <VerticalStackLayout>
                    ...
    </VerticalStackLayout>
</ContentPage>

If we define the styles inside the Grid, they will be accessible only to the Grid and its children, but not anywhere else on the page. We can define an implicit style. Have a look:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage ...>
    ...
                <Grid
                    RowDefinitions="*"
                    ColumnDefinitions="100, 3*, 2*">

                    <Grid.Resources>
                        <Style TargetType="Label">
                            <Setter Property="TextColor" Value="Green" />
                            <Setter Property="FontSize" Value="30" />
                        </Style>
                    </Grid.Resources>
                    
                    <Label
                        ...

Here the style is defined in the ResourceDictionary that is the content property of the Grid.Resources object. Run the app and you will see that only the labels inside the Grid have been styled:

Now remove the style you just created and let’s try to define a style on a view level. This definitely isn’t something you’re going to be doing frequently, but it is possible. Let’s define the style inside the first label with the Text property set to “Settings”:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage ...>
    <VerticalStackLayout
        ...
        <!--the Settings label-->
        <Label 
            Text="Settings"
            VerticalOptions="Center" 
            FontSize="18">

            <Label.Resources>
                <Style TargetType="Label">
                    <Setter Property="TextDecorations" Value="Underline" />
                    <Setter Property="TextColor" Value="Red" />
                </Style>
            </Label.Resources>
            
        </Label>
        
        <!--the Players panel-->
        ...

If you run the app, you’ll see that only this one label has been styled:

To achieve the same effect, you could just have set the properties directly on the Label object like we did before we talked about styles.

Remove the style from the Label view. We set it there just for demonstrational purposes. OK, we know how to define styles with different scopes within a page, but what if we want to create a style that can be used globally, everywhere in the app. For example, let’s say we want all the buttons across all the pages to have the same look and feel.

Global styles are defined in the app’s resource dictionary, in the App.xaml file. They can be implicit or explicit. Let’s define an implicit style for the Button. Here’s the code:

<?xml version = "1.0" encoding = "UTF-8" ?>
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:Slugrace"
             x:Class="Slugrace.App">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Resources/Styles/Colors.xaml" />
                <ResourceDictionary Source="Resources/Styles/Styles.xaml" />
            </ResourceDictionary.MergedDictionaries>

            <Style TargetType="Button">
                <Setter Property="BackgroundColor" Value="Red" />
                <Setter Property="TextColor" Value="Yellow" />
                <Setter Property="FontSize" Value="30" />
                <Setter Property="FontAttributes" Value="Bold, Italic" />
            </Style>                       
        </ResourceDictionary>
    </Application.Resources>
</Application>

Now the style should be applied to all the buttons across the app. But is it? Well, let’s check it out. We should see a red button with a yellow text. You have to manually change the page in the AppShell.xaml file to test it. Here’s the SettingsPage:

The style has been applied correctly. Let’s now run the RacePage:

Here it works too. What about the GameOverPage? Lest check it out:

Well, here it doesn’t look that good. What happened? Looks like some features were taken from the global style and some from the style defined in the GameOverPage’s ResourceDictionary. The text is yellow, because this is what we set in the global style. But the background color is black, which is what we set on page level. In order to understand why it is like that, we have to talk about precedence of styles.

Precedence of Styles

If you have ever worked with CSS, you know that styles can be overridden by styles defined lower in the hierarchy. In XAML it works exactly the same. App-level styles are overridden by page-level styles and control-level styles. Page-level styles are overridden by control-level styles, etc. All styles are overridden by properties set directly on a control. And this is why the buttons in the GameOverPage looked different than those in the other pages. But let’s have a closer look at what exactly happened.

Let’s start at the app level. Do we have any styles defined here that are applied to buttons? Turns out we do:

<?xml version = "1.0" encoding = "UTF-8" ?>
<Application ...>
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                ...
            </ResourceDictionary.MergedDictionaries>

            <Style TargetType="Button">
                <Setter Property="BackgroundColor" Value="Red" />
                <Setter Property="TextColor" Value="Yellow" />
                <Setter Property="FontSize" Value="30" />
                <Setter Property="FontAttributes" Value="Bold, Italic" />
            </Style>                       
        </ResourceDictionary>
    </Application.Resources>
</Application>

The BackgroundColor, TextColor, FontSize and FontAttributes properties are set here. At this level all the buttons across the app are red, with a yellow text with FontSize set to 30 and the FontAttributes set to Bold and Italic. Let’s move down the hierarchy. We’re interested in the GameOverPage, so let’s see if there are any styles defined on the page level:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage ...>

    <ContentPage.Resources>
        <Style x:Key="baseStyle" TargetType="View">
            <Setter Property="Rotation" Value="5" />
        </Style>
        
        <Style TargetType="Button"
               BasedOn="{StaticResource baseStyle}">
            <Setter Property="FontSize" Value="40" />
            <Setter Property="WidthRequest" Value="300" />
            <Setter Property="Margin" Value="0, 0, 50, 20" />
            <Setter Property="HeightRequest" Value="100" />
            <Setter Property="BackgroundColor" Value="Black" />
            <Setter Property="CornerRadius" Value="50" />            
        </Style>
        
        ...
    </ContentPage.Resources>    
    ...

Here we have an implicit style that is applied to all buttons on this page. The following properties are set here: FontSize, WidthRequest, Margin, HeightRequest, BackgroundColor, CornerRadius and Rotation, which is inherited from baseStyle. As you can see, two of the properties, FontSize and BackgroundColor are set again. They will take precedence and override the same properties set on app level. This is why the font size will be 40 and the background color will be black. The other properties were not defined on app level, so they are set here for the first time.

Let’s move down the hierarchy to see if any styles for the buttons are defined there. Well, there are no styles defined in the Grid that encloses the FlexLayout where the buttons are defined, nor in the FlexLayout itself. Let’s see if any properties were set on the Button objects themselves:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage ...>
    ...
        <FlexLayout
            ...>               
            <Button
                Text="Play Again" />
            <Button
                Text="Quit" />
        </FlexLayout>
    ...

Well, just the Text property. The Text property could also be set higher up in the hierarchy, even in global styles if for some reason you wanted to have the same text on all buttons. But here it’s defined directly on the buttons, so this value will be applied. No other properties are set directly on the buttons. We’re done. The properties that were set lower in the hierarchy overrode the ones set higher up.

If you want to set a different value for, let’s say, the BackgroundColor property on just one of the buttons, all you have to do is set it directly on that button:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage ...>
    ...
        <FlexLayout
            ...
            <Button
                Text="Play Again" />
            <Button
                Text="Quit" 
                BackgroundColor="Green"/>
        </FlexLayout>
    </Grid>
</ContentPage>

This value will override the one defined on page level:

Styles in the Slugrace Project

Now that we know how to use styles in .NET MAUI, let’s implement them in our Slugrace project. As we want the styles to be consistent across the entire app, we’ll define most of the styles globally, in the App.xaml file. If a different style should be applied to a particular control, we’ll define it lower in the hierarchy.

ContentPages

Let’s start by setting the background color of the ContentPages to a shade of yellow. We’ll use hex values to set the color this time, not a named color.

First, remove the style we created for the buttons in the App.xaml file and set the SettingsPage in AppShell.xaml as the page you want to see when you run the app. Then add the following code to the app’s resource dictionary:

<?xml version = "1.0" encoding = "UTF-8" ?>
<Application ...>
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                ...
            </ResourceDictionary.MergedDictionaries>

            <Style TargetType="ContentPage"
                   ApplyToDerivedTypes="True">
                <Setter Property="BackgroundColor" Value="#FFFBDB" />
            </Style>                       
        </ResourceDictionary>
    </Application.Resources>
</Application>

For this to work, we also have to set the ApplyToDerivedTypes property to True. Normally, this property is set to True to enable a style to be applied to controls that derive from a base type assigned to the TargetType property, but sometimes it must be used, like here, even if the style is to be applied to the base type.

Anyway, if you run the app now, the SettingsPage will look like this:

Let’s not only define the styles that will be used in our app, but also let’s take care of the other visual aspects.  Assuming we’re still in the SettingsPage.xaml file, you can see that the main elements on this page are placed inside a VerticalStackLayout, but I think a Grid would be a better choice. Fortunately, this is very easy to change. Make sure your code in the SettingsPage.xaml file looks like this:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage ...>
    <Grid
        Margin="10"
        RowDefinitions="50, 2.5*, 1.5*, 50">
        
        <!--the Settings label-->
        <Label 
            Text="Settings"
            ... />

        <!--the Players panel-->
        <Border
            Grid.Row="1"
            Stroke="brown"
            ...            
        </Border>
        
        <!--the Ending Conditions panel-->
        <Border
            Grid.Row="2"
            Stroke="brown"
            ...
        </Border>
        
        <!--the Ready button-->
        <Button 
            Grid.Row="3"
            Text="Ready"
            ... />
    </Grid>
</ContentPage>

Now the page should look like so:

Borders

Next, let’s define a style for the Borders. As there are Borders in the RacePage as well, let’s put the code in the App.xaml file. An implicit style will do because we want to apply the style to all the Borders:

<?xml version = "1.0" encoding = "UTF-8" ?>
<Application ...>
    <Application.Resources>
        <ResourceDictionary>            
            ...
            <Style TargetType="ContentPage"
                   ...
            <Style TargetType="Border">
                <Setter Property="Stroke" Value="#331A00" />
                <Setter Property="StrokeShape" Value="RoundRectangle 10, 10, 10, 10" />
                <Setter Property="StrokeThickness" Value="5" />
                <Setter Property="Padding" Value="5" />
                <Setter Property="Margin" Value="5" />
            </Style>            
        </ResourceDictionary>
    ...

As you can see, I used the same values as were originally set on the Border objects, except for the Stroke property, which is now set to a darker shade of brown. I also added the Margin property to add some room around the borders. Also remove all the properties except Grid.Row, Grid.Column and Grid.ColumnSpan from the Border objects in the SettingsPage and in the RacePage. Now the SettingsPage should look like this:

In the RacePage I added some padding directly on the ContentPage object:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage ...
             x:Class="Slugrace.Views.RacePage" 
             Padding="5">
    <Grid
        ...

If you run the RacePage, you should see the following:

Buttons

The next element I’d like to style is the Button. There are buttons on each of the three pages, and they all (except the Sound button) should look the same, so, again, let’s define a global style in App.xaml:

<?xml version = "1.0" encoding = "UTF-8" ?>
<Application ...>
    ...
            <Style TargetType="Border">
                ...
            </Style>

            <Style TargetType="Button">
                <Setter Property="BackgroundColor" Value="#520000" />
                <Setter Property="TextColor" Value="#FFCC1A" />
                <Setter Property="FontSize" Value="18" />
                <Setter Property="FontAttributes" Value="Bold" />
                <Setter Property="HorizontalOptions" Value="Center" />
                <Setter Property="WidthRequest" Value="250" />
                <Setter Property="HeightRequest" Value="50" />
            </Style>
            
        </ResourceDictionary>
    ...

Now remove all the Button styles from the three pages as well as the properties set directly on the buttons that were defined in the resource dictionary. An exception is the Sound button in the RacePage, which is supposed to be shorter and right-aligned. Besides, we want a note image to be displayed on this button instead of the text. Actually two note images will be needed, one to indicate the sound is on and another to indicate the sound is off. You can grab the images from the Github repository. Their names are sound_on.png and sound_off.png. We can place an image on the button using the ImageSource property. The sound button should be defined like this in the RacePage:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage ...>
    ...
        <!--the buttons-->
        <VerticalStackLayout
            ...
            <Button
                Text="Instructions" />
            <Button 
                ImageSource="sound_on.png"
                WidthRequest="125"
                HorizontalOptions="End"/>
        </VerticalStackLayout>        
        ...

Now the RacePage should look like this:

The buttons on the other pages look pretty much the same. Check it out.

The Other Controls

Next, let’s take care of the other controls. I’m not going to discuss each and every detail because this would take too long. I’ll just show you the code and will only discuss those things that need an explanation. On the way I’m also going to tweak some of the values I set before in order to adjust the general look to the new styles. I’m going to tweak some layouts and controls as well, so make sure you compare the version of code you have with the newer one.

Some controls are used on multiple pages, so I’ll define the styles for them globally. Those controls that are used only in one location, will be styled locally. I’ll try to keep the code clean and simple. So, let’s first define some global styles in the App.xaml file:

<?xml version = "1.0" encoding = "UTF-8" ?>
<Application ...>
    ...
            <Style TargetType="Button">
                ...
            </Style>

            <Style x:Key="labelBaseStyle" TargetType="Label">
                <Setter Property="FontSize" Value="18" />
                <Setter Property="VerticalOptions" Value="Center" />
            </Style>
            
            <Style x:Key="labelSectionTitleStyle" 
                   TargetType="Label"
                   BasedOn="{StaticResource labelBaseStyle}">
                <Setter Property="FontSize" Value="20" />
                <Setter Property="FontAttributes" Value="Bold" />
            </Style>

            <Style TargetType="Entry">
                <Setter Property="BackgroundColor" Value="White" />
                <Setter Property="FontSize" Value="18" />
                <Setter Property="CharacterSpacing" Value="1.5" />
                <Setter Property="HorizontalOptions" Value="Start" />
            </Style>

            <Style TargetType="RadioButton">
                <Setter Property="FontSize" Value="18" />
                <Setter Property="VerticalOptions" Value="Center" />
            </Style>

        </ResourceDictionary>
    </Application.Resources>
</Application>

Here we have a base style for the Label and another Label style that inherits from it. We don’t need any more styles on app level because these are the two styles that occur over and over again throughout the entire application. There are some labels that are styled differently, but we’ll define the styles for them locally.

Let’s have a look at the pages and content views one by one where these styles are applied. You will find the full code base in my Github repository, so if something is not clear, make sure to check it out right there. Here’s how the XAML code in the SettingsPage.xaml file ended up looking:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage ...>    

    <ContentPage.Resources>
        <Style TargetType="RadioButton">
            <Setter Property="Margin" Value="0, 0, 0, 10" />
        </Style>
    </ContentPage.Resources>
    
    <Grid
        Margin="10"
        RowDefinitions="40, 2.5*, 1.5*, 50">        

        <!--the Settings label-->
        <Label 
            Text="Settings"
            FontAttributes="Bold"
            FontSize="24" />

        <!--the Players panel-->
        <Border
            Grid.Row="1">

            <Grid>
                <Image 
                    Source="all_slugs.png"
                    Aspect="Fill"
                    Opacity=".5"/>

                <VerticalStackLayout VerticalOptions="Center">                              
                    <Label
                        Text="The Players"
                        Style="{StaticResource labelSectionTitleStyle}" />
                    <HorizontalStackLayout>
                        ...
                    </HorizontalStackLayout>

                    <Grid
                        RowDefinitions="*"
                        ColumnDefinitions="150, 3*, 2*">                    
                    
                        <Label
                            Grid.Column="1"
                            Text="Name (max 10 characters)" 
                            Style="{StaticResource labelBaseStyle}"/>
                        <Label
                            Grid.Column="2"
                            Text="Initial Money ($10 - $5000)" 
                            Style="{StaticResource labelBaseStyle}"/>                    
                    </Grid>                

                    <VerticalStackLayout>
                        ...
                    </VerticalStackLayout>
                </VerticalStackLayout>

            </Grid>
        </Border>
        
        <!--the Ending Conditions panel-->
        <Border
            Grid.Row="2">
            <VerticalStackLayout VerticalOptions="Center">
                <Label
                    Text="Ending Conditions"
                    Style="{StaticResource labelSectionTitleStyle}" 
                    Margin="0, 0, 0, 10"/>
                <Grid
                    RowDefinitions="*, *, *"
                    ColumnDefinitions="4*, 2*">
                    ...
                </Grid>
            </VerticalStackLayout>
        </Border>
        
        <!--the Ready button-->
        <Button 
            Grid.Row="3"
            Text="Ready" />
    </Grid>
</ContentPage>

There’s one thing worth mentioning here. I added a background image to the Players panel. To do that, I wrapped the VerticalStackLayout that was there in a Grid and added the image in the first cell. I set its Aspect property to Fill and Opacity to 0.5. Now the SettingsPage should look like this:

I also changed the color of the Border in the App.xaml file to match the color of the buttons. I also want to fill the circles in the radio buttons with that color, but for now we’re quite limited as far as styling radio buttons is concerned. This is possible and we’ll do it in the next part of the series where we’ll be talking about visual states and control templates. For now let’s leave the radio buttons as is.

The SettingsPage contains four instances of the PlayerSettings content view. The PlayerSettings.xaml view should now look like so:

<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Slugrace.Controls.PlayerSettings">

    <ContentView.Resources>
        <Style TargetType="Label" 
               BasedOn="{StaticResource labelBaseStyle}" />        
    </ContentView.Resources>
    
    <Grid
        RowDefinitions="*"
        ColumnDefinitions="150, 3*, 10, 2*" 
        Margin="0, 10">
        <Label
            Text="Player Name" />
        <Entry
            Grid.Column="1"
            WidthRequest="300"/>
        <Label
            Grid.Column="2"
            Text="$" />
        <Entry
            Grid.Column="3" 
            WidthRequest="250" />
    </Grid>
</ContentView>

What’s interesting here is that in the ResourceDictionary of the view we defined a new style for the Label objects that inherits from labelBaseStyle and doesn’t define any new properties. So, the new style is actually a copy of the original one. However, as this is an implicit style, we don’t have to set the style individually on each and every label. There are also some minor changes in the values of some properties. Next, let’s set the RacePage as the starting page in AppShell.xaml. This page contains several content views, so let’s have a look at the particular views one by one. The first three content views are very similar. The first one is GameInfo. Here’s the code in the GameInfo.xaml file:

<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Slugrace.Controls.GameInfo">

    <ContentView.Resources>
        <Style TargetType="Label" BasedOn="{StaticResource labelBaseStyle}" />
    </ContentView.Resources>
    
    <Grid
        RowDefinitions="*, *, *, *, *"
        ColumnDefinitions="3*, *">
        <Label
            Text="Game Info" 
            Style="{StaticResource labelSectionTitleStyle}" />
        <Label
            ...

Again, I copied the style in the ResourceDictionary and so don’t have to set the style individually on each label. The code in the SlugsStats.xaml and PlayersStats.xaml files looks very similar. In the SlugInfo content view there are white labels, but I didn’t create a style for them. The properties are still set directly on the objects. I made some minor changes in the WinnerInfo content view. I also defined a style for the labels in this view. Here’s the code in the WinnerInfo.xaml file:

<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Slugrace.Controls.WinnerInfo">

    <ContentView.Resources>
        <Style TargetType="Label">
            <Setter Property="FontAttributes" Value="Bold" />
            <Setter Property="HorizontalOptions" Value="Center" />
        </Style>
    </ContentView.Resources>
    
    <Grid
        RowDefinitions=".6*, *, 3*">
        <Label 
            Text="The winner is"
            FontSize="28" />
        <Label 
            Grid.Row="1"
            Text="Speedster"
            FontSize="36" />
        <Image
            Grid.Row="2"
            Source="speedster.png" />
    </Grid>
</ContentView>

In the Bets content view I created a style for the sliders. The style is defined for the content view, so it’s also available for its children all the way down in the hierarchy. The sliders are actually inside the PlayerBet views. The same works for the labels. I created a copy of labelBaseStyle, which will be used on the labels in the PlayerBet views. Here’s the code of the Bets.xaml file:

<?xml version="1.0" encoding="utf-8" ?>
<ContentView ...>

    <ContentView.Resources>
        <Style TargetType="Label" BasedOn="{StaticResource labelBaseStyle}" />
        <Style TargetType="Slider">
            <Setter Property="ThumbColor" Value="#520000" />
            <Setter Property="MinimumTrackColor" Value="#520000" />
        </Style>
    </ContentView.Resources>

    <Grid
        RowDefinitions="*, 4*, *">
        <Label 
            Text="Bets"
            Style="{StaticResource labelSectionTitleStyle}"/>
        <VerticalStackLayout
            Grid.Row="1">
            <controls:PlayerBet />
            <controls:PlayerBet />
            <controls:PlayerBet />
            <controls:PlayerBet />
        </VerticalStackLayout>
        
        <Button
            Grid.Row="2"
            Text="Go" 
            Margin="0, 0, 0, 5"/>
    </Grid>
</ContentView>

The Results content view is styled in a similar way. If you now run the app, the RacePage will look like so:

As far as the GameOverPage is concerned, I created an implicit style for the labels there, with the FontSize property overridden on the first label:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage ...>

    <ContentPage.Resources>
                
        <Style TargetType="Label">
            <Setter Property="FontAttributes" Value="Bold" />
            <Setter Property="HorizontalOptions" Value="Center" />
            <Setter Property="FontSize" Value="40" />
        </Style>        
        
    </ContentPage.Resources>
    
    <Grid
        RowDefinitions="2*, 2*, 2*, *">
        <Label 
            Text="Game Over"
            FontSize="120" />
        <Label 
            Grid.Row="1"
            Text="There's only one player with any money left." />
        <Label 
            Grid.Row="2" 
            Text="The winner is Player 2, having started at $1000, winning at $396." />
        <FlexLayout
            Grid.Row="3"
            JustifyContent="SpaceEvenly">           
            
            <Button
                Text="Play Again" />
            <Button
                Text="Quit" />
        </FlexLayout>
    </Grid>
</ContentPage>

As you can see, the buttons are spaced a bit differently now. Here’s the effect:

We’re almost, although not entirely, done with styles. As I mentioned before, in the next part of the series we’ll be talking about visual states and control templates.


Spread the love

Leave a Reply