-1

When binding a ListBox to a CollectionViewSource (for view filtering), when re-binding to the Source property, the ListBox automatically selects the first item (causing the SelectedItem binding to also fire).

I do not want the first item in my list to automatically select, nor do I want the setter for SelectedItem to fire at all (as I have other logic that gets called when an item is selected).

Here is a self-contained example, drop this inside a blank, new WPF application to demonstrate:

using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;

namespace TestBinding
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            var template = new DataTemplate();
            FrameworkElementFactory tbFactory = new FrameworkElementFactory(typeof(TextBlock));
            tbFactory.SetBinding(TextBlock.TextProperty, new Binding("Name"));
            template.VisualTree = tbFactory;

            var itemsA = new List<Item>
            {
                new Item { Name = "A Item 1" },
                new Item { Name = "A Item 2" },
                new Item { Name = "A Item 3" },
                new Item { Name = "A Item 4" },
                new Item { Name = "A Item 5" },
                new Item { Name = "A Item 6" },
            };
            var itemsB = new List<Item>
            {
                new Item { Name = "B Item 1" },
                new Item { Name = "B Item 2" },
                new Item { Name = "B Item 3" },
                new Item { Name = "B Item 4" },
            };
            var itemsC = new List<Item>
            {
                new Item { Name = "C Item 1" },
                new Item { Name = "C Item 2" },
                new Item { Name = "C Item 3" },
                new Item { Name = "C Item 4" },
                new Item { Name = "C Item 5" },
                new Item { Name = "C Item 6" },
                new Item { Name = "C Item 7" },
                new Item { Name = "C Item 8" },
            };

            var sp = new StackPanel();
            var b1 = new Button
            {
                Content = "Bind A"
            };
            b1.Click += (_, __) =>
            {
                Items = itemsA;
            };
            var b2 = new Button
            {
                Content = "Bind B"
            };
            b2.Click += (_, __) =>
            {
                Items = itemsB;
            };
            var b3 = new Button
            {
                Content = "Bind C"
            };
            b3.Click += (_, __) =>
            {
                Items = itemsC;
            };

            var list = new ListBox
            {
                ItemTemplate = template
            };

            list.SetBinding(ItemsControl.ItemsSourceProperty, new Binding("ItemFilter.View")
            {
                Source = this
            });
            list.SetBinding(Selector.SelectedItemProperty, new Binding("SelectedItem")
            {
                Source = this
            });

            sp.Children.Add(b1);
            sp.Children.Add(b2);
            sp.Children.Add(b3);
            sp.Children.Add(list);

            (Content as Grid).Children.Add(sp);

            ItemFilter = new CollectionViewSource();

            DataContext = this;
        }

        private List<Item> _Items;
        public List<Item> Items
        {
            get { return _Items; }
            set
            {
                _Items = value;
                ItemFilter.Source = _Items;
            }
        }

        #region Dependency Properties

        public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register("SelectedItem", typeof(Item), typeof(MainWindow), new PropertyMetadata(default(Item)));
        public Item SelectedItem
        {
            get { return (Item)GetValue(SelectedItemProperty); }
            set { SetValue(SelectedItemProperty, value); }
        }

        public static readonly DependencyProperty ItemFilterProperty = DependencyProperty.Register("ItemFilter", typeof(CollectionViewSource), typeof(MainWindow), new PropertyMetadata(default(CollectionViewSource)));
        public CollectionViewSource ItemFilter
        {
            get { return (CollectionViewSource)GetValue(ItemFilterProperty); }
            set { SetValue(ItemFilterProperty, value); }
        }

        #endregion
    }

    public class Item : INotifyPropertyChanged
    {
        private string _Name;
        public string Name
        {
            get { return _Name; }
            set { NotifyPropertyChanged(ref _Name, value, "Name"); }
        }

        #region INPC

        public event PropertyChangedEventHandler PropertyChanged;
        public void NotifyPropertyChanged<T>(ref T item, T value, string name)
        {
            item = value;
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(name));
            }
        }

        #endregion
    }
}
qJake
  • 16,821
  • 17
  • 83
  • 135
  • Try setting the SelectedIndex = -1 – paparazzo Apr 30 '14 at 18:09
  • @Blam That still fires the setter of the `SelectedItem` property, which I do not want. – qJake Apr 30 '14 at 18:09
  • Want and get are not the same. You can ignore the -1. – paparazzo Apr 30 '14 at 18:26
  • What? `-1` never comes across as a value, `SelectedItem` would just be `null`, but it would still fire the `set { }` block on the property, which again, causes some logic and data retrieval in my application that I don't want to happen. Re-binding the `CollectionViewSource` should **not** fire the setter of the `SelectedItem` property (but it is), hence the question. – qJake Apr 30 '14 at 18:31
  • What??? The -1 causes the SelectedItem to be null (not the first item). So you don't want it fire the setter- that is not going to stop it. Really you cannot check for SelectedItem = null or SelectedIndex = -1 and adjust the logic. e.g. if (value == null) return; – paparazzo Apr 30 '14 at 18:52
  • No, because `null` is actually a valid value in my logic, it serves a distinct purpose, if no items are selected it returns a unique dataset. Ignoring `null` would cause functionality in the app to break. – qJake Apr 30 '14 at 18:56
  • Really? I don't see a null in any of those items source. What is so hard about checking for a SelectedIndex of -1? Rebinding is going to fire the set. The answer is deal with it and there are ways to deal with it. – paparazzo Apr 30 '14 at 19:46
  • You don't see a `null` in the items source because **`null` is automatically returned as the `SelectedItem` when the `SelectedIndex` is `-1`.** Clearly you don't understand what's really going on, or the difference between what a selected index and a selected value are, if your answer to me is "*deal with it*". – qJake Apr 30 '14 at 19:57
  • My answer is that there is an easy way to deal with it. You don't want the first item to be selected with a rebind. Easy SelectedIndex = -1. You don't want to process a null value caused by a rebind. Easy check for SelectedIndex = -1. What part of that do you not understand? – paparazzo Apr 30 '14 at 20:12
  • `SelectedIndex = -1` and `SelectedItem = null` are the same thing, and I'm telling you that these mean something in my application, I pass this value to my data provider and it accepts `null` as a valid value and performs some action based on it. The user is able to deselect the listbox manually which causes this case, but I'm saying that I don't want this event to occur when rebinding occurs - *only* when the user triggers it. – qJake Apr 30 '14 at 20:14
  • Really you cannot figure out how to sense the difference between user item deselect and button rebind. One you could like set a flag in the button rebind. And ListView does not support deselect. If you have custom code that that deals with a deselect that you did not post then you could deal with it there. – paparazzo Apr 30 '14 at 20:43

2 Answers2

1

short answer: you can't... updating the datasource automatically updates the display, and that automatically reverts the selection back to the first one in the list. The only other thing you could do would be to remember the selection in memory, then re-select it once the binding is updated, but since you said that's not an option for you, then it's impossible.

Unless you don't mind setting a flag to skip your Select event code...

e.g.:

remember,
update binding
set flag to "skip code"
select item from memory
set flag to "run code"

MaxOvrdrv
  • 1,780
  • 17
  • 32
-1

Try collectionviewsource.view.moveto(-1).

amolDotnet
  • 139
  • 6
  • 1
    If you're referring to [`MoveCurrentTo()`](http://msdn.microsoft.com/en-us/library/system.windows.data.collectionview.movecurrentto(v=vs.110).aspx), this deals with the internal pointer that the CollectionView uses, it does not affect the `SelectedItem` of the `ListBox`. – qJake Apr 30 '14 at 18:59