1
votes

I'm having trouble working with the List View setup to Bind Multiple Selections and remember the selections when I switch the Selection Mode to between Single and Multiple.

Context

I'm creating a Soccer coaching app. The List View will display the Player Roster. On game day, I want an easy way to select the players that showed up to play. I would like to use the Multiple Selection mode to allow the user to select the players that showed up. I will provide a button in the app bar that will control the SelectionMode through a converter. It will switch the List View from Single to Multiple. This part works fine. I can see the List View switch between Single and Multiple.

The part that doesn't work fine is the binding of Selected Items. I must be missing something because it seems extremely difficult to bind to the SelectedItems property reliably. The best thing that has worked so far is the ListViewExtentions from the WinRTXamlToolkit which is what I show in my xaml below. It seems to bind the items to the backing property in the view mode when the selection is made, however, when the SelectionMode is switched back to Single, the back property is cleared out. Also, without modifying the extension code, it broke my SelectionMode code. There is a castastrophic failure in the extension on _listView.SelectedItems.Clear(). If I remove that line, then SelectionMode is back working.

I don't care to use the ListViewExtentions from WinRTXamlToolkit. I only present it here so you know that I have tried it. Ultimately, I'm looking for the right solution for binding SelectedItems.

Here is the List View XAML.

<ListView ItemsSource="{Binding Roster}"
          toolkitExt:ListViewExtensions.BindableSelection="{Binding SelectedPlayers, Mode=TwoWay}"      
          SelectionMode="{Binding IsEditingGameRoster, Converter={StaticResource ListViewSelectionModeFromBooleanNoneOrMultipleConverter}}">
<ListView.ItemTemplate>
    <DataTemplate>
        <!-- List View Display  Not important for describing problem. -->
    </DataTemplate>
</ListView.ItemTemplate>

Namespace is: xmlns:toolkitExt="using:WinRTXamlToolkit.Controls.Extensions"

The View Model has the properties: Roster : ObservableCollection IsEditingGameRoster : Bool SelectedPlayers : ObservableCollection

Thanks for any samples that demonstrate binding Multiple Selection especially if it handles switching SelectionMode as well.

Thanks, Tom

2

2 Answers

1
votes

I was facing this problem too. When i have trying to change Selection mode in converter it worked not as expected. Any workaround i have found is to use multiple selection mode, but operate in CollectionChanged event with added/removed items. For example if i need Single selection mode i am rewriting selection to new selected item.

Something like this:

private void OnSelectedPlayersChanged(object sender, NotifyCollectionChangedEventArgs e)
{
        if (e.Action == NotifyCollectionChangedAction.Add)
        {
            if (this.SelectionMode == Mode.Once)
            {
                SynchronizationContextProvider.UIThreadSyncContext.Post((d) =>
                {
                    this.SelectedPlayers.Clear();
                    this.SelectedPlayers.Add((Players)e.NewItems[0]);
                }, null);
            }
        }

        if (e.Action == NotifyCollectionChangedAction.Remove)
        {
            //some logic
        }
}

It is the best option, what i have found so far. If you'll figure out something better i'll happy to use it.

0
votes

I ended up solving my issue close to what I would like to see but still not perfect. I ended up creating a sub-class of ListView and adding two properties SelectedItems2 and SelectionMode2. If anyone could help adjust the following code so that I could reuse SelectedItems and SelectionMode that would make the solution much closer to perfect.

Sub-Class Code

/// <summary>
/// Enhanced List View
/// 
/// SeletionMode Switching:
/// This class enhances ListView to support changing the selection mode from None/Single to Multiple and back and having it
/// bind the selected items without forgetting them when changing selection mode.  The base ListView will remove the selected items when switching
/// from Multiple SelectionMode to another SelectionMode.
/// 1) Created SelectedItems2 to bind to View Model as SelectedItems will have items removed and SelectedItems is not easy to bind to SelectedItems.
/// 2) On SelectionChanged, remember the selected items in SelectedItems2 how the user selected or unselected them.
/// 3) On SelectionMode set to Multiple, fill the underlying SelectedItems with the values from SelectedItems2 so that the checkboxes are checked on the ListView.
/// 4) Use _isMergingItems flag to ignore selection changed events being fired by merging of the SelectedItem2 and SelectedItems2 collections.
/// 
/// Doesn't support changing SelectedItems2 bound collection while in Multiple SelectionMode yet.
/// To do so we would need to hook the CollectionChanged event on the SelectedItems2 List object that is bound.
/// and likely handle threading issues with the merging of SelectedItems and SelectedItems2.
/// </summary>
public class MyListView : ListView
{
    private bool _isMergingItems;

