Value Converters

Value Converters form an important concept in data-binding because they allow you to customize the appearance of a data property at the time of binding. If you have done any WPF or Windows app development, you are probably familiar with how Value Converters work. Xamarin.Forms provides an almost identical Value Converter interface as part of its API.

One of the biggest benefits to a Value Converter is that it prevents you from having to add a bunch of getter properties to your data model to adjust how things are displayed. For example, imagine you had a status property on your model and you wanted to change the font color of the status when it is displayed based on its value. You could add a getter property to your model that returns a color based on the current value of the status property. This approach works, but it clutters the model and also potentially leaks platform-specific and user interface logic into the model, which should typically remain very lean and agnostic. The more appropriate approach is to create a Value Converter that allows you to bind directly to the status property, but display it differently based on the value.

Another common way that Value Converters are helpful in Xamarin.Forms is to toggle visibility of elements based on a Boolean property. Luckily, the Xamarin.Forms API made the VisualElement IsVisible property into a Boolean instead of an enumeration; so showing things based on Boolean properties is fairly straight forward. However, if you want to hide something when a data bound property is true, you will need a Value Converter to convert the true value to a false value when it is bound to IsVisibleProperty of an element.

In the next section, we will create a reverse visibility converter that we will use to hide controls on the screen until the ViewModel has finished loading. We'll also create a converter that converts our integer rating property to stars for a more appealing visual effect.

Creating a reverse visibility Value Converter

There are often cases where your user interface must wait for data to be loaded; in the meantime, the user might see what appears to be a broken or incomplete page. In these situations, it is best to let the user know what is happening by showing some sort of progress indicator and hiding the rest of the user interface, such as labels, until the data is ready.

Right now, our TripLog app is only using local data, so we do not really see any negative visual effects while the ViewModel data is loaded. We will connect our app to a live API in the next chapter, but until then, we can simulate a waiting period by simply adding a three second delay to our MainViewModel LoadEntries method before the LogEntries collection is populated:

async Task LoadEntries()
{
    LogEntries.Clear ();

    // TODO: Remove this in Chapter 6
    await Task.Delay (3000);

    // ...
}

Now, if we run the app, while the list of entries is being loaded, we just see an awkwardly half loaded page as shown in the next image. The empty ListView is not only unappealing, there is also no visual indicator to explain to the user that their data is on its way.

Creating a reverse visibility Value Converter

We can improve this by hiding the ListView until the data is loaded and by showing an ActivityIndicator while it's loading.

In order to know if our ViewModel is loading data, we can create a boolean property called IsBusy that we only set to true while we are actually loading data or doing some sort of lengthy processing. Since we will need to do similar things in other ViewModels, it makes the most sense to include this IsBusy property on the BaseViewModel:

  1. Add a public bool property named IsBusy to the BaseViewModel class, as follows:
    bool _isBusy;
    public bool IsBusy
    {
        get { return _isBusy; }
        set
        {
            _isBusy = value;
            OnPropertyChanged();
            OnIsBusyChanged();
        }
    }
    protected virtual void OnIsBusyChanged()
    {
    }
  2. Next, we need to update the LoadEntries method in MainViewModel to toggle the IsBusy value while it's loading data:
    async Task LoadEntries()
    {
        if (IsBusy)
            return;
    
        IsBusy = true;
    
        LogEntries.Clear ();
    
        // TODO: Remove this in Chapter 6
        await Task.Delay (3000);
    
        LogEntries.Add (new TripLogEntry {
            Title = "Washington Monument",
            Notes = "Amazing!",
            Rating = 3,
            Date = new DateTime(2015, 2, 5),
            Latitude = 38.8895,
            Longitude = -77.0352
        });
        LogEntries.Add (new TripLogEntry {
            Title = "Statue of Liberty",
            Notes = "Inspiring!",
            Rating = 4,
            Date = new DateTime(2015, 4, 13),
            Latitude = 40.6892,
            Longitude = -74.0444
        });
        LogEntries.Add (new TripLogEntry {
            Title = "Golden Gate Bridge",
            Notes = "Foggy, but beautiful.",
            Rating = 5,
            Date = new DateTime(2015, 4, 26),
            Latitude = 37.8268,
            Longitude = -122.4798
        });
    
        IsBusy = false;
    }

