Data Binding Custom Controls in Forms

Xamarin.Forms is a new way to build mobile applications.  Using the ideas of generalization and translation, it allows developers to build a single UI definition which is then translated into native controls for a platform by the rendering engine.  Effectively this enables you to build as many as 4 UIs (Windows, Windows Phone, iOS, and Android) at the same time, greatly improving the productivity of development teams.  On top of that, Xamarin has ensured that the data binding support from Microsoft Xaml is alive and well in Forms Xaml, which makes Forms ideal for supporting the MVVM pattern.  However, there are certain shortcoming that may require additional work on the part of the developer, usually to add support for these bindings to support Mvvm ViewModels.  This is where Custom Controls come in.

The Purpose of a Custom Control

When I say Custom Control, I am talking about a class which either represents a control or extends an existing one.  In Forms, these controls serve two purposes: custom property holding and/or custom rendering markings.  I will discuss the later in a future blog entry, today we are going to talk about why supporting these custom properties, specifically bindable properties make sense in Forms.

When doing Mvvm its not at all uncommon to have to add custom properties to controls, often times these controls are not designed with Mvvm in mind as in the case with two controls in Forms: the Picker and the ListView.

When addressing a shortcoming such as this, the first step is always to identify how you would like your ViewModel to interact with he bindings of the control (its also helpful to have a clear understanding of the Mvvm pattern).  The default ListView in Forms does have a bindable ItemsSource property which I can bind IEnumerable types to, however, it is lacking a bindable property that enables an action to be invoked when an item is selected; there is an event, but its considered best practice to steer clear of events in view models when doing MVVM.  So our goal is to add this property which our ViewModel can bind an ICommand instance to.

    public class MvvmListView : ListView
    {
        public static BindableProperty ItemSelectedCommandProperty =
            BindableProperty.Create<MvvmListView, ICommand>(
               x => x.ItemSelectedCommand, null);

        public ICommand ItemSelectedCommand
        {
            get { return (ICommand)GetValue(ItemSelectedCommandProperty); }
            set { SetValue(ItemSelectedCommandProperty, value); }
        }
    }

Forms properties a special type called BindableProperty, if you are coming from WPF/Silverlight, this is analgous to the DependencyProperty concept there.  In essence, we are indicating that the ItemSelectedCommand property is bindable; notice the convention for a bindable property is the name of the target property suffixed with ‘Property’.  The create calls informs the Xaml parser that this property exists on MvvmPicker and is of type ICommand.

With this in place, we can write something like this:

<c:MvvmListView HorizontalOptions="Fill" VerticalOptions="Fill"
     HasUnevenRows="false" ItemsSource="{Binding Routes}"
   ItemSelectedCommand="{Binding SelectRouteCommand}">
</c:MvvmListView>

Fair warning, Xamarin Studio is not very good at parsing custom controls, so you may or may not get intellisense here.  Here is a snippet of the property this is bound to in my view model:

public ICommand SelectRouteCommand { get { return new Command<Route>(SelectRoute); } }

This is a very common convention for MVVM and it works nicely.  However, there is still a problem here.  While we have bound the command to the view model, there is nothing that allows the command to be invoked and thus call our SelectRoute method on the view model.  To invoke this we need to tie into the event of the underlying ListView.  So we add the following code to our control:

    public class MvvmListView : ListView
    {
        public static BindableProperty ItemSelectedCommandProperty =
            BindableProperty.Create<MvvmListView, ICommand>(
               x => x.ItemSelectedCommand, null);

        public MvvmListView()
        {
            ItemSelected += OnItemSelected;
        }

        private void OnItemSelected(object sender, SelectedItemChangedEventArgs e)
        {
            var item = e.SelectedItem;
            if (item != null && ItemSelectedCommand != null
               && ItemSelectedCommand.CanExecute(item))
            {
                ItemSelectedCommand.Execute(e.SelectedItem);
            }

            SelectedItem = null;
        }

        public ICommand ItemSelectedCommand
        {
            get { return (ICommand)GetValue(ItemSelectedCommandProperty); }
            set { SetValue(ItemSelectedCommandProperty, value); }
        }
    }

