当前位置:首页 > 技术积累 > 正文内容

.NET Framework WPF中处理DataGrid与ItemsSource不一致问题

前言

在WPF开发中,ItemsControl(如DataGridListView)经常用来展示绑定的数据集合。然而,很多开发者可能遇到这样的运行时异常:

System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> 
System.InvalidOperationException: An ItemsControl is inconsistent with its items source.

异常信息提示:ItemsControl 的生成器接收到的 CollectionChanged 事件与其当前状态不一致。本文将从问题分析、根本原因以及解决方案三个层面,详细探讨这一问题。

异常分析

通过查看详细的异常栈信息,可以总结出以下关键点:

  1. 异常触发点

    • 触发异常的控件是 System.Windows.Controls.DataGrid

    • 异常与ItemContainerGenerator生成器和CollectionChanged事件有关。

  2. 异常原因

    • 数据源 (ItemsSource) 的 CollectionChanged 事件通知与 ItemsControl 的状态不匹配。

    • WPF 使用虚拟化技术提升性能,控件会依赖 CollectionChanged 来同步 UI 与数据源。

    • 当开发者直接操作数据源集合(如List<T>)而未正确触发事件时,可能导致控件无法正确刷新视图。

  3. 可能的触发条件

    • 数据源在未通知UI的情况下被修改。

    • 多线程操作导致数据源状态不一致。

    • 自定义数据模板或容器时未正确绑定。

    • 数据绑定过程中未遵守WPF的数据通知规则。


典型问题场景

1. 使用普通集合(如List<T>)作为数据源

在WPF中,DataGrid 等控件无法自动检测到普通集合的变化。例如,向 List<T> 添加或删除数据不会自动触发 CollectionChanged 事件。

2. 多线程修改数据源

当 UI 线程绑定了一个集合,而另一个线程直接修改该集合时,可能导致状态不一致。

3. 错误的事件索引

如果开发者手动触发了 CollectionChanged 事件,但提供了错误的索引或参数,也会导致异常。


解决方案

针对以上问题,可以采用以下方法逐步解决。

1. 确保使用ObservableCollection

ObservableCollection<T> 是 WPF 中推荐的数据源集合,因为它实现了 INotifyCollectionChanged 接口,能在集合变化时自动通知 UI。

ObservableCollection<Product> products = new ObservableCollection<Product>();
dataGrid.ItemsSource = products;

// 添加或移除数据时:
products.Add(new Product { Name = "Product A", Price = 10.0 });
products.RemoveAt(0);

切勿直接操作List<T>,否则需要手动更新绑定:

List<Product> products = new List<Product>();
dataGrid.ItemsSource = products;

// 手动刷新绑定(不推荐)
products.Add(new Product { Name = "Product A", Price = 10.0 });
dataGrid.ItemsSource = null;
dataGrid.ItemsSource = products;
2. 多线程安全更新UI

UI操作必须在主线程中完成。如果需要从其他线程修改数据源,建议使用 Dispatcher

Application.Current.Dispatcher.Invoke(() =>
{
    products.Add(new Product { Name = "Product B", Price = 20.0 });
});

也可以借助ObservableCollection的线程安全扩展,如:

public static class ObservableCollectionExtensions
{
    public static void AddItem<T>(this ObservableCollection<T> collection, T item)
    {
        if (Application.Current.Dispatcher.CheckAccess())
        {
            collection.Add(item);
        }
        else
        {
            Application.Current.Dispatcher.Invoke(() => collection.Add(item));
        }
    }
}
3. 开启调试追踪

WPF 提供了调试工具,可以帮助开发者追踪 CollectionChanged 事件,快速定位问题。

在代码中启用以下设置:

System.Diagnostics.PresentationTraceSources.SetTraceLevel(
    dataGrid.ItemContainerGenerator, 
    System.Diagnostics.PresentationTraceLevel.High);

运行后,可以在调试输出中看到详细的事件日志。

4. 优化复杂的绑定逻辑

如果使用了自定义数据模板或多层嵌套绑定,需确保数据模型支持INotifyPropertyChanged接口。

例如,数据模型:

