none
ListBox.SelectedItem stuck at whatever is first selected and other strange ListBox behavior

    Question

  • Hi,

    I've built a List<Participant> (Participant being my custom type) and assigned this to ListBox.ItemsSource. (I'm aware this is more the Windows Forms way than the WPF way of databinding, but I'd still like to understand what's going on here - even though suggestions on how to perform my task in a more WPF-y way are also welcome.) I've set ListBox.SelectionMode to Single, and attached an event handler to the SelectionChanged event. In this handler, I dump the SelectedIndex and SelectedItem to debug output.

    Contrary to my expectation,

    1) SelectedIndex is always -1. This isn't really causing me any trouble, but it's certainly not what I'd expect.

    2) SelectedItem refers to the correct object the first time (in the windows lifetime) I select an item, but then keeps referring to this first-selected item regardless of whether I deselect (by clicking the item again while holding down the CTRL key) or select some other item.

    3) If I keep clicking around and scrolling a bit (the list has ~320 items) within the list, now and then the list suddenly displays a bunch of items - sometimes many in a row, sometimes non-contiguous items, but it seems always "nearby" items! - as if they were selected. The list keeps firing the SelectionChanged event, but SelectedIndex is forever -1 and SelectedItem (and SelectedValue) always refers to whatever I had selected the first time.

    The item type Participant overrides GetHashCode and Equals as follows:

    public override bool Equals(object obj)
    {
      var other = obj as Participant;
      if (other == null) return false;
    
      return (EmailAddress == other.emailAddress && Name == other.Name);
    }
    
    public override int GetHashCode()
    {
      return EmailAddress.GetHashCode();
    }
    
    

    All the instances in the list have unique email addresses, but even if this wasn't the case I've never told the list anything about duplicates or anything like that, so it seems to me the list should, if it needs to perform equality tests at all, do so based on Object.Equals (a reference comparison) rather than the item type's implementation.

    How it is even possible to get a UI state that apparently indicates lots of selected items is a mystery to me given that my XAML states SelectionMode="Single", but then again according to SelectedIndex nothing's selected at all, and according to SelectedItem/Value it's always whatever I selected first...

    For someone new to WPF this isn't an encouraging experience!

    In case you'd rather suggest a good WPF way to do things, an explanation of what I'm trying to do follows:

    I want to display a list of participants, from where the user is required to select one and only one item. The participants should be presented with name and email address. The participant data comes from a file (a CSV file if it matters). A textbox should allow searching (filtering) the list items; any participant whose name or email address contains the search string should be displayed in the list.

    The list should always reflect the textbox (filter it's items accordingly), but the textbox should also reflect the list if an item is selected. The presentation of participants (which includes name and email) doesn't correspond to any single property on the Participant type, though I can always make a presentation wrapper for the type that does expose such a property if this helps.

    Please note: I am certain that this weird listbox behaivor does not result from anything I'm doing to it's data source. I've commented out all code that touches it, assign the ItemsSource only once, and get precisely the same problems anyway.

    Tuesday, September 14, 2010 11:48 AM

All replies

  • Hi The Dag,

    I performed a test based on your description, I created a custom type and created a list, bound it to the ListBox.ItemsSource and showed the items in a specific DataTemplate. It can work and output the correct SelectedIndex and SelectedItem. I share my test code, hope it can help you; By the way, recommend you to use the ObservableCollection<T> collection type, since it has implemented the INotifyCollectionChanged interface that can notify the collection changed when any item has been changed in the Control. Please refer to http://msdn.microsoft.com/en-us/library/ms752347.aspx#binding_to_collections to know more about "Binding to Collections" in WPF.

     <Grid>
      <ListBox x:Name="list" ItemsSource="{Binding}" SelectionChanged="list_SelectionChanged" SelectionMode="Single">
       <ListBox.ItemTemplate>
        <DataTemplate>
         <Grid>
          <Border Margin="5" BorderBrush="Blue" BorderThickness="1">
           <TextBlock Text="{Binding text}" />
          </Border>
         </Grid>
        </DataTemplate>
       </ListBox.ItemTemplate>
      </ListBox>
     </Grid>
    

    C#:

     public partial class Window1 : Window
     {
      public Window1()
      {
       InitializeComponent();
       ObservableCollection<Item> items = new ObservableCollection<Item>();
       for (int i = 0; i < 320; i++)
       {
        items.Add(new Item() { text = "text " + i.ToString() });
       }
       list.DataContext = items;
      }
    
      private void list_SelectionChanged(object sender, SelectionChangedEventArgs e)
      {
       Console.WriteLine((sender as ListBox).SelectedIndex);
       Console.WriteLine((sender as ListBox).SelectedItem);
      }
    
     }
    
     public class Item
     { public string text { get; set; } }
    

    I think it is unneccessary to implement the Equals method for the bound item class, binding engine can expand the bound items to each ListBoxItem container and can know the corresponding item in the ListBoxItem.

    If it is possible, could please share some XAML and behind-code about your application?

    Sincerely,

    Bob Bao

    MSDN Subscriber Support in Forum 

    If you have any feedback on our support, please contact msdnmg@microsoft.com


    Please remember to mark the replies as answers if they help and unmark them if they provide no help.
    Are you looking for a typical code sample? Please download all in one code framework !
    Wednesday, September 15, 2010 2:20 AM
    Moderator
  • Hi Bob,

    Thanks for the effort to help me out!

    I've tried with an ObservableCollecion<T> as well, but then discovered that this collection doesn't support sorting (at least, there's no Sort(IComparer<T>) method), which I needed. While it seems odd that one should have to choose between the collection being observable and it being sortable, I didn't want to spend much time on *that* right now, so I just reverted to the List<T>.

    My code has now changed and suddenly the list box began behaving as I'd expect it to, i.e. the selected index, item, and value now reflect what appears selected in the UI, and it's now only possible to select one. So unfortunately I'm not able to reproduce it anymore. It makes me feel a bit stupid - having changed *something* in my code that cured it and having no idea *what* - but that's the way it is. I really don't think I was doing anything too exotic though, so it would be interesting to know how it could be reproduced, as surely the listbox shouldn't ever behave like it did.

    In fact, I wonder if it may be the case that it behaved very strangely right up until the point when I rebooted the machine. But that too is a long shot; I certainly did try closing down VS-2010 and restarting it, as I am not 100% convinced of it's internal correctness. (I do occasionally get "unknown compiler error (NullReferenceException)" and similar messages, though usually a simple rebuild (of my tiny project) seems to be enough to get past it.

    Wednesday, September 15, 2010 7:27 AM
  • Hi The Dag,

    Well, regarding the sort for ObservableCollecion<T> or List<T> that bound to a ItemsControl, we can according to below articles, to set the sort for the CollectionView of the ItemsControl: How to: Sort and Group Data Using a View in XAML How to: Sort Data in a View And the following thread discuss a solution to implement the sort on a derived ObservablCollection<T> class: http://social.msdn.microsoft.com/forums/en-US/wpf/thread/5909dbcc-9a9f-4260-bc36-de4aa9bbd383/

    And regarding the SelectedIndex and SelectedItem, I know an issue about it, please refer to this thread: http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/64529f60-fe7b-4cf6-a686-3933e22e5169

    If the items in the ListBox/ListView (ItemsControl) are the same object reference, the selection behavior may occur incorrectly, such as we add the same string in to the ListBox, and select them, the selection is wrong. So please ensure the objects in the ItemSource are the different references of the object.

    For the error of the compiler, there is some error in your environment, like Operation System, .Net Framework or Visual Studio; I am not sure, it need to be debugged deeply.

    If you have any problem, pleaser feel free to let me know.

    Sincerely,
    Bob Bao


    Please remember to mark the replies as answers if they help and unmark them if they provide no help.
    Are you looking for a typical code sample? Please download all in one code framework !
    Thursday, September 16, 2010 10:39 AM
    Moderator
  • I used to run into very similar issue inside of a DataGridView. One column of that grid was an editable ComboBox. I would load the contents of the grid first, and based on values of other TextBox cell, different drop-down list could be loaded in that ComboBox. Initially I used a common binding source, but that would result in the symptoms descriped in this post.

    After totally skipping usage of the binding source, things started working as expected, which is right in line with comments from Bob Bao below.

    Slighly modified code that works properly:

    foreach (DataGridViewRow dgvr in dataGridViewAccountMappings.Rows)
                        {
                            if (!dgvr.IsNewRow)
                            {
                                String strClientID = dgvr.Cells[1].Value.ToString();
                                if (strClientID.Trim() != String.Empty)
                                {
                                    using (DataTable dt = GetAccounts(strClientID))
                                    {
                                        using (DataView dv = dt.DefaultView)
                                        {
                                            dv.Sort = cmAccountingCSAccountSortColumn;
                                            DataGridViewComboBoxCell cBoxCell =  dgvr.Cells[3] as DataGridViewComboBoxCell;
                                            if (cBoxCell != null)
                                            {
                                                cBoxCell.DataSource = dv.ToTable();
                                                cBoxCell.ValueMember = "Account_Number";
                                                cBoxCell.DisplayMember = cmAccountingCSAccountDisplayColumn;                                            
                                            }                                        
                                        }
                                    }
                                }
                            }
                        }

    Code that used to get me stuck o nthe first row of the DataGridView:

    foreach (DataGridViewRow dgvr in dataGridViewAccountMappings.Rows)
                        {
                            if (!dgvr.IsNewRow)
                            {
                                String strClientID = dgvr.Cells[1].Value.ToString();
                                if (strClientID.Trim() != String.Empty)
                                {
                                    using (DataTable dt = GetAccounts(strClientID))
                                    {
                                        using (DataView dv = dt.DefaultView)
                                        {
                                            dv.Sort = cmAccountingCSAccountSortColumn;
                                            bindingSourceAccountMappingAccounts.DataSource = dv.ToTable();
                                            DataGridViewComboBoxCell cBoxCell =  dgvr.Cells[3] as DataGridViewComboBoxCell;
                                            if (cBoxCell != null)
                                            {
                                                cBoxCell.DataSource = bindingSourceAccountMappingAccounts;
                                                cBoxCell.ValueMember = "Account_Number";
                                                cBoxCell.DisplayMember = cmAccountingCSAccountDisplayColumn;                                            
                                            }                                        
                                        }
                                    }
                                }
                            }
                        }



    Dominik Ras mintol1@poczta.onet.pl www.dominikras.com

    Wednesday, August 01, 2012 4:56 AM