So, when the control is created we immediately create an event handler internally to listen for item selection.  When it happens our event handler is invoked which checks if we have a command which can be executed.  If we have such, we execute it passing the selected item to the underlying command handler (SelectRoute).  And since our underlying command is of type Command<T> the parameter will be cast to T for us before it reaches SelectRoute.  So as a result we can write this in our view model.

        async void SelectRoute(Route route)
        {
            await NavigationService.NavigateToAsync<DetailPageViewModel>(
                new Dictionary<string, string> { { "RouteNumber", route.RouteNumber } });
        }

It is amazing how simple things get when you have MVVM working properly.  Now, lets look at a more complicated example.

When you have to do some extra stuff when things change

The ListView control is an example of a control which requires only minor tweaking to support MVVM Bindings.  There is no such good fortune with the Picker control.  The Picker control essentially is a touch friend dropdown control, enabling the user to make a single selection from a list of items.  As with the ListView we have to first consider our goals in supporting MVVM with this control.

  • We would like to be able to bind a list of data from the view model
  • We would like to be able to take some action in our ViewModel when a selection is made
  • We would like the text value of the current selection to show up in the control when not focused

The first goal sounds an awful lot like the ListView.  Check the control we know there is an Items property.  If we try to bind it we notice that it doesnt work, and in fact the documentation indicates the property is not bindable.  So it means we must create our own.  So we start off creating a BindableProperty field and corresponding value field:

    public class MvvmPicker : Picker
    {
        public static BindableProperty ItemsSourceProperty =
            BindableProperty.Create<MvvmPicker, IEnumerable>(
               x => x.ItemsSource, default(IEnumerable), BindingMode.OneWay,
                null, OnItemsSourceChanged);

        static void OnItemsSourceChanged(BindableObject bindable,
             IEnumerable oldvalue, IEnumerable newvalue)
        {
            var picker = bindable as MvvmPicker;
            picker.Items.Clear();
            if (newvalue != null)
            {
                //now it works like "subscribe once" but you can improve
                foreach (var item in newvalue)
                {
                    picker.Items.Add(item.ToString());
                }
            }
        }

        public IEnumerable ItemsSource
        {
            get { return (IEnumerable)GetValue(ItemsSourceProperty); }
            set { SetValue(ItemsSourceProperty, value); }
        }
    }

This is a bit more complicated, mainly because of what we are actually doing.  Like before we created a BindableProperty, however, this time we named it ItemsSource.  We indicated that this property supports ONLY OneWay binding, meaning data is allowed to come from the view model, changes are not transmitted to the view model.  Finally, and most importantly, we indicate that when the binding does change a specific method should be invoked: OnItemsSourceChanged.  This is very important.

Regardless, of what new properties we create within our custom control, the Picker will still look at its Items collection for data.  So, when we receive a new list of items we have to clear and then add each item to Items one at a time.  You will find this pattern quite often in MVVM, especially when controls do not support binding.  Remember, you are not adding new functionality so much as adding new ways for existing functionality to be invoked.  With this, we are able to bind a list of items to the Picker.  Notice that the display which appears in the Picker UI is the string representation of the object (call to ToString()).

With that in place, we know have to add a means to invoke a command when a selection is made.

    public class MvvmPicker : Picker
    {
        public static BindableProperty ItemsSourceProperty =
            BindableProperty.Create<MvvmPicker, IEnumerable>(
               x => x.ItemsSource, default(IEnumerable), BindingMode.OneWay,
               null, OnItemsSourceChanged);

        public static BindableProperty SelectedItemProperty =
            BindableProperty.Create<MvvmPicker, object>(
               x => x.SelectedItem, default(object), BindingMode.TwoWay,
               null, OnSelectedItemChanged);

        public MvvmPicker()
        {
            SelectedIndexChanged += OnSelectedIndexChanged;
        }

        static void OnItemsSourceChanged(BindableObject bindable,
              IEnumerable oldvalue, IEnumerable newvalue)
        {
            var picker = bindable as MvvmPicker;
            picker.Items.Clear();
            if (newvalue != null)
            {
                //now it works like "subscribe once" but you can improve
                foreach (var item in newvalue)
                {
                    picker.Items.Add(item.ToString());
                }
            }
        }

        static void OnSelectedItemChanged(BindableObject bindable, object oldValue, object newValue)
        {
            var picker = bindable as MvvmPicker;
            if (newValue != null)
            {
                picker.SelectedIndex = picker.Items.IndexOf(newValue.ToString());
            }
        }

        void OnSelectedIndexChanged(object sender, EventArgs ev)
        {
            if (SelectedIndex < 0 || SelectedIndex > Items.Count - 1)
            {
                SelectedItem = null;
            }
            else
            {
                SelectedItem = Items[SelectedIndex];
            }
        }

        public IEnumerable ItemsSource
        {
            get { return (IEnumerable)GetValue(ItemsSourceProperty); }
            set { SetValue(ItemsSourceProperty, value); }
        }

        public object SelectedItem
        {
            get { return GetValue(SelectedItemProperty); }
            set { SetValue(SelectedItemProperty, value); }
        }
    }

