left-icon

Xamarin.Forms for macOS Succinctly®
by Alessandro Del Sole

Previous
Chapter

of
A
A
A

CHAPTER 7

Resources and Data Binding

Resources and Data Binding


XAML is a very powerful declarative language, and it shows all of its power with two particular scenarios: working with resources and data binding. If you have an existing background in Windows development with platforms like WPF, Silverlight, and Universal Windows Platform, you will be familiar with the concepts described in this chapter. If this is your first time, you will immediately appreciate how XAML simplifies difficult things in both scenarios.

Working with resources

Generally speaking, in XAML-based platforms resources are reusable pieces of information that you can apply to visual elements in the user interface. Typical XAML resources are styles, control templates, object references, and data templates. Xamarin.Forms supports styles and data templates, so these will be discussed in this chapter.

Declaring resources

Every Page object and layout exposes a property called Resources, a collection of XAML resources that you can populate with one or more objects of type ResourceDictionary. A ResourceDictionary is a container of XAML resources such as styles, data templates, and object references. For example, you can add a ResourceDictionary to a page as follows:

<ContentPage.Resources>

   <ResourceDictionary>

       <!-- Add resources here -->

   </ResourceDictionary>

</ContentPage.Resources>

Resources have scope. This implies that resources you add to the page level are available to the whole page, whereas resources you add to the layout level are only available to the current layout, like in the following snippet:

<StackLayout.Resources>

   <ResourceDictionary>

       <!-- Resources are available only to this layout, not outside -->

   </ResourceDictionary>

</StackLayout.Resources>

Sometimes you might want to make resources available to the entire application. In this case, you can take advantage of the App.xaml file. The default code for this file is shown in Code Listing 14.

Code Listing 14

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

<Application xmlns="http://xamarin.com/schemas/2014/forms"

             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"

             x:Class="App1.App">

     <Application.Resources>

 

         <!-- Application resource dictionary -->

            <ResourceDictionary>

            

            </ResourceDictionary>

     </Application.Resources>

</Application>

As you can see, the auto-generated code of this file already contains an Application.Resources node with a nested ResourceDictionary. Resources you put inside this resource dictionary will be visible to any page, layout, and view in the application. Now that you have knowledge of where resources are declared and their scope, it is time to see how resources work, starting with styles. Other resources, such as data templates, will be discussed later in this chapter.

Introducing styles

When designing your user interface, in some situations, you might have multiple views of the same type and, for each of them, you might need to assign the same properties with the same values. For example, you might have two buttons with the same width and height, or two or more labels with the same width, height, and font settings. In such situations, instead of assigning the same properties many times, you can take advantage of styles. A style allows you to assign a set of properties to views of the same type. Styles must be defined inside a ResourceDictionary, and they must specify the type they are intended for and an identifier. The following code demonstrates how to define a style for Label views:

<ResourceDictionary>

   <Style x:Key="labelStyle" TargetType="Label">

      <Setter Property="TextColor" Value="Green" />

      <Setter Property="FontSize" Value="Large" />

   </Style>

</ResourceDictionary>

You assign an identifier with the x:Key expression (as opposed to the x:Name attribute) and the target type with TargetType, passing the type name for the target view. Property values are assigned with Setter elements, whose Property property represents the target property name and Value represents the property value. You then assign the style to Label views as follows:

<Label Text="Enter some text:" Style="{StaticResource labelStyle}"/>

A style is therefore applied by assigning the Style property on a view with an expression that encloses the StaticResource markup extension and the style identifier within curly braces. You can then assign the Style property on each view of that type instead of manually assigning the same properties every time. With styles, XAML supports both StaticResource and DynamicResource markup extensions. In the first case, if a style changes, the target view will not be updated with the refreshed style. In the second case, the view will be updated to reflect the changes in the style.

Style inheritance

Styles support inheritance; therefore, you can create a style that derives from another style. For example, you can define a style that targets the abstract View type as follows:

<Style x:Key="viewStyle" TargetType="View">

    <Setter Property="HorizontalOptions" Value="Center" />

    <Setter Property="VerticalOptions" Value="Center" />

</Style>

This style can be applied to any view, regardless of the concrete type. Then you can create a more specialized style using the BasedOn property as follows:

<Style x:Key="labelStyle" TargetType="Label"

       BasedOn="{StaticResource viewStyle}">

    <Setter Property="TextColor" Value="Green" />

</Style>

The second style targets Label views, but also inherits property settings from the parent style. Put succinctly, the labelStyle will assign the HorizontalOptions, VerticalOptions, and TextColor properties on the targeted Label views.

Implicit styling

The views’ Style property allows the assigning of a style defined inside resources. This allows you to selectively assign the style only to certain views of a given type. However, if you want the same style to be applied to all of the views of the same type in the user interface, assigning the Style property to each view manually might be annoying. In this case, you can take advantage of the so-called implicit styling. This feature allows you to automatically assign a style to all the views of the type specified with the TargetType property without the need to set the Style property. To accomplish this, you simply avoid assigning an identifier with x:Key, like in the following example:

<Style TargetType="Label">

   <Setter Property="HorizontalOptions" Value="Center" />

   <Setter Property="VerticalOptions" Value="Center" />

   <Setter Property="TextColor" Value="Green" />

</Style>

Styles with no identifier will automatically be applied to all the Label views in the user interface (according to the scope of the containing resource dictionary), and you will not need to assign the Style property on the Label definitions.

Working with data binding

Data binding is a built-in mechanism that allows visual elements to communicate with data so that the user interface is automatically updated when data changes, and vice versa. Data binding is available in all the most important development platforms, and Xamarin.Forms is no exception. In fact, its data binding engine relies on the power of XAML, and the way it works is similar in all the XAML-based platforms. Xamarin.Forms supports binding an object to visual elements, a collection to visual elements, and visual elements to other visual elements. This chapter describes the first two scenarios. Because data binding is a very complex topic, the best way to start is with an example. Suppose you want to bind an instance of the following Person class to the user interface, so that a communication flow is established between the object and views:

public class Person

{

    public string FullName { get; set; }

    public DateTime DateOfBirth { get; set; }

    public string Address { get; set; }

}

In the user interface, you will want to allow the user to enter their full name, date of birth, and address via an Entry, a DatePicker, and another Entry, respectively. In XAML, this can be accomplished with the code shown in Code Listing 15.

Code Listing 15

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

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"

             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"

             xmlns:local="clr-namespace:App1"

             Title="Main page"

             x:Class="App1.MainPage">

 

    <StackLayout Orientation="Vertical" Padding="20">

        <Label Text="Name:" />

        <Entry Text="{Binding FullName}"/>

 

        <Label Text="Date of birth:"/>

        <DatePicker Date="{Binding DateOfBirth, Mode=TwoWay}"/>

 

        <Label Text="Address:"/>

        <Entry Text="{Binding Address}"/>

    </StackLayout>

</ContentPage>

As you can see, the Text property for Entry views and the Date property of the DatePicker have a markup expression as their value. Such an expression is made up of the Binding literal followed by the property you want to bind from the data object. Actually, the expanded form of this syntax could be {Binding Path=PropertyName}, but Path can be omitted. Data binding can be of four types:

  • TwoWay: Views can read and write data
  • OneWay: Views can only read data
  • OneWayToSource: Views can only write data
  • Default: Xamarin.Forms resolves the appropriate mode automatically, based on the view (see the explanation that follows)

TwoWay and OneWay are the most-used modes, and in most cases you do not need to specify the mode explicitly because Xamarin.Forms automatically resolves the appropriate mode based on the view. For example, binding in the Entry control is TwoWay because this kind of view can be used to read and write data, whereas binding in the Label control is OneWay because this view can only read data. However, with the DatePicker, you need to explicitly set the binding mode, so you use the following syntax:

<DatePicker Date="{Binding DateOfBirth, Mode=TwoWay}"/>

Views’ properties that are bound to an object’s properties are known as bindable properties (or dependency properties if you come from the WPF or UWP world).

Tip: Bindable properties are very powerful, but a bit more complex in architecture. In this chapter, I’m going to explain how to use them, but for further details about their implementation and how you can use them in your custom objects, you can refer to the official documentation.

