Binding SelectedItems of ListView to ViewModel

1. One way to source binding:

You have to use SelectionChanged event. The easiest way is to write eventhandler in codebehind to “bind selecteditems” to viewmodel.

//ViewModel
public ICollectionView BusinessCollection {get; set;}
public List<YourBusinessItem> SelectedObject {get; set;}

//Codebehind
private void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    var viewmodel = (ViewModel) DataContext;
    viewmodel.SelectedItems = listview.SelectedItems
        .Cast<YourBusinessItem>()
        .ToList();
}

This still aligns with MVVM design, because view and viewmodel resposibilities are kept separated. You dont have any logic in codebehind and viewmodel is clean and testable.

2. Two way binding:

if you also need to update view, when viewmodel changes, you have to attach to ViewModel’s PropertyChanged event and to the selected items’ CollectionChanged event. of course you can do it in codebehind, but in this case I would create something more reusable:

//ViewModel
public ObservableCollection<YourBusinessItem> SelectedObject {get; set;}

//in codebehind:
var binder = new SelectedItemsBinder(listview, viewmodel.SelectedItems);
binder.Bind();

or can create custom attached property, so you can use binding syntax in xaml:

<ListView local:ListViewExtensions.SelectedValues="{Binding SelectedItem}" .../>
public class SelectedItemsBinder
{
    private ListView _listView;
    private IList _collection;


    public SelectedItemsBinder(ListView listView, IList collection)
    {
        _listView = listView;
        _collection = collection;

        _listView.SelectedItems.Clear();

        foreach (var item in _collection)
        {
            _listView.SelectedItems.Add(item);
        }
    }

    public void Bind()
    {
        _listView.SelectionChanged += ListView_SelectionChanged;

        if (_collection is INotifyCollectionChanged)
        {
            var observable = (INotifyCollectionChanged) _collection;
            observable.CollectionChanged += Collection_CollectionChanged;
        }
    }

    public void UnBind()
    {
        if (_listView != null)
            _listView.SelectionChanged -= ListView_SelectionChanged;

        if (_collection != null && _collection is INotifyCollectionChanged)
        {
            var observable = (INotifyCollectionChanged) _collection;
            observable.CollectionChanged -= Collection_CollectionChanged;
        }
    }

    private void Collection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        foreach (var item in e.NewItems ?? new object[0])
        {
            if (!_listView.SelectedItems.Contains(item))
                _listView.SelectedItems.Add(item);
        }
        foreach (var item in e.OldItems ?? new object[0])
        {
            _listView.SelectedItems.Remove(item);
        }
    }

    private void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        foreach (var item in e.AddedItems ?? new object[0])
        {
            if (!_collection.Contains(item))
                _collection.Add(item);
        }

        foreach (var item in e.RemovedItems ?? new object[0])
        {
            _collection.Remove(item);
        }
    }
}

Attached property implementation

public class ListViewExtensions
{

    private static SelectedItemsBinder GetSelectedValueBinder(DependencyObject obj)
    {
        return (SelectedItemsBinder)obj.GetValue(SelectedValueBinderProperty);
    }

    private static void SetSelectedValueBinder(DependencyObject obj, SelectedItemsBinder items)
    {
        obj.SetValue(SelectedValueBinderProperty, items);
    }

    private static readonly DependencyProperty SelectedValueBinderProperty = DependencyProperty.RegisterAttached("SelectedValueBinder", typeof(SelectedItemsBinder), typeof(ListViewExtensions));


    public static readonly DependencyProperty SelectedValuesProperty = DependencyProperty.RegisterAttached("SelectedValues", typeof(IList), typeof(ListViewExtensions),
        new FrameworkPropertyMetadata(null, OnSelectedValuesChanged));


    private static void OnSelectedValuesChanged(DependencyObject o, DependencyPropertyChangedEventArgs value)
    {
        var oldBinder = GetSelectedValueBinder(o);
        if (oldBinder != null)
            oldBinder.UnBind();

        SetSelectedValueBinder(o, new SelectedItemsBinder((ListView)o, (IList)value.NewValue));
        GetSelectedValueBinder(o).Bind();
    }

    public static void SetSelectedValues(Selector elementName, IEnumerable value)
    {
        elementName.SetValue(SelectedValuesProperty, value);
    }

    public static IEnumerable GetSelectedValues(Selector elementName)
    {
        return (IEnumerable)elementName.GetValue(SelectedValuesProperty);
    }
}

Leave a Comment