Multi-Select ListView

I was just hanging around in the IDE this week and I had a chat with ListView. He was a very nice guy, but he was complaining about the fact that for some reason he could only ever select one item at a time…

I think of myself as a somewhat decent coder, and I decided to solve this problem – at least some part of it.

The easiest, and quickest, way to add multi-select capabilities to a ListView requires two steps: the “selectable” item and the “select” action.

The Selectable Item

I started with the model:

public class SelectableItem
{
    public object Data { get; set; }
    public bool IsSelected { get; set; }
}

But, since we are doing data binding, I created a bindable object that has several bindable properties:

public class SelectableItem : BindableObject
{
    public static readonly BindableProperty DataProperty =
        BindableProperty.Create(
            nameof(Data),
            typeof(object),
            typeof(SelectableItem),
            (object)null);

    public static readonly BindableProperty IsSelectedProperty =
        BindableProperty.Create(
            nameof(IsSelected),
            typeof(bool),
            typeof(SelectableItem),
            false);

    public SelectableItem(object data)
    {
        Data = data;
        IsSelected = false;
    }

    public SelectableItem(object data, bool isSelected)
    {
        Data = data;
        IsSelected = isSelected;
    }

    public object Data
    {
        get { return (object)GetValue(DataProperty); }
        set { SetValue(DataProperty, value); }
    }

    public bool IsSelected
    {
        get { return (bool)GetValue(IsSelectedProperty); }
        set { SetValue(IsSelectedProperty, value); }
    }
}

You may have noticed that I didn’t create a generic type. This is because later on we are going to want to get the value of the IsSelected property from the ItemSelected event handler, and we won’t know what the type is. However, we can create a generic type that inherits from this base type:

public class SelectableItem<T> : SelectableItem
{
    public SelectableItem(T data)
        : base(data)
    {
    }

    public SelectableItem(T data, bool isSelected)
        : base(data, isSelected)
    {
    }

    // this is safe as we are just returning the base value
    public new T Data
    {
        get { return (T)base.Data; }
        set { base.Data = value; }
    }
}

We now have a nice generic type for the developer (us) and a non-generic type for the event (the machine).

The Selectable ListView

The next step is to hook up the ListView with the selectable items. So, we will need a data field on the view model or the Page – depending on how you work (note the generic SelectableItem):

public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();

        Items = new ObservableCollection<SelectableItem<string>>();

        BindingContext = this;
    }

    public ObservableCollection<SelectableItem<string>> Items { get; }
}

And, we need a list item that has a “checked” state (here it is just a red block):

<ListView ItemsSource="{Binding Items}">
    <ListView.ItemTemplate>
        <DataTemplate>
            <ViewCell>
                <StackLayout Orientation="Horizontal">
                    <!-- the "check" mark -->
                    <BoxView IsVisible="{Binding IsSelected}" 
                             WidthRequest="12" HeightRequest="12"
                             Color="Red" />
                    <!-- the text/data -->
                    <Label Text="{Binding Data}" />
                </StackLayout>
            </ViewCell>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

We should now have a list of items that can show either a selected or unselected state. To handle when an item is selected by the user, we just need an event on the list:

<ListView ... ItemSelected="OnItemSelected">
    ...
</ListView>

And then we write the code to toggle the selection (note the non-generic SelectableItem):

private void OnItemSelected(object sender, SelectedItemChangedEventArgs e)
{
    var item = e.SelectedItem as SelectableItem;
    if (item != null)
    {
        // toggle the selection property
        item.IsSelected = !item.IsSelected;
    }

    // deselect the item
    ((ListView)sender).SelectedItem = null;
}

This is all we need, and we will have a ListView that that can support multiple items being selected.

Improvements

We could call it a day, but we are not going to. The day has just started, and we are thinking that we might want to use this in multiple places. We can just copy the logic – mainly the event and cell view, but there are better ways (and we don’t like duplicate code).

Attached Property