Bindable properties will automatically update the value of the bound object’s property, and will automatically refresh their value in the user interface if the object is updated. However, this automatic refresh is possible only if the data-bound object implements the INotifyPropertyChanged interface, which allows an object to send change notifications. As a consequence, you must extend the Person class definition, as shown in Code Listing 16.

Code Listing 16

using System;

using System.ComponentModel;

using System.Runtime.CompilerServices;

 

namespace App1

{

    public class Person : INotifyPropertyChanged

    {

        private string fullName;

        public string FullName

        {

            get

            {

                return fullName;

            }

            set

            {

                fullName = value;

                OnPropertyChanged();

            }

        }

 

        private DateTime dateOfBirth;

 

        public DateTime DateOfBirth

        {

            get

            {

                return dateOfBirth;

            }

            set

            {

                dateOfBirth = value;

                OnPropertyChanged();

            }

        }

        private string address;

        public string Address

        {

            get

            {

                return address;

            }

            set

            {

                address = value;

                OnPropertyChanged();

            }

        }

 

        public event PropertyChangedEventHandler PropertyChanged;

 

        private void OnPropertyChanged([CallerMemberNamestring propertyName 

                                        = null)

        {

            PropertyChanged?.Invoke(this

                new PropertyChangedEventArgs(propertyName));

        }

    }

}

By implementing INotifyPropertyChanged, property setters can raise a change notification via the PropertyChanged event. Bound views will be notified of any changes and will refresh their contents.

Tip: With the CallerMemberName attribute, the compiler automatically resolves the name of the caller member. This avoids the need to pass the property name in each setter and helps keep code much cleaner.

The next step is binding an instance of the Person class to the user interface. This can be accomplished with the following lines of code, normally placed inside the page’s constructor or in its OnAppearing event handler:

Person person = new Person();

this.BindingContext = person;

Pages and layouts expose the BindingContext property, of type object, that represents the data source for the page or layout, and is the same as DataContext in WPF or UWP. Child views that are data-bound to an object’s properties will search for an instance of the object in the BindingContext property value and bind to properties from this instance. In this case, the Entry and the DatePicker will search for an object instance inside BindingContext, and they will bind to properties from that instance. Remember that XAML is case-sensitive, so binding to FullName is different from binding to Fullname. The runtime will throw an exception if you try to bind to a property that does not exist or has a different name. If you now try to run the application, not only will data binding work, but the user interface will also be automatically updated if the data source changes. You may think of binding views to a single object instance, like in the previous example, as binding to a row in a database table.

Working with collections and with the ListView

Though working with a single object instance is a common scenario, another very common situation is working with collections that you display as lists in the user interface. Xamarin.Forms supports data binding over collections via the ObservableCollection<T> class. This collection works exactly like the List<T>, but it also raises a change notification when items are added to or removed from the collection. Collections are very useful, for example, when you want to represent rows in a database table.

Suppose you have the following collection of Person objects:

Person person1 = new Person { FullName = "Alessandro" };

Person person2 = new Person { FullName = "James" };

Person person3 = new Person { FullName = "Jacqueline" };

var people = new ObservableCollection<Person>() { person1, person2, 

             person3 };

 

this.BindingContext = people;

The code assigns the collection to the BindingContext property of the root container, but at this point, you need a visual element that is capable of displaying the content of this collection. This is where the ListView control comes in. The ListView can receive the data source from either the BindingContext of its container or by assigning its ItemsSource property, and any object that implements the IEnumerable interface can be used with the ListView. You will typically assign ItemsSource directly if the data source for the ListView is not the same data source as for the other views in the page.

The problem to solve with the ListView is that it does not know how to present objects in a list. For example, think of the people collection that contains instances of the Person class. Each instance exposes the FullName, DateOfBirth, and Address properties, but the ListView does not know how to present these properties, so it is your job to explain to it how. This is accomplished with the so-called data templates. A data template is a set of views that are bound to properties in the object. It instructs the ListView on how to present items. Data templates in Xamarin.Forms rely on the concept of cells. Cells can display information in a specific way, and are summarized in Table 8.

Table 8: Cells in Xamarin.Forms

Cell Type

Description

TextCell

Displays two labels, one with a description and one with a data-bound text value.

EntryCell

Displays a label with a description and an Entry with a data-bound text value. It also allows a placeholder to be displayed.

ImageCell

Displays a label with a description and an Image control with a data-bound image.

SwitchCell

Displays a label with a description and a Switch control bound to a bool value.

ViewCell

Allows for creating custom data templates.

Tip: Labels within cells are also bindable properties.

For example, if you only had to display and edit the FullName property, you could write the following data template:

<Grid>

