即使在SelectionMode="Single"的情况下,ListBox仍然选择了许多项目

7
我遇到了一些非常奇怪的事情,一个简单的WPF应用程序。
<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>

使用代码后端
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 ();
    }
}

当我点击一个单个项目时,如果我继续点击其他项目,则它们会聚合。点击已选择的项目不会有任何反应。让我感到困惑的是,我以前将数据绑定列表到ListBox,从未见过这种情况。我的操作系统是Win7(64位),使用VS2010,这种行为出现在.Net 3.5,.Net 3.5客户端框架,.Net 4和.Net 4客户端框架中。

额,我应该提到我预期会有常规、默认的单选行为。


4
我怀疑这是因为所有的选项实际上都是同一个实例(指向相同常量字符串的相同引用)。 - Dan Bryant
请检查SelectedItems属性。可能只选择了一项,但是样式中的某些东西出了问题。 - Jonathan Allen
@Dan。那是一个有趣的理论,我想亲自测试一下,但我马上就要离开了。 - Jonathan Allen
@Dan Bryant,嗯,很可能 - 只是将字面量更改为不同的字符,似乎就可以工作了。如果选择操作等效于值,为什么不选择所有值?如果选择操作等效于引用,为什么会发生这种情况(不使用 const 但是使用内联字面量)?我只是很困惑,但这是在生成测试数据时出现的... - johnny g
你的内联字面量由编译器确定为相同的常量值,并存储在加载的应用程序内存中的单个位置。它们将全部是相同的引用。 - Dan Bryant
我怀疑这与ListBox使用的String的Compare方法有关,以确定选择了哪个项目。想一想...当您比较包含相同数据的2个字符串时,它们被认为是相等的(有意义):因此,ListBox选择包含相同数据的所有字符串。如果字符串比较不同...例如,如果它们基于它们的内存地址或某种唯一ID进行比较,那么它可能会起作用。 要解决问题,请将字符串包装在对象中,以便可以比较内存地址而不是字符串内容。 - Frinavale
2个回答

11

Dan Bryant在他的评论中已经解释了大部分问题。

这里发生的是字符串池化。当您创建一堆具有相同值的字符串时,.Net通过使所有对相同字符串值的引用实际上都引用同一个字符串对象来节省内存使用。 (有关详细信息,请参见此处。)

我不太清楚ListBox的行为为什么会像它现在这样,即您第一次选择列表中的任何项目时,它会同时选择该项目和列表中的第一个项目。但是当您单击新项目时,它不会取消选择,因为它检查SelectedItem是否与您刚刚单击的项目不同,而它确实不同。

我通过将ListBox绑定到测试对象集合来获得完全相同的行为:

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

MainWindow.xaml 中:

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

在 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;

接受答案并添加一些自己的评论。抱歉,但感谢你第一个出现在现场,Dan。很想知道为什么当遇到这种特定情况时ListBox会表现出这种行为... - johnny g
2
@johnny g,我认为这是WPF设计哲学的产物,即列表框代表不同数据项的列表。通常情况下,您不会直接绑定字符串,而是绑定呈现为字符串的数据项(通过DataTemplate或DisplayMember),这意味着您应该拥有唯一的数据项,即使在某些情况下呈现的字符串相同。如果您需要在具有相同表示的项目之间进行选择,则仍然需要某些数据对象来编码它们之间的差异方式(它们必须不同,否则您将没有两个选项)。 - Dan Bryant
2
如果非唯一项是预期的使用情况,那么公开SelectedItem属性就不是一个明智的选择。 - Robert Rossney
@Dan Bryant - 如果这真的是设计理念的话,我认为这个理念很差。我曾遇到这样的情况:我将一个模型实例列表绑定到列表框,并允许用户向列表中添加新的实例。新实例具有相同的默认值,并且模型通过实现NHibernate所要求的Equals/GetHashCode方法。然而,列表框却认为所有新项目都是相同的项目。显然,这不应该由UI来决定,对吧? - Neil Moss
我有一些独特的项目,使用.ToString()方法生成了相同的值,导致出现了这个问题,所以这也是需要注意的。 - imma

7

我也遇到了这个问题--正如其他人所指出的,.NET以一种奇特的方式处理字符串以改善内存管理。

我的直接解决方法是创建一个UniqueListItem类来代替我计划添加到列表框中的字符串。

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;
    }
}

因为每个此对象的实例都会有自己的内存位置,将此对象的实例添加到列表框控件中而不是添加字符串将导致唯一的选择,即使在列表框中显示的字符串相同。

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

我不能确定这是否是最佳解决方案(这取决于您的项目需求),但希望有人会觉得这有所帮助。


我添加了数据到listBox,如何获取这些数据?请帮帮我。 - Samarth Agarwal

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接