Now that our ViewModel indicates when it is busy, we need to update the UI on MainPage to hide the ListView when data is loading and to show a loading indicator instead. We will do this by data binding the IsBusy property in two places. In order to hide the ListView when IsBusy is true, we will need to create a reverse Boolean Value Converter.

  1. Create a new folder in the core project named Converters, and, within it, create a new file named ReverseBooleanConverter that implements Xamarin.Forms.IValueConverter:
    public class ReverseBooleanConverter : IValueConverter
    {
    }
  2. Next, implement the Convert and ConvertBack methods of IValueConverter. The goal of this converter is to return the opposite of a given Boolean value; so, when something is false, the converter will return true:
    public class ReverseBooleanConverter : IValueConverter
    {
        public object Convert (object value, Type targetType,
    object parameter, System.Globalization.CultureInfo culture)
        {
            if (!(value is Boolean))
                return value;
    
            return !((Boolean)value);
        }
    
        public object ConvertBack (object value,
    Type targetType, object parameter,
    System.Globalization.CultureInfo culture)
        {
            if (!(value is Boolean))
                return value;
    
            return !((Boolean)value);
        }
    }
  3. Now, we can bind the IsBusy property to the ListView on the MainPage using this converter, so it is only visible (IsVisible is true) when IsBusy is false:
    public MainPage ()
    {
        // ...
    
        var entries = new ListView {
            ItemTemplate = itemTemplate
        };
    
        entries.SetBinding (ListView.ItemsSourceProperty,
            "LogEntries");
    
        entries.SetBinding (ListView.IsVisibleProperty,
        "IsBusy",
        converter: new ReverseBooleanConverter ());
    
        // ...
    }
  4. Finally, we need to add a loading indicator to the MainPage and only show it when IsBusy is true. We'll do this by adding an ActivityIndicator and a label to a StackLayout, and displaying it in the center of the screen. Also, because we now have two elements to show on the screen, we need to update how we're setting Content:
    public MainPage ()
    {
        // ...
    
        var loading = new StackLayout {
            Orientation = StackOrientation.Vertical,
            HorizontalOptions = LayoutOptions.Center,
            VerticalOptions = LayoutOptions.Center,
            Children = {
                new ActivityIndicator { IsRunning = true },
                new Label { Text = "Loading Entries..." }
            }
        };
    
        loading.SetBinding (StackLayout.IsVisibleProperty,
    "IsBusy");
    
        var mainLayout = new Grid {
            Children = { entries, loading }
        };
    
        Content = mainLayout;
    }

Now, when we launch the app, we will see a nice loading indicator while the data loads instead of a blank list view, as shown in the following screenshot:

Creating a reverse visibility Value Converter

Creating an integer to image Value Converter

In this section we're going to continue to improve the user experience with the use of another Value Converter. Currently, the DetailPage binds to the Rating property and simply displays the integer value in a formatted string, which is a rather boring way to display that data, as shown in the following screenshot:

Creating an integer to image Value Converter

This rating data would look much nicer and stand out to the user much quicker if it were an image of stars instead of plain text. In order to translate a number value to an image, we will need to create a new Value Converter.

  1. Create a new file in the core library Converters folder named RatingToStarImageNameConverter that implements Xamarin.Forms.IValueConverter:
    public class RatingToStarImageNameConverter
       : IValueConverter
    {
    }
  2. Next, override the Convert and ConvertBack methods of IValueConverter. In the Convert method, we need to check that the value is an integer, and then, based on its value, we convert it to an image filename:
    public class RatingToStarImageNameConverter
       : IValueConverter
    {
        public object Convert (object value, Type targetType,
    object parameter, System.Globalization.CultureInfo culture)
        {
            if (value is int) {
                var rating = (int)value;
                if (rating <= 1)
                    return "star_1";
    
                if (rating >= 5)
                    return "stars_5";
    
                return "stars_" + rating;
            }
    
            return value;
        }
    
        public object ConvertBack (object value,
    Type targetType, object parameter,
    System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException ();
        }
    }

    Note

    The star images used in this code are available in the book's companion code. Be sure to add them to the appropriate place in each of the platform projects.

  3. Finally, we need to update the DetailPage to use an Image view instead of a label to display the entry rating. We will still bind to the same ViewModel property; however, we will use the converter we just created to convert it to an image filename:
    public DetailPage ()
    {
        // ...
    
        var rating = new Image {
           HorizontalOptions = LayoutOptions.Center
        };
    
        rating.SetBinding (Image.SourceProperty, "Entry.Rating",
        converter: new RatingToStarImageNameConverter ());
    
        // ...
    
    Content = mainLayout;
    }

Now if we run the app and navigate to one of the entries, we will see a much nicer display that immediately causes the rating to standout to the user, as shown in the following screenshot:

Creating an integer to image Value Converter
..................Content has been hidden....................

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