    <ListView x:Name="PeopleList" ItemsSource="{Binding}">

        <ListView.ItemTemplate>

            <DataTemplate>

                <EntryCell Label="Full name:" Text="{Binding FullName}"/>

            </DataTemplate>

        </ListView.ItemTemplate>

    </ListView>

</Grid>

Tip: The DataTemplate definition is always defined inside the ListView.ItemTemplate element.

As a general rule, if the data source is assigned to the BindingContext property, the ItemsSource must be set with the {Binding} value, which means your data source is the same as that of your parent. With this code, the ListView will display all the items in the bound collection, showing two cells for each item. However, each Person also exposes a property of type DateTime, and no cell is suitable for that. In such situations, you can create a custom cell using the ViewCell, as shown in Code Listing 17.

Code Listing 17

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

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"

             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"

             xmlns:local="clr-namespace:App1"

             Title="Main page" Padding="20"

             x:Class="App1.MainPage">

 

    <StackLayout>

        <ListView x:Name="PeopleList" ItemsSource="{Binding}"

                  HasUnevenRows="True">

            <ListView.ItemTemplate>

                <DataTemplate>

                    <ViewCell>

                        <ViewCell.View>

                            <StackLayout Margin="10">

                                <Label Text="Full name:"/>

                                <Entry Text="{Binding FullName}"/>

                                <Label Text="Date of birth:"/>

                                <DatePicker Date="{Binding DateOfBirth, 

                                            Mode=TwoWay}"/>

                                <Label Text="Address:"/>

                                <Entry Text="{Binding Address}"/>

                            </StackLayout>

                        </ViewCell.View>

                    </ViewCell>

                </DataTemplate>

            </ListView.ItemTemplate>

        </ListView>

    </StackLayout>

</ContentPage>

As you can see, the ViewCell allows you to create custom and complex data templates, contained in the ViewCell.View property, so that you can display whatever kind of information you need. Notice the HasUnevenRows property: if True on Android, this dynamically resizes a cell’s height based on its content. On iOS, this property must be set to False, and you must provide a fixed row height by setting the RowHeight property. In Chapter 8 you will learn how to take advantage of the OnPlatform object to make UI decisions based on the platform.

Tip: The ListView is a very powerful and versatile view, and there is much more to it, such as interactivity, grouping and sorting, and customizations. I strongly recommend you read the official documentation on the ListView, and this article that describes how to improve performance, which is extremely useful with Android.

Figure 38 shows the result for the code described in this section. Notice that the ListView includes built-in scrolling capability, and must never be enclosed within a ScrollView.

A data-bound ListView

Figure 38: A data-bound ListView

A data template can be placed inside the page or app resources so that it becomes reusable. Then you assign the ItemTemplate property in the ListView definition with the StaticResource expression, as shown in Code Listing 18.

Code Listing 18

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

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"

             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"

             xmlns:local="clr-namespace:App1"

             Title="Main page"

             x:Class="App1.MainPage">

 

    <ContentPage.Resources>

        <ResourceDictionary>

            <DataTemplate x:Key="MyTemplate">

                <ViewCell>

                    <ViewCell.View>

                        <StackLayout Margin="10" Orientation="Vertical"

                                         Padding="10">

                            <Label Text="Full name:"/>

                            <Entry Text="{Binding FullName}"/>

                            <Label Text="Date of birth:"/>

                            <DatePicker Date="{Binding DateOfBirth,Mode=TwoWay}"/>

                            <Label Text="Address:"/>

                            <Entry Text="{Binding Address}"/>

                        </StackLayout>

                    </ViewCell.View>

                </ViewCell>

            </DataTemplate>

        </ResourceDictionary>

    </ContentPage.Resources>

 

    <ListView x:Name="PeopleList" VerticalOptions="FillAndExpand"

              HasUnevenRows="True" ItemTemplate="{StaticResource MyTemplate}"/>

