2

I want to select programatically multiple items in my ListBox. So, to be as mvvm friendly as possible, I create a custom control inherited from ListBox. In this custom control I've made a dependency property allowing items selection changes. Here is the code of the OnPropertyChanged part :

private static void OnSetSelectionToPropertyChanged
            (DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            InitializableListBox list = d as InitializableListBox;
            Dictionary<int, string> toSelect = e.NewValue as Dictionary<int, string>;
            if (toSelect == null)
                return;

            list.SetSelectedItems(toSelect);
        }

The selection works great, but this solution does not raise the OnSelectionChanged event

So I try also :

private static void OnSetSelectionToPropertyChanged
            (DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            InitializableListBox list = d as InitializableListBox;
            Dictionary<int, string> toSelect = e.NewValue as Dictionary<int, string>;
            if (toSelect == null)
                return;

            SelectionChangedEventArgs e_selChanged;

            List<Object> removed = new List<object>();
            List<Object> added = new List<object>();

            //Clear the SelectedItems list
            while(list.SelectedItems.Count > 0)
            {
                removed.Add(list.SelectedItems[0]);
                list.SelectedItems.RemoveAt(0);
            }

            //Add each selected items
            foreach (var item in toSelect)
            {
                list.SelectedItems.Add(item);
                added.Add(list.SelectedItems[list.SelectedItems.Count - 1]);
            }

            //Raise the SelectionChanged event
            e_selChanged = new SelectionChangedEventArgs(SelectionChangedEvent,removed,added);
            list.OnSelectionChanged(e_selChanged);
        }

But this was not better. I think I'm not dealing the right way with the event so if you could help me, that would be great.

Thanks in advance.

EDIT

I have found another solution (more a hack actually) than @NETscape. I don't think it's better, but it seems to work pretty well, and it's maybe easier.

The trick is to made a dependency property wich allow you to access the SelectedItems property (wich is readonly and unbindable on the normal ListBox). Here is the code of my custom ListBox :

public class InitializableListBox : ListBox
    {

        public InitializableListBox()
        {
            SelectionChanged += CustomSelectionChanged;
        }

        private void CustomSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            InitializableListBox s = sender as InitializableListBox;
            if (s == null)
                return;

            s.CustomSelectedItems = s.SelectedItems;
        }

        #region CustomSelectedItems DependyProperty

        public static DependencyProperty CustomSelectedItemsProperty = 
            DependencyProperty.RegisterAttached("CustomSelectedItems",
            typeof(System.Collections.IList),typeof(InitializableListBox),
            new PropertyMetadata(null, OnCustomSelectedItemsPropertyChanged));

        public  System.Collections.IList  CustomSelectedItems 
        {
            get 
            {
                return (System.Collections.IList)GetValue(CustomSelectedItemsProperty); 
            }
            set 
            { 
                SetValue(CustomSelectedItemsProperty, value);                
            }
        }

        private static void OnCustomSelectedItemsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            InitializableListBox list = d as InitializableListBox;
            System.Collections.IList toSelect = e.NewValue as System.Collections.IList;
            if (toSelect == null)
                return;

            list.SetSelectedItems(toSelect);
        }

        #endregion
    }
Bastien
  • 994
  • 11
  • 25

2 Answers2

1

See this

If you set your ItemsSource to a collection, and the objects in the collection implement INPC, you can set the Style on ListViewItem to use the bound objects IsSelected property.

See this answer to understand what I mean.

Let's say you have ItemsSource="{Binding Items}", then you can do something like:

Items.Where(item => item.IsSelected == true);  

to return your list of items that are selected.
You could also do Items[0].IsSelected = true; to programmatically select an item.

In short, you shouldn't have to use a custom control to implement multiple selection on a ListView.

EDIT

I experienced the virtualization problem when I was implementing Telerik's RadGridView. I used a behavior to counter this problem and it seems to have worked:

public class RadGridViewExt : Behavior<RadGridView>
{
    private RadGridView Grid
    {
        get
        {
            return AssociatedObject as RadGridView;
        }
    }

    public INotifyCollectionChanged SelectedItems
    {
        get { return (INotifyCollectionChanged)GetValue(SelectedItemsProperty); }
        set { SetValue(SelectedItemsProperty, value); }
    }

    public static readonly DependencyProperty SelectedItemsProperty =
        DependencyProperty.Register("SelectedItems", typeof(INotifyCollectionChanged), typeof(RadGridViewExt), new PropertyMetadata(OnSelectedItemsPropertyChanged));


    private static void OnSelectedItemsPropertyChanged(DependencyObject target, DependencyPropertyChangedEventArgs args)
    {
        var collection = args.NewValue as INotifyCollectionChanged;
        if (collection != null)
        {
            collection.CollectionChanged += ((RadGridViewExt)target).ContextSelectedItemsCollectionChanged;
        }
    }

    protected override void OnAttached()
    {
        base.OnAttached();

        Grid.SelectedItems.CollectionChanged += GridSelectedItemsCollectionChanged;
    }

    void ContextSelectedItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        UnsubscribeFromEvents();

        Transfer(SelectedItems as IList, AssociatedObject.SelectedItems);

        SubscribeToEvents();
    }

    void GridSelectedItemsCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        UnsubscribeFromEvents();

        Transfer(AssociatedObject.SelectedItems, SelectedItems as IList);

        SubscribeToEvents();
    }

    private void SubscribeToEvents()
    {
        AssociatedObject.SelectedItems.CollectionChanged += GridSelectedItemsCollectionChanged;

        if (SelectedItems != null)
        {
            SelectedItems.CollectionChanged += ContextSelectedItemsCollectionChanged;
        }
    }

    private void UnsubscribeFromEvents()
    {
        AssociatedObject.SelectedItems.CollectionChanged -= GridSelectedItemsCollectionChanged;

        if (SelectedItems != null)
        {
            SelectedItems.CollectionChanged -= ContextSelectedItemsCollectionChanged;
        }
    }

    public static void Transfer(IList source, IList target)
    {
        if (source == null || target == null)
            return;

        target.Clear();

        foreach (var o in source)
        {
            target.Add(o);
        }
    }
}

Inside RadGridView control:

<i:Interaction.Behaviors>
    <local:RadGridViewExt SelectedItems="{Binding SelectedItems}" />
</i:Interaction.Behaviors>

where

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

and remember to add reference to Interactivity assembly.

Community
  • 1
  • 1
Kcvin
  • 5,073
  • 2
  • 32
  • 54
  • Yeah I knew about the IsSelected property but there is some problems with this and the virtualized `ListBox`of WPF. This is why I try with a custom control. – Bastien May 28 '14 at 11:25
  • Yeah, you right I can do that but since my List can have a lot of items, a problem of fluidity occurs, that why I'm going nuts with this custom control. -Edit Oops sorry didn't see your edit – Bastien May 28 '14 at 11:39
  • I found another solution (see my edit). It seems to work great even with a lot of items. I will try with tons of them to see if it's good but I see no reason why it will not. Anyway, thanks a lot for your help. – Bastien May 28 '14 at 13:31
0

ListBox has a property called SelectionMode. You can set it to Multiple to enable multiple selection.

<ListBox x:Name="MyListBox" SelectionMode="Multiple"/>

You can use SelectedItems property to get all the selected items afterwards.

Noctis
  • 11,507
  • 3
  • 43
  • 82
pankaj
  • 185
  • 1
  • 2
  • 10
  • Thank you but I'am already in `SelectionMode="Multiple"`.The problem occurs when I try to select the items **programmatically**. – Bastien May 28 '14 at 11:12