Skip to content
Home » Basics of .NET MAUI – Part 4 – Views in XAML

Basics of .NET MAUI – Part 4 – Views in XAML

Spread the love

In part 3 of this series we were talking about the Label view and its property attributes. But there are lots of other views that you get out of the box. In this part of the series we’ll have a look at some of them, mainly the ones that we’ll be using in our app. As we are going to just practice new stuff here, we’ll be using the TestPage for that.

The source code for this article is available on Github.

Let’s start with the Button view.

Button

Open the TestPage.xaml file and add a button below the label:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             ...
    <VerticalStackLayout>
        <Label 
            ...
        <Button
            Text="Click Me!"
            BackgroundColor="Red"
            TextColor="Bisque"
            FontSize="40"            
            FontAttributes="Bold"
            BorderColor="Black"
            BorderWidth="10"
            HorizontalOptions="Center" />
    </VerticalStackLayout>
</ContentPage>

The attributes are self-explanatory again. You can see two new attributes here, BorderColor and BorderWidth, which are defined in the Button class. All the other properties are derived from the View class.

Let’s run the app on Android. Here’s our fancy button:

Buttons are used instead of labels if you want add some functionality to your app. There are a couple events associated with them. Let’s have a look at them now.

Button Events

The events associated with a button are Clicked, Pressed and Released. Let’s implement them in our button. As you saw in part 2 of the series, the counter button implements the Clicked event. First it binds a method to the event:

<Button
    x:Name="CounterBtn"
    Text="Click me"
    SemanticProperties.Hint="Counts the number of times you click"
    Clicked="OnCounterClicked"
    HorizontalOptions="Center" />

And then it defines the method in the code-behind:

private void OnCounterClicked(object sender, EventArgs e)
{
    count++;

    if (count == 1)
        CounterBtn.Text = $"Clicked {count} time";
    else
	CounterBtn.Text = $"Clicked {count} times";

    SemanticScreenReader.Announce(CounterBtn.Text);
}

Here it refers to the button by its name (CounterBtn), which the x:Name attribute is set to. We’ll be talking about this attribute later on, but for now we’ll be referring to the button using the sender argument. The sender argument refers to the object that fired the event, so the button in our case. As it’s an object, we must cast it to the Button type. So, let’s implement the Clicked event in our button now. Here’s the XAML code in the TestPage.xaml file:

...
        <Button            
            ...
            HorizontalOptions="Center"
            Clicked="Button_Clicked" />
    </VerticalStackLayout>
</ContentPage>

And here’s the method defined in the code-behind:

namespace Slugrace;

public partial class TestPage : ContentPage
{
	...
	private void Button_Clicked(object sender, EventArgs e)
	{
		var button = (Button)sender;
		button.Text = "Clicked";
		button.BorderWidth = 5;
		button.BackgroundColor = Color.FromRgba(.5, .5, .5, 1);
	}
}

Now run the app and click the button. You should see the text, border width and border color of the button change:

This change is only visible after you release the button. And now remove the Clicked event from XAML and add the Pressed event instead:

...
        <Button            
            ...
            HorizontalOptions="Center"            
            Pressed="Button_Pressed" />
    </VerticalStackLayout>
</ContentPage>

This time we’ll cast the sender object to Button in a different way:

namespace Slugrace;

public partial class TestPage : ContentPage
{
    ...
    private void Button_Pressed(object sender, EventArgs e)
    {
        (sender as Button).Text = "Pressed";
    }
}

This event will fire the moment you press the button, not only after you release it. This time only the text on the button is going to change:

Finally, let’s see how the Released event works. First, let’s add this event in XAML:

...
        <Button 
            ...
            Pressed="Button_Pressed"
            Released="Button_Released" />
    </VerticalStackLayout>
</ContentPage>

And here’s the Button_Released method in the code-behind:

namespace Slugrace;

public partial class TestPage : ContentPage
{
    ...

    private void Button_Pressed(object sender, EventArgs e)
    {
	  (sender as Button).Text = "Pressed";
    }