</ContentPage>

Working with the TableView

When you need to present a list of settings, data in a form, or data that is different from row to row, you can consider the TableView control. The TableView is based on sections and can display content through the same cells described previously. With this view, you need to specify a value for its Intent property, which basically represents the type of information you need to display. Possible values are Settings (list of settings), Data (to display data entries), Form (when the table view acts like a form), and Menu (to present a menu of selections). Code Listing 19 provides an example.

Code Listing 19

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

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"

             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"

             xmlns:local="clr-namespace:App1"

             Title="Main page"

             x:Class="App1.MainPage">

 

    <ContentPage.Content>

        <TableView Intent="Settings">

            <TableRoot>

                <TableSection Title="Network section">

                    <SwitchCell Text="Allowed" On="True"/>

                </TableSection>

                <TableSection Title="Push notifications">

                    <SwitchCell Text="Allowed" On="True"/>

                </TableSection>

            </TableRoot>

        </TableView>

    </ContentPage.Content>

</ContentPage>

You can divide the TableView into multiple TableSections. Each contains a cell to display the required type of information, and, of course, you can use a ViewCell for a custom, more complex template. Figure 39 shows an example of TableView based on the previous listing.

A TableView in action

Figure 39: A TableView in action

Obviously, you can bind cell properties to objects rather than setting their value explicitly like in the previous example.

Showing and selecting values with the Picker view

With mobile apps, it is common to provide the user an option to select an item from a list of values, which can be accomplished with the Picker view. Xamarin.Forms 2.3.4 has introduced data binding support in the Picker. You can now easily bind a List<T> or ObservableCollection<T> to its ItemsSource property and retrieve the selected item via its SelectedItem property. For example, suppose you have the following Fruit class:

public class Fruit

{

    public string Name { get; set; }

    public string Color { get; set; }

}

Now, in the user interface, suppose you want to ask the user to select a fruit from a list with the XAML shown in Code Listing 20.

Code Listing 20

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

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"

             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"

             xmlns:local="clr-namespace:App2"

             x:Class="App2.MainPage">

 

    <ContentPage.Content>

 

        <StackLayout VerticalOptions="FillAndExpand">

            <Label Text="Select your favorite fruit:"/>

            <Picker x:Name="FruitPicker" ItemDisplayBinding="{Binding Name}"

                    SelectedIndexChanged="FruitPicker_SelectedIndexChanged"/>

        </StackLayout>

    </ContentPage.Content>

</ContentPage>

As you can see, the Picker exposes the SelectedIndexChanged event, which is raised when the user selects an item. With the ItemDisplayBinding, you specify which property from the bound object it needs to display: in this case, the fruit name. The ItemsSource property can, instead, be assigned either in XAML or in the code-behind. In this case, a collection can be assigned in C#, as demonstrated in Code Listing 21.

Code Listing 21

public partial class MainPage : ContentPage

{

    public MainPage()

    {

        InitializeComponent();

 

        var apple = new Fruit { Name = "Apple", Color = "Green" };

        var strawberry = new Fruit { Name = "Strawberry", Color = "Red" };

        var orange = new Fruit { Name = "Orange", Color = "Orange" };

 

        var fruitList = new ObservableCollection<Fruit>() 

            { apple, strawberry, orange };

        this.FruitPicker.ItemsSource = fruitList;

    }

 

    private async void FruitPicker_SelectedIndexChanged(object sender, 

                                                        EventArgs e)

    {

        var currentFruit = this.FruitPicker.SelectedItem as Fruit;

        if (currentFruit != null)

            await DisplayAlert("Selection"

            $"You selected {currentFruit.Name}""OK");

    }

}

Like the same-named property in the ListView, ItemsSource is of type object and can bind to any object that implements the IEnumerable interface. Notice how you can retrieve the selected item handling the SelectedIndexChanged event and casting the Picker.SelectedItem property to the type you expect. In such situations, it is convenient to use the as operator, which returns null if the conversion fails, instead of an exception. Figure 40 shows how the user can select an item from the picker.

Selecting items with a Picker

Figure 40: Selecting items with a Picker

