【问题标题】:WPF ListView: Changing ItemsSource does not change ListViewWPF ListView:更改 ItemsSource 不会更改 ListView
【发布时间】:2014-01-26 14:11:17
【问题描述】:

我正在使用ListView 控件来显示一些数据行。有一个后台任务接收对列表内容的外部更新。新收到的数据可能包含更少、更多或相同数量的项目,而且项目本身也可能发生了变化。

ListView.ItemsSource 绑定到 OberservableCollection (_itemList),因此对 _itemList 的更改也应该在 ListView 中可见。

_itemList = new ObservableCollection<PmemCombItem>();
_itemList.CollectionChanged += new NotifyCollectionChangedEventHandler(OnCollectionChanged);
L_PmemCombList.ItemsSource = _itemList;

为了避免刷新完整的 ListView,我将新检索到的列表与当前的 _itemList 进行了简单的比较,更改不相同的项目并在必要时添加/删除项目。集合“newList”包含新创建的对象,因此替换 _itemList 中的项目会正确发送“刷新”通知(我可以使用 ObservableCollection 的事件处理程序OnCollectionChanged 记录该通知)

Action action = () =>
{
    for (int i = 0; i < newList.Count; i++)
    {
        // item exists in old list -> replace if changed
        if (i < _itemList.Count)
        {
            if (!_itemList[i].SameDataAs(newList[i]))
                _itemList[i] = newList[i];
        }
        // new list contains more items -> add items
        else
            _itemList.Add(newList[i]);
     }
     // new list contains less items -> remove items
     for (int i = _itemList.Count - 1; i >= newList.Count; i--)
         _itemList.RemoveAt(i);
 };
 Dispatcher.BeginInvoke(DispatcherPriority.Background, action);

我的问题是,如果在此循环中更改了许多项目,ListView 不会刷新并且屏幕上的数据保持原样......我不明白。

即使是这样的简单版本(交换所有元素)

List<PmemCombItem> newList = new List<PmemCombItem>();
foreach (PmemViewItem comb in combList)
    newList.Add(new PmemCombItem(comb));

if (_itemList.Count == newList.Count)
    for (int i = 0; i < newList.Count; i++)
        _itemList[i] = newList[i];
else
{
    _itemList.Clear();
    foreach (PmemCombItem item in newList)
        _itemList.Add(item);
}

工作不正常

这有什么线索吗?

更新

如果我在更新所有元素后手动调用以下代码,一切正常

OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));

但这当然会导致 UI 更新我仍然想避免的所有内容。

【问题讨论】:

  • 您是否尝试过同步Dispatcher.Invoke 而不是异步Dispatcher.BeginInvoke?
  • 我使用 Dispatcher.Invoke 得到了相同的结果
  • 我们在这里谈论多少项目?您是否尝试过不同的调度程序优先级?
  • 该列表有大约 100 个项目并且在不断变化,例如其中4个工作正常,更改10个没有视觉效果。我尝试了几个优先事项,到目前为止没有改变行为
  • 为了确保您的更新代码没有任何问题,如果您简单地清除整个 _itemList 然后添加来自 newList 的所有项目,它的行为如何?

标签: c# wpf listview


【解决方案1】:

修改后,可以使用如下刷新Listview,更方便

listView.Items.Refresh();

【讨论】:

  • 以及如何使用 MVVM 做到这一点?
  • 如果您实际上已将 ItemsSource 替换为新对象,这似乎不起作用。在这种情况下,我们需要先按照 Xopher 的建议将 ItemsSource 设置为 null,或者更确切地说,按照 Anatolii 的建议,可能会更好。
  • @MatthisKohli 我能想到的唯一方法是让视图模型公开一个视图可以订阅的事件,该事件只需调用上面的代码。也许不理想...
【解决方案2】:

这是我必须做的才能让它工作。

MyListView.ItemsSource = null;
MyListView.ItemsSource = MyDataSource;

【讨论】:

    【解决方案3】:

    我知道这是一个老问题,但我只是偶然发现了这个问题。我真的不想对已更新的字段使用空分配技巧或刷新。

    所以,看了MSDN之后,发现了这篇文章: https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.inotifypropertychanged?redirectedfrom=MSDN&view=netframework-4.7.2

    总而言之,你只需要实现这个接口的item,它就会自动检测到这个对象可以被观察到。

    public class MyItem : INotifyPropertyChanged
    {
        private string status;
    
        public string Status
        {
            get => status;
            set
            {
                OnPropertyChanged(nameof(Status));
                status = value;
            }
        }
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        [NotifyPropertyChangedInvocator]
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    

    因此,每次有人更改 Status 时都会调用该事件。而且,在您的情况下,列表视图将在 PropertyChanged 事件上自动添加一个处理程序。

    这并不能真正解决您的问题(添加/删除)。 但为此,我建议你看看BindingList&lt;T&gt; https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.bindinglist-1?view=netframework-4.7.2

    使用相同的模式,您的列表视图将在不使用任何技巧的情况下正确更新。

    【讨论】:

      【解决方案4】:

      每次可观察集合发生变化时,您不应重置 ItemsSource 或 ListView。只需设置适当的绑定即可。在xaml:

      <ListView ItemsSource='{Binding ItemsCollection}'
       ...
      </ListView>
      

      并且在代码隐藏(建议使用MVVM)属性中将负责持有_itemList:

      public ObservableCollection<PmemCombItem> ItemsCollection
      {
         get 
         {
            if (_itemList == null)
            {
                _itemList = new ObservableCollection<PmemCombItem>();
            }
            return _itemList;
         }
      }
      


      更新: 有类似的帖子很可能会回答您的问题:How do I update an ObservableCollection via a worker thread?

      【讨论】:

      • 我在初始化期间只设置了一次 ItemsSource 属性,当列表发生变化时只执行第二段代码
      • @Danny1075 是的,但这不是应该使用的方式。设置 != 绑定。
      • 尽管这不能回答问题(并且在这里使用绑定与否无关紧要),但为什么要对 ItemsSource 属性进行双向绑定?
      • 对不起,但即使在您编辑之后,这也不能回答问题。是否将 ItemsSource 属性绑定到 ObservableCollection 或直接设置它并不重要。在这两种情况下,对集合的更改都由 ObservableCollection 通知。绑定再也不会起作用了。
      • 添加另一件事:当只有少数项目发生变化时,代码工作正常,ListView 显示新项目。如果在循环中许多项目被更改,则 ListView 根本不会更改
      【解决方案5】:

      我找到了一种方法。它并不是真的那么好,但它确实有效。

      YourList.ItemsSource = null;
      // Update the List containing your elements (lets call it x)
      YourList.ItemsSource = x;
      

      这应该会刷新您的 ListView(它适用于我的 UAP :))

      【讨论】:

      • Xopher 提供的答案的副本
      【解决方案6】:

      Xopher 答案的另一种选择。

      MyListView.ItemsSource = MyDataSource.ToList();
      

      这会刷新 Listview,因为它是另一个列表。

      【讨论】:

        【解决方案7】:

        请检查这个答案: Passing ListView Items to Commands using Prism Library

        列表视图项目需要通知更改(完成是 ​​setter)

        public ObservableCollection<Model.Step> Steps 
        {
            get { return _steps; }
            set { SetProperty(ref _steps, value); }
        } 
        

        并且UpdateSourceTrigger需要在xaml中设置

        <Image Source="{Binding ImageData, UpdateSourceTrigger=PropertyChanged}" />
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2023-04-03
          • 1970-01-01
          • 2012-06-22
          • 2015-12-05
          • 2014-11-02
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多