    private void Button_Released(object sender, EventArgs e)
    {
        (sender as Button).Text = "Click Me Again!";
    }
}

When you release the button, you will see this:

Looks like the button wants to play with us. So much fun, isn’t it? Now we can move on to the next view, which is the slider.

Slider

The slider can be used to set a value (of type double) between two extreme values (minimum and maximum) by dragging a thumb. You can set the minimum value, the maximum value and the current value by respectively using the Minimum, Maximum and Value properties. Let’s play with the Slider for a while. First let’s remove the XAML code that creates the button and the code related to the button in the code-behind. We don’t need it anymore. Instead, let’s create a slider:

...
        <Label 
            ...

        <Slider
            Minimum="-100"
            Maximum="200"
            Value="0" />        
    </VerticalStackLayout>
</ContentPage>

As you can see, the minimum and maximum values are set to -100 and 200 respectively and the current value is 0. So the slider thumb should sit at 0 when you run the app. Let’s try it out:

It would be great if we could see the current value of the slider. We have a label above it, which we can use for that. But the label must have a way to read the value from the slider. To reference a view, you just have to add the x:Name attribute to it. Then it will be available by name in both XAML and C# code. We’re going to cover this topic in much more detail later on in the series, but for now it’s enough to say that one way to do it is by setting the BindingContext attribute of the target view using the x:Reference markup extension (what’s a markup extension? – more on that later as well) to reference the view and then we use the Binding markup extension with the property we want to bind to. Looks complicated, so let’s have a look at the code:

...
        <Label 
            BindingContext="{x:Reference slider1}"            
            Text="{Binding Value}"
            FontSize="30"
            VerticalOptions="Center" 
            HorizontalOptions="Center" />

        <Slider
            x:Name="slider1"
            Minimum="-100"
            Maximum="200"
            Value="0" />
        ...

So, here we name the object slider1. Then, in the target view, which is the label, we set BindingContext to the slider by its name and we set the label’s Text property to the Value property of the slider. I also set the font size to make the text slightly more readable. If you run the code now and move the slider thumb, you will see the text above it change in real time.

The initial value is 0. If you drag the thumb all the way to the left, the value will be -100. If you drag the thumb all the way to the right, the value will be 200. And the values in-between are floating-point numbers (of type double). You should see something like this:

Let’s now set some other attributes:

...
        <Slider
            x:Name="slider1"
            Minimum="-100"
            Maximum="200"
            Value="0"
            HeightRequest="40"
            BackgroundColor="LightBlue"
            FlowDirection="RightToLeft"
            MaximumTrackColor="Red"
            MinimumTrackColor="Green"
            ThumbColor="Black" />
        ...

Now the slider is thicker and has a light blue background color. Its flow direction is reversed, so the maximum value is on the left-hand side. The part of the slider with values higher than the current value is red, the part with lower values is green. And the thumb is black:

Sometimes a vertical slider is the way to go. To make your slider vertical, just set its Rotation property accordingly. Let’s simplify the first horizontal slider and add a vertical one:

...
    <VerticalStackLayout>
        <Label 
            BindingContext="{x:Reference slider2}"            
            Text="{Binding Value}"
            ...

        <Slider
            x:Name="slider1"            
            Maximum="10"
            Minimum="0"
            Value="7" 
            WidthRequest="200"
            HorizontalOptions="Start"/>

        <Slider
            x:Name="slider2"            
            Maximum="15"
            Minimum="-15"
            WidthRequest="200"
            HorizontalOptions="End"
            TranslationY="100"
            Rotation="-90"
            Value="0" />

    </VerticalStackLayout>
</ContentPage>

As you can see, we specified the width of each slider to be 200. The first slider has HorizontalOptions set to Start, so it shows up on the left. The second slider has this property set to End, so it shows up on the right. The second slider is also translated vertically by 100 units and rotated so that the minimum value is now at the bottom. If you want the maximum value to be at the bottom, just rotate it 90 degrees instead of -90. Finally, the label now displays the current value of the second slider:

Slider Events

There are a couple interesting Slider events, the most obvious one being probably ValueChanged. It’s triggered when the value changes, which isn’t that hard to guess. Let’s see it in action. The second slider has a range of -15 to 15. This value should be used to rotate the first slider. We could achieve this using simple binding like before:

...
        <Slider
            x:Name="slider1"   
            BindingContext="{x:Reference slider2}"
            Rotation="{Binding Value}"
            Maximum="10"
            ...

        <Slider
            x:Name="slider2"            
            ...

Try it out. If you drag the thumb of the second slider, the first slider rotates:

But if you’re opting for a more complex scenario, the ValueChanged event is a good solution. Have a look:

<?xml version="1.0" encoding="utf-8" ?>
...
    <VerticalStackLayout>
        <Label 
            x:Name="label"            
            Text=""
            FontSize="30"
            VerticalOptions="Center" 
            HorizontalOptions="Center" />

        <Slider
            x:Name="slider1" 
            Maximum="10"
            Minimum="0"
            Value="7" 
            WidthRequest="200"
            HorizontalOptions="Start"/>

        <Slider
            x:Name="slider2"            
            Maximum="15"
            Minimum="-15"
            WidthRequest="200"
            HorizontalOptions="End"
            TranslationY="100"
            Rotation="-90"
            Value="0" 
            ValueChanged="slider2_ValueChanged"/>

    </VerticalStackLayout>
</ContentPage>

And here’s the code-behind:

namespace Slugrace;

public partial class TestPage : ContentPage
{
    public TestPage()
    {
        InitializeComponent();
    }
	
    private void slider2_ValueChanged(object sender, ValueChangedEventArgs e)
    {	
        if (e.NewValue > 0)
	  {
		label.Text = "Rotating normally";
		slider1.Rotation = e.NewValue;
	  }
	  else
	  {
             label.Text = "Rotating faster";
             slider1.Rotation = e.NewValue * 5;
          }
     }
}

I removed the binding from the label. Instead I set its x:Name attribute so that we can reference it in code. Now if you drag the thumb of the second slider, the first slider will be rotated and the label’s Text property will be set. The rotation will be different for the positive and negative range of the second slider though:

Two other events are DragStarted and DragCompleted, which are fired when you respectively start and finish dragging the slider thumb. Let’s add them to the first slider:

...
        <Slider
            x:Name="slider1" 
            Maximum="10"
            Minimum="0"
            Value="7" 
            WidthRequest="200"
            HorizontalOptions="Start"
            DragStarted="slider1_DragStarted"
            DragCompleted="slider1_DragCompleted"/>

        <Slider
            x:Name="slider2"            
            ...

And here’s how they are implemented in the code-behind:

namespace Slugrace;

public partial class TestPage : ContentPage
{
       ...
       private void slider1_DragStarted(object sender, EventArgs e)
       {
           label.Text = "Dragging started";
       }

       private void slider1_DragCompleted(object sender, EventArgs e)
       {
           label.Text = "Dragging completed";
       }
}

Now run the app and start dragging the first slider:

As soon as you stop dragging the slider thumb, the label text will change to “Dragging Completed.” Fine, let’s move on to the next view, the check box.

CheckBox

A CheckBox is a two-state button that can be either checked or unchecked. Let’s remove the slider-related code in both XAML and C# code and add two check boxes. We use the IsChecked property to set the check box’s state in code. It’s False by default. Our first check box is unchecked, but we set the IsChecked property of the second one to True.

...
    <VerticalStackLayout>
        <Label 
            x:Name="label"            
            Text=""
            FontSize="30"
            VerticalOptions="Center" 
            HorizontalOptions="Center" />

        <CheckBox />
        <CheckBox IsChecked="True" />
    </VerticalStackLayout>
</ContentPage>

This is how they look:

You can now check or uncheck them by clicking on them. Try it out.

You can change the color of your check box if you want:

...
        <CheckBox Color="Red"/>
        <CheckBox IsChecked="True" Color="Green"/>
    ...