Actually, data binding support was added to the Picker only with Xamarin.Forms 2.3.4. In previous versions, you could only manually populate its Items property via the Add method, and then handle indices. This is the real reason why the SelectedIndexChanged event exists, but it is still useful with the new approach. Data binding a list to the Picker is very common, but you can certainly still populate the list manually and handle the index.

Binding images

Displaying images in data-binding scenarios is very common, and Xamarin.Forms makes it easy. You simply need to bind the Image.Source property to an object of type ImageSource or to a URL that can be represented by both a string and a Uri. For example, suppose you have a class with a property that stores the URL of an image as follows:

public class Picture

{

     public Uri PictureUrl { get; set; }

}

When you have an instance of this class, you can assign the PictureUrl property:

var picture1 = new Picture();

picture1.PictureUrl = new Uri("http://mystorage.com/myimage.jpg");

Supposing you have an Image view in your XAML code and a BindingContext assigned with an instance of the class, data binding would work as follows:

<Image Source="{Binding PictureUrl}"/>

XAML has a type converter for the Image.Source property, so it automatically resolves strings and Uri instances into the appropriate type.

Hints for value converters

The last sentence of the previous section about image binding highlights the existence of type converters that resolve specific types into the appropriate type for the Image.Source property. This actually happens with many other views and types. For example, if you bind an integer value to the Text property of an Entry view, such an integer is converted into a string by a XAML type converter. However, there are situations in which you might want to bind objects that XAML type converters cannot automatically convert into the type a view expects. For example, you might want to bind a Color value to a Label’s Text property, which is not possible out of the box. In these cases, you can create value converters. A value converter is a class that implements the IValueConverter interface and that must implement and expose the Convert and ConvertBack methods. Convert translates the original type into a type that the view can receive, while ConvertBack does the opposite.

Code Listing 22 shows an example of a value converter that converts a string containing HTML markup into an object that can be bound to the WebView control. ConvertBack is not implemented because this value converter is supposed to be used in a read-only scenario, so a round-trip conversion is not required.

Code Listing 22

using System;

using System.Collections.Generic;

using System.Globalization;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

using Xamarin.Forms;

namespace App1

{

    public class HtmlConverter : IValueConverter

    {

        public object Convert(object value, Type targetType,

               object parameter, CultureInfo culture)

        {

            try

            {

                var source = new HtmlWebViewSource();

                string originalValue = (string)value;

                source.Html = originalValue;

                return source;

            }

            catch (Exception)

            {

                return value;

            }

        }

        public object ConvertBack(object value, Type targetType,

                      object parameter, CultureInfo culture)

        {

            throw new NotImplementedException();

        }

    }

}

Both methods always receive the data to convert as object instances, and then you need to cast the object into a specialized type for manipulation. In this case, Convert creates an HtmlWebViewSource object, converts the received object into a string, and populates the Html property with the string that contains the HTML markup. The value converter must then be declared in the resources of the XAML file where you wish to use it (or in App.xaml). Code Listing 23 provides an example that also shows how to use the value converter.

Code Listing 23

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

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"

             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"

             xmlns:local="clr-namespace:App1"

             Title="Main page"

             x:Class="App1.MainPage">

 

    <ContentPage.Resources>

        <local:HtmlConverter x:Key="HtmlConverter"/>

    </ContentPage.Resources>

 

    <!-- Assumes you have a data-bound .NET object that exposes 

    a property called HtmlContent -->

    <WebView Source="{Binding HtmlContent, 

     Converter={StaticResource HtmlConverter}}"/>

</ContentPage>

You declare the converter as you would do with any other resource. Then your binding will also contain the Converter expression, which points to the value converter with the typical syntax you used with other resources.

Introducing Model-View-ViewModel

Model-View-ViewModel (MVVM) is an architectural pattern used in XAML-based platforms that allows for clean separation between the data (model), the logic (view model), and the user interface (view). With MVVM, pages only contain code related to the user interface and strongly rely on data binding, and most of the work is done in the view model. MVVM can be quite complex if you have never seen it before, so I will try to simplify the explanations as much as possible, but you should use Xamarin’s MVVM documentation as a reference.