public class Product : INotifyPropertyChanged
{
    private string name;
    public string Name
    {
        get => name;
        set
        {
            name = value;
            OnPropertyChanged(nameof(Name));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

确保每当属性变化时,UI能够正确更新。

案例总结

以下是一个完整的例子,展示了如何正确处理 DataGrid 与其数据源的绑定。

public partial class MainWindow : Window
{
    public ObservableCollection<Product> Products { get; set; }

    public MainWindow()
    {
        InitializeComponent();

        Products = new ObservableCollection<Product>
        {
            new Product { Name = "Product 1", Price = 10.0 },
            new Product { Name = "Product 2", Price = 20.0 }
        };

        dataGrid.ItemsSource = Products;
    }

    private void AddProductButton_Click(object sender, RoutedEventArgs e)
    {
        Products.Add(new Product { Name = "Product 3", Price = 30.0 });
    }

    private void RemoveProductButton_Click(object sender, RoutedEventArgs e)
    {
        if (Products.Any())
        {
            Products.RemoveAt(0);
        }
    }
}

XAML 文件:

<Window x:Class="WpfApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="DataGrid 示例" Height="350" Width="525">
    <Grid>
        <DataGrid x:Name="dataGrid" AutoGenerateColumns="True" Margin="10" />
        <StackPanel Orientation="Horizontal" VerticalAlignment="Bottom" Margin="10">
            <Button Content="添加" Click="AddProductButton_Click" Margin="5" />
            <Button Content="删除" Click="RemoveProductButton_Click" Margin="5" />
        </StackPanel>
    </Grid>
</Window>

总结

本次博客详细剖析了 WPF 中 ItemsControlItemsSource 不一致的常见问题,从异常原因、典型场景到实际解决方案,帮助开发者更好地处理 WPF 数据绑定中的潜在问题。

通过遵循以下最佳实践,可以有效避免类似问题:

  1. 使用 ObservableCollection 替代 List<T>

  2. 在主线程中更新 UI。

  3. 数据模型实现 INotifyPropertyChanged

  4. 启用调试追踪快速定位问题。



扫描二维码推送至手机访问。

版权声明:本文由久爱编程网发布,如需转载请注明出处。

本文链接:https://www.9icode.com/index.php/post/377.html

标签: C#.NETWPF
分享给朋友:

相关文章

SVN源码版本控制目录方案

SVN源码版本控制目录方案

        SVN的目录规划:(1)trunk:主干目录,用于存放当前正在开发和维护中的源码版本,这是一个进行日常开发和维护的主要目录;(2)branche...

ASP.NET中报“无法在已发送HTTP标头之后进行重定向”异常问题解决

ASP.NET中报“无法在已发送HTTP标头之后进行重定向”异常问题解决

        ASP.NET中报“无法在已发送HTTP标头之后进行重定向”异常时,其中一个原因是:在已经重定向后又重定向。在ASP.NET中实现重定向有以下几...

ASP.NET中报“无法在已发送HTTP标头之后设置状态”异常问题解决

ASP.NET中报“无法在已发送HTTP标头之后设置状态”异常问题解决

        ASP.NET中报“无法在已发送HTTP标头之后设置状态”异常,是因为设置Response的StatusCode之前,程序已设置响应标头。可从以...

ASP.NET网站自定义错误处理及其它安全相关

ASP.NET网站自定义错误处理及其它安全相关

        ASP.NET网站在运行过程中总是有可能报错,例如404等HTTP错误、500等程序异常。在IIS托管并报错的情况下,网站的默认行为依次是:(1).NET运行时接收并处理的部分显示.N...

从被扫描记录看网站安全应该注意的一些细节

从被扫描记录看网站安全应该注意的一些细节

1、尽可能地隐藏服务器真实IP,减少攻击目标。例如使用负载均衡、网关等作为门户,由负载均衡或网关转发到后端服务器。2、服务器必须开放的服务(除HTTP、HTTPS等为公众开放的服务外),尽可能改为其它较大的不常用的端口号,这样攻击者需要扫描...

ASP.NET MVC WebApi控制器方法使用async假死超时问题

ASP.NET MVC WebApi控制器方法使用async假死超时问题

        在一个ASP.NET MVC WebApi控制器方法中使用async标记为异步方法后,有时会假死超时。根本原因是同步方法和异步方法混合造成的死锁...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。