Now they look a little bit different:

CheckBox Events

As for the CheckBox events, there’s actually only one if you exclude those that are inherited from the Element, VisualElement or BindableObject classes. It’s CheckedChanged. Here’s an example where you can see how to use it. First, the XAML file:

...
    <VerticalStackLayout>
        <Label 
            x:Name="label"            
            Text=""
            FontSize="30"
            VerticalOptions="Center" 
            HorizontalOptions="Center" />

        <CheckBox 
            Color="Red" 
            CheckedChanged="CheckBox_CheckedChanged"/>
        <CheckBox 
            IsChecked="True" 
            Color="Green" 
            CheckedChanged="CheckBox_CheckedChanged"/>

    </VerticalStackLayout>
</ContentPage>

And now the code-behind:

...
public partial class TestPage : ContentPage
{
      ...
      private void CheckBox_CheckedChanged(object sender, CheckedChangedEventArgs e)
      {
	  string labelText = $"Color: {(sender as CheckBox).Color.ToHex()} - ";

	  if (e.Value)
	  {
	      labelText += "checked";
	  }
	  else
	  {
	      labelText += "unchecked";
	  }

	  label.Text = labelText;
      }
}

As you can see, we’re using the label here to display information about which check box has been clicked and how its state has changed. This is what you should see after unchecking the green check box:

The next view we’re going to have a look at is RadioButton.

RadioButton

A RadioButton is also a type of button, just like CheckBox, but it’s used if only one option out of many should be selected. We also use the IsChecked property to set the value of a radio button’s state. There are a couple other properties that we often use with radio buttons.

One of them is Content. We use it to set the string or view that is to be displayed in the radio button.

Another important property is GroupName. All radio buttons sharing the same group name are mutually exclusive, so only one of them can be selected at any given time. If you need more sets of radio buttons, just use different group names for them.

Yet another property is Value. You can use it to store an optional unique value associated with the radio button.

Let’s have a look at all these properties in action. Remove the check boxes from XAML and the corresponding C# code from the code-behind and create the following two groups of radio buttons:

...
    <VerticalStackLayout>
        <Label 
            x:Name="label"            
            Text=""
            FontSize="20"
            VerticalOptions="Center" 
            HorizontalOptions="Center" />

        <VerticalStackLayout>
            <Label Text="Pick your prize:" />
            <RadioButton
                GroupName="prizes"
                Content="A houseplant"
                Value="45" />
            <RadioButton
                GroupName="prizes"
                Content="An antique chair"
                Value="900" />
            <RadioButton
                GroupName="prizes"
                Content="A pair of gloves"
                Value="68" />
        </VerticalStackLayout>

        <VerticalStackLayout>
            <Label Text="Where do you want it to be delivered?" />
            <RadioButton
                GroupName="delivery"
                Content="New York"
                IsChecked="True"
                Value="25" />
            <RadioButton
                GroupName="delivery"
                Content="Atlanta"
                Value="35" />
            <RadioButton
                GroupName="delivery"
                Content="Boston"
                Value="30" />
        </VerticalStackLayout>

    </VerticalStackLayout>
</ContentPage>

This should look like so on Android:

Now you can select one radio button in each group.

You can group the radio buttons also in a different way, by setting the RadioButtonGroup.GroupName attached property (we’re going to talk about attached properties soon) on the parent view, so in our case on the VerticalStackLayout. So, we could rewrite the code above like so:

...
        <VerticalStackLayout RadioButtonGroup.GroupName="prizes">
            <Label Text="Pick your prize:" />
            <RadioButton
                Content="A houseplant"
                Value="45" />
            <RadioButton
                Content="An antique chair"
                Value="900" />
            <RadioButton
                Content="A pair of gloves"
                Value="68" />
        </VerticalStackLayout>

        <VerticalStackLayout RadioButtonGroup.GroupName="delivery">
            <Label Text="Where do you want it to be delivered?" />
            <RadioButton
                Content="New York"
                IsChecked="True"
                Value="25" />
            <RadioButton
                Content="Atlanta"
                Value="35" />
            <RadioButton
                Content="Boston"
                Value="30" />
        </VerticalStackLayout>
    ...

