为什么IEnumerable <t>需要调用ToList来更新列表视图?</t>

时间:2010-09-16 12:32:44

标签: c# wpf listview

我确信这有很好的解释。我猜它与感冒有关,遗漏了一些明显的东西......

我有一个简单的窗口:

<Window x:Class="WpfIdeas.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:w="clr-namespace:WpfIdeas"
    Title="Window1" Height="300" Width="315">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="20"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
        </Grid.RowDefinitions>
        <Button Grid.Row="0" x:Name="btnAddObject" Click="btnAddObject_Click">Add Object</Button>
        <ListView Grid.Row="1"  ItemsSource="{Binding Objects}">
        </ListView>
    </Grid>
</Window>

窗口后面的代码是:

using System.Windows;

namespace WpfIdeas
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
            DataContext = new ObjectVM();
        }

        private void btnAddObject_Click(object sender, RoutedEventArgs e)
        {
            (DataContext as ObjectVM).AddObject();
        }
    }
}

它的DataContext设置为以下类:

class ObjectVM : INotifyPropertyChanged
{
    private readonly List<ObjectModel> objects = new List<ObjectModel>();

    //public IEnumerable<ObjectModel> Objects { get { return objects } } //doesn't work
    public IEnumerable<ObjectModel> Objects { get { return objects.ToList() } } //works

    private Random r = new Random();

    public void AddObject()
    {
        ObjectModel o = new ObjectModel(r);
        objects.Add(o);
        if(PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs("Objects"));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

ObjectModel类实际上是一个在实例化时生成14个字符串的结构。它的ToString()方法只输出该字符串。

如上面的代码所示,当我点击“添加对象”按钮时,ListView中会出现一个新字符串。

但是,如果我删除ToList()属性中的Objects来电,则ListView中不会显示任何内容。它只是空白。

为什么会这样?

4 个答案:

答案 0 :(得分:4)

Using Collection Objects as a Binding Source

您可以枚举任何实现IEnumerable接口的集合。但是,要设置动态绑定以便集合中的插入或删除自动更新UI,集合必须实现INotifyCollectionChanged接口。此接口公开了一个事件,只要底层集合发生更改,就必须引发该事件。

答案 1 :(得分:2)

objects.ToList()将在每个Button-Click上创建一个新列表。这可能是列表刷新自己的暗示?

我猜这里......但是当你NotifyPropertyChanged时,那么框架可能会检查该属性是否确实发生了变化(它不是return objects情况下 - 它仍然是相同的列表)

答案 2 :(得分:2)

如果在属性上引发PropertyChanged事件,绑定将检查属性的值是否已更改,并刷新目标(如果已更改)。由于Objects是引用类型,因此只有在将其分配给新实例时才会更改其值 - 这是使用ToList()ToArray()的实例。

换句话说,当你的代码引发PropertyChanged时,你并没有声明列表的内容已经改变,你断言该属性包含一个新列表。绑定检查源上的属性与目标上的属性,并且不同意。

这就是您应该使用ObservableCollection<T>或其他一些实现INotifyCollectionChanged的集合的原因。如果绑定到实现INotifyCollectionChanged的属性,绑定将侦听两个PropertyChanged事件(如果创建新集合并更改属性的值,则会引发)和CollectionChanged(在项目被添加到集合中或从集合中删除。

另请注意,将基础集合更改为ObservableCollection<T>是不够的。您必须更改您正在公开的属性的类型。绑定不会尝试侦听IEnumerable<T>属性上的事件,因为该接口不会公开这些事件。

答案 3 :(得分:0)

这是最常见的,因为IEnumerable是Linq查询的结果,实际类型与简单的List&lt;&gt;完全不同。或集合&lt;&gt;。会发生的是它(Linq)构建“查询”的逻辑表示但不立即运行它,而是在请求值并且每个值yielding时运行它。这是Linq的基本概念之一。 Linq的不同风格可能会选择在封面下以不同的方式实现它,但概念是相同的。

实际上没关系,我应该在回答时更仔细地阅读代码;我认为您的代码没有任何方式,因为您只是实例化List。但是,ListBox永远不会直接公开项目,而是将它们包装在ICollectionView中。 ICollectionView可能与此有关,如果类型被视为IEnumerable,则选择延迟加载项目。不过不确定。它也可能取决于ObjectModel的内部结构......虽然可能没有,因为这不会受到ToList()的调用的影响。