【问题标题】:WPF INotifyPropertyChanged and INotifyCollectionChanged not being called未调用 WPF INotifyPropertyChanged 和 INotifyCollectionChanged
【发布时间】:2020-12-18 02:26:44
【问题描述】:

我已经为我的 DataGrid 中的各个项目实现了 INotifyCollectionChanged(可能不正确)和 INotifyPropertyChanged。 一旦 Window 被渲染(ContentRendered="Window_ContentRendered" ),我会修改数据以查看 DataGrid 是否得到更新。 我有 2 个问题。

  1. 当我调用“priceLadderData.data.Values[0].ChangePrice(1);”时我可以看到 OnPropertyChanged 被击中,但没有可见的变化。 2.当我调用“priceLadderData.AddRow(new PriceLadderRowData(99, 99));”时我收到以下错误:

例外:开发人员信息(使用文本可视化工具阅读 this): 抛出这个异常是因为生成器用于控制 'System.Windows.Controls.DataGrid Items.Count:5' 名称为 'dataGrid' 已收到不同意的 CollectionChanged 事件序列 与 Items 集合的当前状态。以下 检测到差异:累计计数 4 与 实际计数 5. [累计计数为 (Count at last Reset + #Adds - #Removes since last Reset)。] 在索引 0:生成器的项目 '[364525, WPF_PriceLadder.PriceLadderRowData]' 与实际项目不同 '[99,WPF_PriceLadder.PriceLadderRowData]'。在索引 1:发电机的 项目“[364550,WPF_PriceLadder.PriceLadderRowData]”不同于 实际项目“[364525,WPF_PriceLadder.PriceLadderRowData]”。在索引 2:生成器的项目 '[364575, WPF_PriceLadder.PriceLadderRowData]' 是 与实际项目不同'[364550, WPF_PriceLadder.PriceLadderRowData]'。 (……还有 1 个实例……)

以下一个或多个来源可能引发了错误事件: System.Windows.Controls.ItemContainerGenerator System.Windows.Controls.ItemCollection MS.Internal.Data.EnumerableCollectionView * System.Collections.Generic.SortedList`2[[System.Single, System.Private.CoreLib,版本=5.0.0.0,文化=中性, PublicKeyToken=7cec85d7bea7798e],[WPF_PriceLadder.PriceLadderRowData, WPF_Console2,版本=1.0.0.0,文化=中性,PublicKeyToken=null]] (加星标的来源被认为更有可能是导致 问题。)

最常见的原因是 (a) 更改集合或其计数 不引发相应的事件,并且 (b) 引发事件 不正确的索引或项目参数。

异常的堆栈跟踪描述了不一致是如何产生的 检测到,而不是它们是如何发生的。要获得更及时的异常,请设置 附加属性“PresentationTraceSources.TraceLevel” 生成器将值设置为“高”并重新运行场景。一种方法来做到这一点 是运行类似于以下的命令:\n
System.Diagnostics.PresentationTraceSources.SetTraceLevel(myItemsControl.ItemContainerGenerator, System.Diagnostics.PresentationTraceLevel.High) 从即时 窗户。这会导致检测逻辑在每个 CollectionChanged 事件,因此它会降低应用程序的速度。

MainWindow.xaml:

<Window x:Class="WPF_PriceLadder.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WPF_PriceLadder"
        mc:Ignorable="d"
        ContentRendered="Window_ContentRendered" 
        Title="MainWindow" Height="450" Width="400">

    <Grid>
        <DataGrid x:Name="dataGrid" 
                  HorizontalScrollBarVisibility="Hidden"
                  VerticalScrollBarVisibility="Hidden" 
                  IsManipulationEnabled="False" 
                  IsReadOnly="True"
                  AllowDrop="False"
                  CanUserAddRows="False"
                  CanUserDeleteRows="False"
                  EnableColumnVirtualization="True"
                  CanUserReorderColumns="False"
                  CanUserSortColumns="False"
                  CanUserResizeRows="False" 
                  SelectionMode="Single"
                  FontWeight="Normal"
                  AutoGenerateColumns="False"
                  SelectionUnit="Cell"
                  >

            <DataGrid.Columns>
                <DataGridTextColumn Header="Price" Binding="{Binding Value.Price, UpdateSourceTrigger=PropertyChanged,NotifyOnSourceUpdated=True}">
                </DataGridTextColumn>
    
                <DataGridTextColumn Header="Volume" Binding="{Binding Value.Volume,UpdateSourceTrigger=PropertyChanged}">
                </DataGridTextColumn>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

MainWindow.xaml.cs:

using System;
using System.Collections.Generic;
using System.Windows;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Collections.Specialized;

namespace WPF_PriceLadder
{
public partial class MainWindow : Window
{
    public PriceLadderData priceLadderData;

    public MainWindow()
    {
        priceLadderData = new PriceLadderData();
        FillUpWithDummyRows(priceLadderData);
        this.DataContext = priceLadderData;
        InitializeComponent();
        dataGrid.ItemsSource = priceLadderData.data;
    }

    public void FillUpWithDummyRows(PriceLadderData priceLadderData)
    {
        priceLadderData.AddRow(new PriceLadderRowData(364600, 37));
        priceLadderData.AddRow(new PriceLadderRowData(364575, 18));
        priceLadderData.AddRow(new PriceLadderRowData(364550, 30));
        priceLadderData.AddRow(new PriceLadderRowData(364525, 20));
    }

    public void Window_ContentRendered(object sender, EventArgs e)
    {
        priceLadderData.data.Values[0].ChangePrice(1);
        priceLadderData.AddRow(new PriceLadderRowData(99, 99));
    }
}

public class PriceLadderRowData : IComparable, INotifyPropertyChanged
{

    private float price;
    private int volume = 0;
    public event PropertyChangedEventHandler PropertyChanged;

    public float Price
    {
        get
        {
            return price;
        }
        private set
        {
            price = value;
        }
    }
    public int Volume
    {
        get
        {
            return volume;

        }
        private set
        {
            volume = value;

        }
    }

    public PriceLadderRowData(float price, int volume)
    {
        this.Price = price;
        this.Volume = volume;
    }

    public int CompareTo(object obj)
    {
        return Price.CompareTo(obj);
    }

    protected void OnPropertyChanged([CallerMemberName] string name = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    }
    public void ChangePrice(float price)
    {
        this.Price = price;
        OnPropertyChanged();
    }
}

public class PriceLadderData : INotifyCollectionChanged
{
    public event NotifyCollectionChangedEventHandler CollectionChanged;
    public SortedList<float, PriceLadderRowData> data = new SortedList<float, PriceLadderRowData>();

    public PriceLadderData()
    {

    }

    protected void OnCollectionChanged([CallerMemberName] string name = null)
    {
        CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add));
    }

    public void AddRow(PriceLadderRowData priceLadderRowData)
    {
        this.data.Add(priceLadderRowData.Price, priceLadderRowData);
        OnCollectionChanged();
    }
}

}

这里有什么问题吗?还没有键盘坏过,但我在学习 WPF 和 XAML 时几次接近它,所以我真的很感谢你的帮助。

【问题讨论】:

  • 为什么你发明了自己的东西而不是仅仅使用 observablecollection?我建议您在学习时首先使用开箱即用的东西。我还建议您使用 mainwindowviewmodel 作为主窗口的 datacontect 并绑定 itemssource 而不是设置它。此外,删除 UpdateSourceTrigger=PropertyChanged,用户无法输入任何内容,因此毫无意义。

标签: c# wpf xaml


【解决方案1】:

问题 1. 价格更新。

当您更改价格时,您在 ChangePrice() 函数中调用 OnPropertyChanged()。但是 OnPropertyChanged 中使用的“[CallerMemberName]”将调用函数的名称作为属性名称,在本例中为“ChangePrice”。但是属性名称是“价格”。 通常在 Price 属性的 setter 中调用 OnPropertyChanged:

   public float Price
    {
        get
        {
            return price;
        }
        private set
        {
            price = value;
            OnPropertyChanged();
        }
    }

问题 2. 排序列表。

我同意其他所有人的观点。请改用 ObservableCollection。有很多功能适用于 XAML。

但你希望它排序。 在这里,我更改了 PriceLadderData 类来处理这个问题: (纯粹主义者可能会说您应该在 XAML 中执行此操作,并且可以,但这里是在代码中。)

public class PriceLadderData
{
    public ObservableCollection<PriceLadderRowData> data { get; } = new ObservableCollection<PriceLadderRowData>();

    public CollectionView dataView { get; private set; }

    public PriceLadderData()
    {
        dataView = (CollectionView)CollectionViewSource.GetDefaultView(data); 
        dataView.SortDescriptions.Add(new SortDescription(nameof(PriceLadderRowData.Price), ListSortDirection.Ascending));
    }

    public void AddRow(PriceLadderRowData priceLadderRowData)
    {
        this.data.Add(priceLadderRowData);
    }

    public void Refresh()
    {
    //Refresh the sort order
        dataView.Refresh();
    }
}

评论: 添加了一个 CollectionView (dataView),它将是绑定到 DataGrid 的数据的排序视图。

在 PriceLadderData 的构造函数中设置 dataView 的排序指令。

在 XAML 中将 dataView 绑定到 DataGrid:

    <DataGrid x:Name="dataGrid" ItemsSource="{Binding dataView}"

(去掉MainWindow构造函数中ItemsSource的设置)

如果要添加数据或更改排序键,则必须刷新排序:

    public void Window_ContentRendered(object sender, EventArgs e)
    {
        priceLadderData.data[0].ChangePrice(1);
        priceLadderData.AddRow(new PriceLadderRowData(99, 99));
        priceLadderData.Refresh();
    }

您还可以在数据上设置 CollectionChanged 事件,但是当您只更改属性时,这将无法处理这种情况。

如果您想更改 DataGrid 中的数据,您需要从 DataGrid 中删除“IsReadOnly=True”并将其设置在各个列上。您还需要删除属性设置器上的“私有”。

【讨论】:

  • 非常感谢@RolandJS。几天来,我一直试图弄清楚这一点。 :-)
【解决方案2】:

您对NotifyCollectionChangedEventArgs() 的使用不正确;您需要使用采用 newItemsoldItems 的重载并填充它们。

此外,您正在通过公共 data 字段公开您(应该)封装的集合。这会让你走上一条坏路;它已经掩盖了您调用 NotifyCollectionChangedEventArgs 构造函数的方式的问题。如果您的代码中有类似 x.y.z.w() 的调用,则说明您做错了。

【讨论】:

    猜你喜欢
    • 2010-11-11
    • 2010-11-13
    • 2011-08-13
    • 2018-11-29
    • 2011-12-17
    • 2014-06-02
    • 1970-01-01
    • 2011-06-25
    • 2012-08-18
    相关资源
    最近更新 更多