Let’s start with a simple example and a fresh Xamarin.Forms solution based on the PCL code-sharing strategy. Imagine you want to work with a list of Person objects. This is your model, and you can reuse the Person class from before. Add a new folder called Model to your project, and add a new Person.cs class file to this folder, pasting in the code of the Person class. Next, add a new folder called ViewModel to the project, and add a new class file called PersonViewModel.cs. Before writing the code for it, let’s summarize some important considerations:

  • The view model contains the business logic, acts like a bridge between the model and the view, and exposes properties to which the view can bind.
  • Among such properties, one will certainly be a collection of Person objects.
  • In the view model, you can load data, filter data, execute save operations, and query data.

Loading, filtering, saving, and querying data are examples of actions a view model can execute against data. In a classic development approach, you would handle Clicked events on Button views and write the code that executes an action. However, in MVVM, views should only contain code related to the user interface, not code that executes actions against data. So, in MVVM, view models expose the so-called commands. A command is a property of type ICommand that can be data bound to views such as Button, SearchBar, ListView, and TapGestureRecognizer objects. In the UI, you bind a view to a command in the view model. In this way, the action is executed in the view model instead of in the view’s code behind.

Code Listing 24 shows the PersonViewModel class definition.

Code Listing 24

using MvvmSample.Model;

using System;

using System.Collections.ObjectModel;

using System.Windows.Input;

using Xamarin.Forms;

 

namespace MvvmSample.ViewModel

{

    public class PersonViewModel

    {

        public ObservableCollection<Person> People { getset; }

        public Person SelectedPerson { getset; }

 

        public ICommand AddPerson { getset; }

        public ICommand DeletePerson { getset; }

 

        public PersonViewModel()

        {

            this.People = new ObservableCollection<Person>();

 

            // sample data

            Person person1 = 

                new Person { FullName = "Alessandro",

                    Address ="Italy",

                    DateOfBirth =new DateTime(1977,5,10) };

            Person person2 = 

                new Person { FullName = "James",

                    Address ="United States",

                    DateOfBirth =new DateTime(1960,2,1) };

            Person person3 = 

                new Person { FullName = "Jacqueline",

                    Address ="France",

                    DateOfBirth =new DateTime(1980,4,2) };

 

            this.People.Add(person1);

            this.People.Add(person2);

            this.People.Add(person3);

 

            this.AddPerson = 

                new Command(() => this.People.Add(new Person()));

 

            this.DeletePerson = 

                new Command<Person>((person) => this.People.Remove(person));

        }

    }

}

The People and SelectedPerson properties expose a collection of Person objects and a single Person, respectively, and the latter will be bound to the SelectedItem property of a ListView, as you will see shortly. Notice how properties of type ICommand are assigned with instances of the Command class, to which you can pass an Action delegate via a lambda expression that executes the desired operation. The Command provides an out-of-the-box implementation of the ICommand interface, and its constructor can also receive a parameter, in which case you must use its generic overload (see DeletePerson assignment). In that case, the Command works with objects of type Person, and the action is executed against the received object. Commands and other properties are data-bound to views in the user interface.

  Note: Here I demonstrated the most basic use of commands. However, commands also expose a CanExecute Boolean method that determines whether an action can be executed or not. Additionally, you can create custom commands that implement ICommand and must explicitly implement the Execute and CanExecute methods, where Execute is invoked to run an action. For further details, look at the official documentation.

Now it is time to write the XAML code for the user interface. Code Listing 25 shows how to use a ListView for this, and how to bind two Button views to commands.

Code Listing 25

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

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"

             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"

             xmlns:local="clr-namespace:MvvmSample"

             x:Class="MvvmSample.MainPage" Padding="20">

 

    <StackLayout>

        <ListView x:Name="PeopleList" 

                  ItemsSource="{Binding People}"

                  HasUnevenRows="True" 

                  SelectedItem="{Binding SelectedPerson}">

            <ListView.ItemTemplate>

                <DataTemplate>

                    <ViewCell>

                        <ViewCell.View>

                            <StackLayout Margin="10">

                                <Label Text="Full name:"/>

                                <Entry Text="{Binding FullName}"/>

                                <Label Text="Date of birth:"/>

                                <DatePicker Date="{Binding DateOfBirth, 

                                            Mode=TwoWay}"/>

                                <Label Text="Address:"/>

                                <Entry Text="{Binding Address}"/>

                            </StackLayout>

                        </ViewCell.View>

                    </ViewCell>

                </DataTemplate>

            </ListView.ItemTemplate>

        </ListView>

        <StackLayout Orientation="Horizontal">

            <Button Text="Add" Command="{Binding AddPerson}"/>

            <Button Text="Delete" Command="{Binding DeletePerson}" 

                    CommandParameter="{Binding Source={x:Reference PeopleList}, 

                    Path=SelectedItem}"/>

        </StackLayout>

    </StackLayout>

