7

I have encountered something very strange, simple WPF application

<Window x:Class="ListBoxSelection.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <ListBox ItemsSource="{Binding Path=Strings}" SelectionMode="Single"/>
    </Grid>
</Window>

with code behind

public class ViewModel
{
    public List<string> Strings { get; set; }

    public ViewModel ()
    {
        Strings = new List<string> ();
        Strings.Add ("A");
        // add many items ...
        Strings.Add ("A");
    }
}

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    public MainWindow ()
    {
        InitializeComponent ();

        DataContext = new ViewModel ();
    }
}

and when I click on a single item,

Why multiple values selected?

if I continue clicking items, they just aggregate. Clicking an already selected item does nothing. Scratching my head, I have databound lists to ListBoxes before, and have never seen this before. Running Win7 (64), VS2010, behaviour presents with .Net 3.5, .Net 3.5 Client Profile, .Net 4, and .Net 4 Client Profile.

Arg, I should mention I am expecting normal, default, single-select behaviour.

johnny g
  • 3,533
  • 1
  • 25
  • 40
  • 4
    I suspect this is happening because all of the selections are actually the same instance (all the same reference to the same constant string.) – Dan Bryant Oct 02 '10 at 00:09
  • Check the SelectedItems property. Maybe it is only selecting one item, but something in the styles are screwed up. – Jonathan Allen Oct 02 '10 at 00:12
  • @Dan. That's an interesting theory, I would test it myself but I've got to leave shortly. – Jonathan Allen Oct 02 '10 at 00:13
  • @Dan Bryant, hrm, quite possibly - just changed the literals to distinct characters, and it appears to work. if selection operates as value-equivalent, why don't all values select? if selection operates as reference-equivalent why does this occur at all (not using a const but inline literals)? i'm just very confused, but this arose while generating test data ... – johnny g Oct 02 '10 at 00:18
  • your inline literals are determined by the compiler to be the same constant value and stored in a single place in your loaded application's memory. They will all be the same reference. – Dan Bryant Oct 02 '10 at 00:24
  • I suspect that it has to have something to do with the String's Compare method that the ListBox is using to determine what item is selected. Think about it...when you compare 2 strings that contain the same data, they are considered to be equal (makes sense): so the ListBox selects all Strings that contain the same data. If strings were compared differently...like if they were compared on their memory address or some sort of unique ID it would probably work. To fix the problem, wrap the string in an object so that the memory address can be compared instead of the contents of the string. – Frinavale Jan 18 '12 at 18:14

2 Answers2

11

Dan Bryant got most of the answer in his comment.

What's going on here is string interning. When you create a bunch of strings with the same value, .Net saves on memory usage by having all references to the same string value actually refer to the same string object. (See this, for instance, for details.)

I don't really know why the ListBox behaves exactly the way it does, which is that the first time you select any item in the list, it selects both that item and the first item in the list. But it doesn't unselect when you click on a new item because checks to see if the SelectedItem is different from the item you just clicked on, and it isn't.

I got exactly the same behavior by binding a ListBox to a collection of test objects:

public class TestObject
{
    public override string ToString()
    {
        return GetHashCode().ToString();
    }
}

In MainWindow.xaml:

<ListBox x:Name="MyListBox" ItemsSource={Binding}"/>

In MainWindow.xaml.cs:

ObservableCollection<TestObject> test = new ObservableCollection<TestObject>();
TestObject t = new TestObject();
test.Add(t);
test.Add(t);
test.Add(t);
test.Add(t);
test.Add(t);
test.Add(t);
MyListBox.DataContext = test;
Robert Rossney
  • 94,622
  • 24
  • 146
  • 218
  • accepted for answer-fying Dan's comments and adding some more of your own. apologies Dan, though kudos for being first on the scene. would love to know why ListBox behaves this way when encountering this specific scenario ... – johnny g Oct 02 '10 at 01:00
  • 2
    @johnny g, I think this is an artifact of the design philosophy behind WPF, which is that a list box is representing a list of distinct data items. You typically would not bind strings directly, but rather data items that render as strings (via a DataTemplate or DisplayMember), which means you should have unique data items, even if the strings rendered are identical in some cases. If you need to select between items with identical representations, you still need to have some data object that encodes the way in which they are different (they must be different or you wouldn't have two options) – Dan Bryant Oct 02 '10 at 01:33
  • 2
    If non-unique items were an expected use case, `SelectedItem` wouldn't be a sensible property to expose. – Robert Rossney Oct 02 '10 at 09:24
  • @Dan Bryant - If that really is the design philosophy, then I think it's a poor one. I had the situation where I was binding to a list of my model instances and allowed the user to add new ones to the list. The new instances had the same defaults, and with the model implementing Equals/GetHashCode as required by NHibernate, the list box decided that all new items were the same item. Surely that's not the UI's decision to make? – Neil Moss Sep 27 '12 at 08:53
  • Thankyou :-) I had unique items, with a .ToString() which was producing identical values & was getting this issue, so that's something else to watch out for – imma Oct 03 '13 at 09:24
7

I encountered this problem as well--as others have noted, .NET handles strings in a curious manner to improve memory management.

My immediate workaround was to create a UniqueListItem class to be used in place of the strings I was planning to add to the listbox.

class UniqueListItemObject
{
    private string _text;
    public string Text { get { return _text; } set { _text = value; } }

    public UniqueListItemObject(string input)
    {
        Text = input;
    }
    public UniqueListItemObject()
    {
        Text = string.Empty;
    }

    public override string ToString()
    {
        return Text;
    }
}

Because each instance of this object will get its own memory location, adding instances of this object to a listbox control instead of adding strings will result in unique selections, even if the strings displayed in the listbox are identical.

        yourListBox.Items.Add(new UniqueListItemObject(yourStringHere);

I can't say if this is the best solution (that depends on your project's requirements) but hopefully someone finds this helpful.

Andy Pierre
  • 71
  • 1
  • 1