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

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

竹林之风1个月前 (11-30)技术积累519

前言

在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
分享给朋友:

“.NET Framework WPF中处理DataGrid与ItemsSource不一致问题” 的相关文章

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 Core网站报“An assembly specified in the application dependencies manifest was not found”异常解决

ASP.NET Core网站报“An assembly specified in the application dependencies manifest was not found”异常解决

        ASP.NET Core网站报“An assembly specified in the application dependencies ma...

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

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

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

发表评论

访客

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