</ContentPage>

Tip: Remember to set HasUnevenRows to False and to provide a RowHeight for the ListView on iOS.

The ListView is very similar to the example shown when introducing data binding to collections. However, notice that:

  • The ListView.ItemsSource property is bound to the People collection in the view model.
  • The ListView.SelectedItem property is bound to the SelectedPerson property in the view model.
  • The first Button is bound to the AddPerson command in the view model.
  • The second Button is bound to the DeletePerson command, and it passes the selected Person object in the ListView with a special binding expression: Source represents the data source, in this case the ListView, referred to with x:Reference; Path points to the property in the source that exposes the object you want to pass to the command as a parameter (simply referred to as command parameter).

The final step is creating an instance of the view model and assigning it to the BindingContext of the page, which you can do in the page code-behind, as demonstrated in Code Listing 26.

Code Listing 26

using MvvmSample.ViewModel;

using Xamarin.Forms;

 

namespace MvvmSample

{

    public partial class MainPage : ContentPage

    {

        // Not using a field here because properties

        // are optimized for data binding.

        private PersonViewModel ViewModel { getset; }

 

        public MainPage()

        {

            InitializeComponent();

 

            this.ViewModel = new PersonViewModel();

            this.BindingContext = this.ViewModel;

        }

    }

}

If you now run the application (see Figure 46), you will see the list of Person objects, you will be able to use the two buttons, and the real benefit is that the whole logic is in the view model. With this approach, if you change the logic in the properties or in the commands, you will not need to change the page code. In Figure 41, you can see a new Person object added via command binding.

Showing a list of people and adding a new person with MVVM

Figure 41: Showing a list of people and adding a new person with MVVM

MVVM is very powerful, but real-world implementations can be very complex. For example, if you want to navigate to another page and you have commands, the view model should contain code related to the user interface (launching a page) that does not adhere to the principles of MVVM. Obviously, there are solutions to this problem that require further knowledge of the pattern, so I recommend you look at books and articles on the Internet for further study. My recommendation is to not reinvent the wheel: many robust and popular MVVM libraries already exist, and you might want to choose one from among the following:

I have personally worked with FreshMvvm, but all the aforementioned alternatives are powerful enough to save you a lot of time.

Chapter summary

XAML plays a fundamental role in Xamarin.Forms and allows for defining reusable resources and for data-binding scenarios. Resources are reusable styles, data templates, and references to objects you declare in XAML. In particular, styles allow you to set the same properties to all views of the same type, and they can extend other styles with inheritance. XAML also includes a powerful data-binding engine that allows you to quickly bind objects to visual elements in a two-way communication flow.

In this chapter, you have seen how to bind both a single object and a collection to individual visual elements and to the ListView, respectively. You have seen how to define data templates so that the ListView can have knowledge of how items must be presented, and you have learned about value converters, which are special objects that come in to help when you want to bind objects of a type that is different from the type a view supports.

In the second part of the chapter, you walked through an introduction to the Model-View-ViewModel pattern, focusing on separating the logic from the UI and understanding new objects and concepts such as commands. So far, you have only worked with objects and views that Xamarin.Forms offers out of the box, but more often than not, you will need to implement more advanced features that require native APIs. This is what you will learn in the next chapter.

Scroll To Top
Disclaimer
DISCLAIMER: Web reader is currently in beta. Please report any issues through our support system. PDF and Kindle format files are also available for download.

Previous

Next



You are one step away from downloading ebooks from the Succinctly® series premier collection!
A confirmation has been sent to your email address. Please check and confirm your email subscription to complete the download.