This is a technique that I am very fond of.  You might have been expecting to see an ICommand to handle this case, guess again.  This goes back to the idea of understanding your interaction model.  Why have two things when one will suffice.  As with the ListView we are leveraging an an event to perform an action, however, in this case we are using what is already there to communicate the change.  Because we indicate that SelectedItem is two way bound, when can have our view model react to this field changing instead of a command.

I do want to point out that this is purely circumstantial, we could have easily made a command work here.  It really depends on what sort of interaction you are going for.  In my case, since the values were always going to be strings I can easily tell when they change.  If you are dealing with more complex objects then passing the selection back via a parameter might make sense.  There is also the matter of updating the control itself, which can only be done by setting the Picker’s SelectedIndex property, we do that when we respond to the SelectedIndex changing, which in turn causes the OnSelectedItemChanged method to be invoked.

Understanding what property changing means

The entire basis for MVVM rests with the INotifyPropertyChanged interface which is provided by .NET.  Its only stipulation is support for the ProprtyChanged event.  Any MVVM framwork or helper library you work with will leverage this, its essential to MVVM.  In my examples, I have pushed this to a base class and created a method RaisePropertyChanged to fire it.

        protected void RaisePropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

When this is fired for a bound property the get of that property is invoked.  This can be very helpful for causing the entire ViewModel to change by setting a single property.  For example, when the user loads my view model, all directions for the selected route are loaded, however, only the stops for the selected direction are shown.  If the user selects a different direction the list changes.  Here is the code that does that.

// the activate method retrives the data
// and sets various bound properties
public override async void Activate(IDictionary<string, string> parameters = null)
{
   var routeNumber = parameters["RouteNumber"];
   try
   {
      _routeInfo = await _routeService.GetRouteInfoAsync(routeNumber);
      
      // bound properties
      FormattedRouteNumber = string.Format("Route {0}", _routeInfo.RouteNumber);
      RouteDirections = _routeInfo.Directions.Select(x => x.Direction).ToList();
      SelectedDirection = RouteDirections.ElementAt(0);
   }
   catch
   {
      DialogService.ShowAlert(string.Format("Failed to get info for Route {0}", routeNumber));
   }
}

// when we set SelectedDirection we also change our Stops list
private string _selectedDirection;
public string SelectedDirection
{
   get { return _selectedDirection; }
   set
   {
      if (_selectedDirection != value)
      {
         _selectedDirection = value;
         RaisePropertyChanged("SelectedDirection");
         
         // this causes the get of Stops to get called
         RaisePropertyChanged("Stops");
      }
   }
}

// the readonly Stops property has no setter
public IList<RouteStop> Stops
{
   get
   {
      if (_routeInfo == null || _routeInfo.RouteStops == null)
         return new List<RouteStop>();
       
      return _routeInfo.RouteStops[SelectedDirection];
   }
}

Again, things get very easy when you do MVVM right.  Within this snippet, as the comments explain, the SelectedDirection property dictates the value of the Stops property.  Thus when the SelectedDirection property changes the Stops property will also change.  Its all very clean

I will be presenting more on this topic as part of my MVVM Froms intro talk at CodeStock next week in Knoxville.  If you cant make it, leave a comment below if you have any questions.  Happy Coding.

Advertisements

3 thoughts on “Data Binding Custom Controls in Forms

  1. Great post and really helpful explanation of everything. I wonder if the code for binding to a Picker’s ItemSource would be different if I was trying to bind an ObservableCollection to the Picker instead of a List?

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s