The first thing that we will look at is if we can magically do the event hookup with a single property instead of the event:

<ListView ... local:MultiSelectListView.IsMultiSelect="True">
    ...
</ListView>

That looks nicer than the event, and we have a couple of benefits: no code in the page, the event code is written once and the property is bindable.

What we are doing here is using an attached property to “inject” a bindable property into an existing view type:

public static class MultiSelectListView
{
    public static readonly BindableProperty IsMultiSelectProperty =
        BindableProperty.CreateAttached(
            "IsMultiSelect",
            typeof(bool),
            typeof(ListView),
            false,
            propertyChanged: OnIsMultiSelectChanged);

    public static bool GetIsMultiSelect(BindableObject view)
        => (bool)view.GetValue(IsMultiSelectProperty);

    public static void SetIsMultiSelect(BindableObject view, bool value)
        => view.SetValue(IsMultiSelectProperty, value);

    private static void OnIsMultiSelectChanged(
        BindableObject bindable, object oldValue, object newValue)
    {
        var listView = bindable as ListView;
        if (listView != null)
        {
            // always remove event
            listView.ItemSelected -= OnItemSelected;

            // add the event if true
            if (true.Equals(newValue))
            {
                listView.ItemSelected += OnItemSelected;
            }
        }
    }

    private static void OnItemSelected(
        object sender, SelectedItemChangedEventArgs e)
    {
        var item = e.SelectedItem as SelectableItem;
        if (item != null)
        {
            // toggle the selection property
            item.IsSelected = !item.IsSelected;
        }

        // deselect the item
        ((ListView)sender).SelectedItem = null;
    }
}

We now have a super easy way to turn any list into a multi-select list with a single property.

The ItemsSource

Another area we can improve is the ItemsSource collection. Right now we have quite a long type with two generic types:

public ObservableCollection<SelectableItem<string>> Items { get; }

We can not only make this shorter, but more convenient by creating a new type that derives from ObservableCollection. And, now that we have a new type, we can add additional methods so that we don’t even have to deal with the SelectableItem type at all:

public class SelectableObservableCollection<T> : ObservableCollection<SelectableItem<T>>
{
    ...

    // a constructor overload
    public SelectableObservableCollection(IEnumerable<T> collection)
        : base(collection.Select(c => new SelectableItem<T>(c)))
    {
    }

    ...

    // a convenience property
    public IEnumerable<T> SelectedItems
        => this.Where(i => i.IsSelected).Select(i => i.Data);

    ...

    // a method overload
    public void Add(T item)
    {
        Add(new SelectableItem<T>(item));
    }
}

When we use this new type, we can do some cool things:

// a nice type
public SelectableObservableCollection<string> Items { get; }

// cool methods and properties
private void OnDoSomething()
{
    Debug.WriteLine("You have selected:");

    // use a property
    foreach (var selected in Items.SelectedItems)
    {
        Debug.WriteLine($" - {selected.Data}");
    }

    // use a method
    Items.Add("A New String");
}

The Cell

Finally, as you may have a really cool check mark for selected items (unlike our red block now), we will want to be able to create a custom cell type that will allow us to just specify the item contents, and automatically handle the check mark:

<ListView ItemsSource="{Binding Items}"
          local:MultiSelectListView.IsMultiSelect="True">
    <ListView.ItemTemplate>
        <DataTemplate>
            <local:SelectableViewCell>
                <Label Text="{Binding}" />
            </local:SelectableViewCell>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

This requires a new Cell type:

[ContentProperty(nameof(DataView))]
public class SelectableViewCell : ViewCell
{
    public SelectableViewCell();

    public View CheckView { get; set; }

    public View DataView { get; set; }
}

I have left the implementation of this cell to your imagination… just kidding, I have it all in my my repository. You can find all this code, a sample app and more there too:

I hope you enjoyed this short(ish) post and are able to make your list views handle the selection of multiple items.

One thought on “Multi-Select ListView

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