It still works like before.

RadioButton Events

If you want to react to a state change of a radio button, you have to implement the CheckedChanged event. Here’s how to do it. First, the XAML code:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             ...
    <VerticalStackLayout>
        <Label 
            x:Name="label1"
            FontSize="20"
            VerticalOptions="Center" 
            HorizontalOptions="Center" />

        <Label 
            x:Name="label2"
            FontSize="15"
            VerticalOptions="Center" 
            HorizontalOptions="Center" />

        <VerticalStackLayout RadioButtonGroup.GroupName="prizes">
            <Label Text="Pick your prize:" />
            <RadioButton
                Content="A houseplant"
                Value="45" 
                CheckedChanged="RadioButton_CheckedChanged" />
            <RadioButton
                Content="An antique chair"
                Value="900" 
                CheckedChanged="RadioButton_CheckedChanged" />
            <RadioButton
                Content="A pair of gloves"
                Value="68" 
                CheckedChanged="RadioButton_CheckedChanged" />
        </VerticalStackLayout>

        <VerticalStackLayout RadioButtonGroup.GroupName="delivery">
            <Label Text="Where do you want it to be delivered?" />
            <RadioButton
                Content="New York"
                IsChecked="True"
                Value="25" 
                CheckedChanged="RadioButton_CheckedChanged_1" />
            <RadioButton
                Content="Atlanta"
                Value="35" 
                CheckedChanged="RadioButton_CheckedChanged_1" />
            <RadioButton
                Content="Boston"
                Value="30" 
                CheckedChanged="RadioButton_CheckedChanged_1"/>
        </VerticalStackLayout>

    </VerticalStackLayout>
</ContentPage>

As you can see, I added another label here. And now the code-behind:

...
public partial class TestPage : ContentPage
{
      public TestPage()
      {
	  InitializeComponent();
      }

      private void RadioButton_CheckedChanged(object sender, CheckedChangedEventArgs e)
      {
          var rb = sender as RadioButton;
          string labelText = $"Your award: {rb.Content}\nValue: ${rb.Value}\n";
          label1.Text = labelText;
      }

      private void RadioButton_CheckedChanged_1(object sender, CheckedChangedEventArgs e)
      {
          var rb = sender as RadioButton;
          string labelText = $"Delivery: {rb.Content}, Delivery Cost: ${rb.Value}";
          label2.Text = labelText;
      }
}

Here’s what you can expect after making some selections:

As you can see, we leveraged the Value property here.

Naturally, there’s more to radio buttons, but here we’re covering just the basics, so let’s now move on to the next view, which is the Entry.

Entry

We’ll need some controls in our app to enter text. If this is to be a single line of text, the Entry view is best suited for that. For multiple lines of text we need an Editor, which we discuss next. This view has lots of useful properties. Let’s create an entry then and see some of them in action. As usual, remove all the RadioButton – related code from the XAML and C# files first. In the XAML file leave the first label only and set its x:Name attribute back to “label”. Then add an entry like so:

...
    <VerticalStackLayout>
        <Label 
            x:Name="label"
            FontSize="20"
            VerticalOptions="Center" 
            HorizontalOptions="Center" />

        <Entry 
            x:Name="entry"
            Text="hey"
            Placeholder="So empty here..." />

    </VerticalStackLayout>
</ContentPage>

Here’s what it looks like:

Here we set the initial text value to “hey”. If you delete this text, you will see the placeholder text:

Boring. Let’s make the entry just a tad fancier:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Slugrace.TestPage"
             Title="TestPage">
    <VerticalStackLayout>
        <Label 
            x:Name="label"
            FontSize="20"
            VerticalOptions="Center" 
            HorizontalOptions="Center" />

        <Entry 
            x:Name="entry"
            Text="hey"
            Placeholder="So empty here..." 
            CharacterSpacing="10"
            ClearButtonVisibility="WhileEditing"
            FontAttributes="Bold"
            FontSize="30"
            HorizontalTextAlignment="Center"
            TextColor="Coral"
            PlaceholderColor="BlanchedAlmond" />

    </VerticalStackLayout>