    public MyListView()
    {
        SelectionChanged += MyListView_SelectionChanged;
    }

    private void MyListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        // Ignore selection changed event when selection mode is not multiple.
        // This is mainly here because when changing from Multiple to Single, this event is fired and will cause
        // the items to be removed from SelectedItems2 which is not what is desired.
        if (SelectionMode != ListViewSelectionMode.Multiple) { return; }
        if (_isMergingItems) { return; }

        try
        {
            _isMergingItems = true;

            var selectedItems2List = SelectedItems2 as IList;
            if (selectedItems2List == null) { return; }

            foreach (var itemToAdd in e.AddedItems)
            {
                selectedItems2List.Add(itemToAdd);
            }

            foreach (var itemToRemove in e.RemovedItems)
            {
                selectedItems2List.Remove(itemToRemove);
            }
        }
        finally
        {
            _isMergingItems = false;
        }
    }

    #region --- Dependency Properties ---

    private static readonly DependencyProperty SelectionMode2Property = DependencyProperty.Register("SelectionMode2", typeof(ListViewSelectionMode), typeof(MyListView), new PropertyMetadata(ListViewSelectionMode.None, SelectionMode2Changed));

    public ListViewSelectionMode SelectionMode2
    {
        get { return (ListViewSelectionMode)GetValue(SelectionMode2Property); }
        set { SetValue(SelectionMode2Property, value); }
    }

    private static void SelectionMode2Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {

        var listView = d as MyListView;
        var value = (ListViewSelectionMode)e.NewValue;

        // Set the underlying selection mode property to cause the control to 
        // change to and from Multiple selection mode.
        listView.SelectionMode = value;

        // When Multiple selction mode, we will need to load the underlying SelectedItems of the ListView
        // so that the checkboxes are checked appropriatly.  When the selection mode is changed the underlying ListView
        // removes the selected items.
        if (value == ListViewSelectionMode.Multiple)
        {
            listView.FillUnderlyingSelectedItems();
        }
    }

    private static readonly DependencyProperty SelectedItems2Property = DependencyProperty.Register("SelectedItems2", typeof(object), typeof(MyListView), null);

    /// <summary>
    /// SelectedItems2 is used as the property to bind the selected items to the ViewModel.
    /// Using a List as the data type did not seem to work for binding.  Since ItemSource is an object on the base class
    /// object was chosen for this property as well.
    /// 
    /// However, SelectedItems2 will need to bind to IList in order for it to work properly.
    /// </summary>
    public object SelectedItems2
    {
        get { return (object)GetValue(SelectedItems2Property); }
        set { SetValue(SelectedItems2Property, value); }
    }

    #endregion

    private void FillUnderlyingSelectedItems()
    {
        var selectedItems2List = SelectedItems2 as IList;
        if (selectedItems2List == null) { return; }

        try
        {
            _isMergingItems = true;

            foreach (var item in selectedItems2List)
            {
                SelectedItems.Add(item);
            }
        }
        finally
        {
            _isMergingItems = false;
        }
    }
}

USAGE: XAML

<localUI:MyListView ItemsSource="{Binding Roster}"
          SelectedItems2="{Binding SelectedPlayers}"
          SelectionMode2="{Binding IsEditingGameRoster, Converter={StaticResource ListViewSelectionModeFromBooleanNoneOrMultipleConverter}}">
    <ListView.ItemTemplate>
        <DataTemplate>
            <!-- List View Display  Not important for describing problem. -->
        </DataTemplate>
    </ListView.ItemTemplate>
</localUI:MyListView>

USAGE: ViewModel

    private ObservableCollection<Player> _selectedPlayers;

    public ObservableCollection<Player> SelectedPlayers
    {
        get
        {
            if (_selectedPlayers == null)
            {
                _selectedPlayers = new ObservableCollection<Player>();
            }

            return _selectedPlayers;
        }
    }