</ContentPage>

So, the fancier version looks like this:

Thanks to the ClearButtonVisibility property we can now clear the entry while editing by clicking on the clear button.

You can also use an entry for entering a password. All you need to do is set the IsPassword property to True:

...
        <Entry 
            ...
            PlaceholderColor="BlanchedAlmond" 
            IsPassword="True" />
    ...

Now the text is obscured:

Let’s remove this attribute now.

Entry Events

As with the other views before, there are quite a few events that you can use with an entry. Let’s focus on two, TextChanged and Completed. The former is fired every time you type or delete a character, the latter only after you hit Enter to finalize the text. Let’s implement them for our entry. Here’s the XAML code (with the Entry view considerably simplified):

...
        <Entry 
            TextChanged="Entry_TextChanged"
            Completed="Entry_Completed"/>
    ...

And here’s the code-behind:

...
public partial class TestPage : ContentPage
{
      ...
      private void Entry_TextChanged(object sender, TextChangedEventArgs e)
      {
          var entry = (sender as Entry);
          label.Text = $"{entry.Text.Length} characters";
      }

      private void Entry_Completed(object sender, EventArgs e)
      {
          label.Text = $"You wrote: \"{(sender as Entry).Text}\"";
      }
}

Now run the app and start typing something in the entry. The label text above will display the length of your text:

Now hit the Enter key:

And the label text will change:

There are two more events you might want to use with an Entry from time to time, Focused and Unfocused. They derive from the VisualElement class. Let’s add them to our entry:

...
        <Entry 
            TextChanged="Entry_TextChanged"
            Completed="Entry_Completed"
            Focused="Entry_Focused"
            Unfocused="Entry_Unfocused"/>
    ...

And here’s the code-behind:

...
public partial class TestPage : ContentPage
{
    ...
    private void Entry_Completed(object sender, EventArgs e)
    ...

    private void Entry_Focused(object sender, FocusEventArgs e)
    {
        (sender as Entry).FontSize = 20;
    }

    private void Entry_Unfocused(object sender, FocusEventArgs e)
    {
        (sender as Entry).FontSize = 10;
    }
}

Now run the app and click on the entry so that it gets focus. Type something. The font size is 20 now:

Hit the Tab key on your keyboard so that the entry loses focus. Now the text is much smaller:

Fine. But what if we need multiline text? Well, here comes the Editor view to the rescue.

Editor

The Editor is a view that allows you to enter multiple lines of text. It shares quite a lot of properties and events with the Entry class. Let’s remove the Entry-related code from XAML and C# and let’s create an editor:

...
    <VerticalStackLayout>
        <Label 
            ...

        <Editor 
            Placeholder="Type something..."
            HeightRequest="100" />        

    </VerticalStackLayout>
</ContentPage>

Run the app and type something in the editor. If the text you enter doesn’t fit in the editor, you can scroll it, just like here:

You can also set the AutoSize property to accommodate your input. Let’s remove the HeightRequest property and set the AutoSize property:

...
        <Editor 
            Placeholder="Type something..."
            AutoSize="TextChanges" />      
    ...

Now the editor grows as you type:

We can use the same events as with the Entry, so I’m not going to demonstrate it here again.

There are lots of other views that we could talk about, but you can easily check them out in the documentation if you need them. All the views we’ve covered so far are used to display a single item, like a label, a button or a slider, to mention just a few. But there are also views that allow you to display multiple items in a list or in another form. The two most important ones are ListView and CollectionView. They share a lot of similarities, but the CollectionView is preferred over the ListView in modern apps. Unlike ListView, it allows for laying out the elements both vertically and horizontally, and also it supports single or multiple selection. We’ll have a look at the CollectionView later in the series and in the next part we’ll be talking about layouts.


Spread the love

Leave a Reply