连接 CollectionChanged 和 PropertyChanged (或者:为什么某些 WPF 绑定不刷新?)

2024-04-17

WPF DataBindings 曾经让我很开心。我刚才偶然发现的一件事是,在某些时候它们只是没有按预期刷新。请看下面(相当简单)的代码:

<Window x:Class="CVFix.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="300">
  <Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition></ColumnDefinition>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition></RowDefinition>
        <RowDefinition Height="40"></RowDefinition>
    </Grid.RowDefinitions>
    <ListBox Grid.Row="0" Grid.Column="0" 
                  ItemsSource="{Binding Path=Persons}"
                  SelectedItem="{Binding Path=SelectedPerson}"
                  x:Name="lbPersons"></ListBox>
    <TextBox Grid.Row="1" Grid.Column="0" Text="{Binding Path=SelectedPerson.Name, UpdateSourceTrigger=PropertyChanged}"/>
  </Grid>
</Window>

XAML 背后的代码:

using System.Windows;
namespace CVFix
{
  /// <summary>
  /// Interaction logic for MainWindow.xaml
  /// </summary>
  public partial class MainWindow : Window
  {
    public ViewModel Model { get; set; }

    public MainWindow()
    {
        InitializeComponent();
        this.Model = new ViewModel();
        this.DataContext = this.Model;
    }
  }
}

最后,这是 ViewModel 类:

using System.Collections.ObjectModel;
using System.ComponentModel;

namespace CVFix
{
  public class ViewModel : INotifyPropertyChanged
  {
    private PersonViewModel selectedPerson;

    public PersonViewModel SelectedPerson
    {
        get { return this.selectedPerson; }
        set
        {
            this.selectedPerson = value;

            if (this.PropertyChanged != null)
                this.PropertyChanged(this, new PropertyChangedEventArgs("SelectedPerson"));
        }
    }

    public ObservableCollection<PersonViewModel> Persons { get; set; }

    public ViewModel()
    {
        this.Persons = new ObservableCollection<PersonViewModel>();
        this.Persons.Add(new PersonViewModel() { Name = "Adam" });
        this.Persons.Add(new PersonViewModel() { Name = "Bobby" });
        this.Persons.Add(new PersonViewModel() { Name = "Charles" });
    }

    public event PropertyChangedEventHandler PropertyChanged;
  }
}

public class PersonViewModel : INotifyPropertyChanged
{
    private string name;

    public string Name
    {
        get { return this.name; }
        set
        {
            this.name = value;
            if(this.PropertyChanged != null)
            this.PropertyChanged(this, new PropertyChangedEventArgs("Name"));
        }
    }

    public override string ToString()
    {
        return this.Name;
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

我想要发生的情况:当我从列表框中选择一个条目并在文本框中修改其名称时,列表会更新以显示新值。

发生了什么:什么也没有。如果我有任何判断的话,这就是正确的行为。 我确保 SelectedItem 的 PropertyChanged 被触发,但这(当然)不会导致 CollectionChanged 被触发。

为了解决这个问题,我创建了一个 ObservableCollection 派生类,它具有公共 OnCollectionChanged 方法,请参见此处:

public class PersonList : ObservableCollection<PersonViewModel>
{
    public void OnCollectionChanged()
    {
        this.OnCollectionChanged(new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Reset ));
    }
}

我从 ViewModel 的构造函数访问它,如下所述:

    public ViewModel()
    {
        PersonViewModel vm1 = new PersonViewModel()
        {
            Name = "Adam"
        };
        PersonViewModel vm2 = new PersonViewModel()
        {
            Name = "Bobby"
        };
        PersonViewModel vm3 = new PersonViewModel()
        {
            Name = "Charles"
        };
        vm1.PropertyChanged += this.PersonChanged;

        this.Persons = new PersonList();


        this.Persons.Add(vm1);
        this.Persons.Add(vm2);
        this.Persons.Add(vm3);
    }

    void PersonChanged(object sender, PropertyChangedEventArgs e)
    {
        this.Persons.OnCollectionChanged();
    }

它有效,但不是一个干净的解决方案。我的下一个想法是创建 ObservableCollection 的派生类,它在 CollectionChanged 处理程序中自动进行连接。

public class SynchronizedObservableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Add:
                {
                    foreach (INotifyPropertyChanged item in e.NewItems)
                    {
                        item.PropertyChanged += this.ItemChanged;
                    }
                    break;
                }

            case NotifyCollectionChangedAction.Remove:
                {
                    foreach (INotifyPropertyChanged item in e.OldItems)
                    {
                        item.PropertyChanged -= this.ItemChanged;
                    }
                    break;
                }
        }
        base.OnCollectionChanged(e);
    }

    void ItemChanged(object sender, PropertyChangedEventArgs e)
    {
        this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }
}

问题是:有更好的方法吗?这真的有必要吗?

预先非常感谢您的任何意见!


不,完全没有必要。您的样品失败的原因很微妙,但很简单。

如果您没有为 WPF 提供数据项的模板(例如Person列表中的对象),它将默认使用ToString()方法来显示。这是一个成员,而不是一个属性,因此当值更改时您不会收到任何事件通知。

如果你添加DisplayMemberPath="Name"添加到您的列表框,它将生成一个正确绑定到的模板Name您个人的信息 - 然后将按照您的预期自动更新。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

连接 CollectionChanged 和 PropertyChanged (或者:为什么某些 WPF 绑定不刷新?) 的相关文章

  • 如何在 XAML 中定义变量?

    我在 XAML 中有以下两个按钮
  • 在 StackPanel 中拉伸文本框

    这是我当前用来执行此操作的 XAML 并且我一生都无法弄清楚如何扩展文本框以填充整个列 有人可以指导我正确的方向吗 先感谢您
  • 使用 MVVM 为 WPF 搭建脚手架?

    谁能告诉我是否可以 如果存在某些实用程序 创建考虑到 MVVM 和 WPF 的脚手架 我的想法是我似乎有很多数据访问表单 当然我想自定义它们 但最初我想要一种快速提供表单的方法 即放置在它们上面并绑定到 mvvm 的所有控件 也许我问得很多
  • 在运行时拖动窗体上的控件

    我刚刚开始使用 WPF 但我正在尝试添加我的代码 来自 Winforms 使用户能够在运行时将任何控件拖动到他们想要的任何位置 但我似乎无法获取鼠标当前的位置 呃 没有鼠标位置 在 Mouse 事件中 您可以使用 e GetPosition
  • 限制文本框中每行的最大字符数

    假设我有以下内容
  • MVVM 中的事件而不是命令?

    在MVVM的各种教程中经常会指出 MVVM的目标不是消除代码隐藏 并且代码隐藏中的一些事件处理可能仍然是必要的 在什么场景下您需要在代码隐藏中编写事件而不是在视图模型中使用命令 一般来说 如果您的代码与 UI 逻辑相关 请将其保留在视图的
  • WPF 向我的 GUI 添加时钟

    简单请求 我希望能够在 WPF 应用程序窗口中显示当前时间 有免费的控件吗 只需要显示时间 没有别的 您可以有一个标签或文本块 并将其内容绑定到 System DateTime Now
  • WPF 与分辨率无关

    如果我将所有内容放入 viewbox 容器中 那么我的 wpf 应用程序将与分辨率无关 或者我需要执行其他操作吗 请帮助概念 根据可用屏幕或中等尺寸缩放 元素如果您希望始终填充屏幕或输出设备的某些空间 而与指标无关 那么使用视图框是一个不错
  • 如何在不更改内容字体的情况下更改TabItem的标题字体?

    如何更改 TabItem 标题中的字体而不更改内容的字体 当我设置FontSize财产在TabItem它还改变了 FontSizeTextBlocks
  • 带有 TextWrapping 的 WPF CheckBox 样式

    我需要申请一个TextWrapping在 WPF 中CheckBox 请看这两个示例
  • 如何获取 WPF 用户控件可见部分的大小?

    我有一个由标签和文本框组成的用户控件 它位于滚动查看器内 我正在其顶部绘制一个装饰器 并且需要将装饰器的大小调整为控件的可见大小 如何获得控件的可见大小 在下图中 绿色矩形是装饰器 正如您所看到的 它被绘制在右侧的滚动条上 是否可以获得渲染
  • 在 WPF 字体大小和“标准”字体大小之间转换

    我注意到在 WPF 中 12 磅的默认字体大小大致相当于 普通 应用程序 例如写字板 中的 9 磅 WPF 中的 10 磅大约是 7 磅标准 当我尝试匹配默认字体大小时在WPF写字板中的10 pt中 我发现13是最接近的 首先 为什么WPF
  • 创建新视图时如何初始化视图模型中的属性?

    我有一个应用程序 可以打开一个视图 允许您搜索数据 然而 为了进行搜索 用户必须选择他想要在什么类别下进行搜索 目前 我正在尝试弄清楚如何将所选类别从主视图模型 作为 int 传递到新搜索视图的视图模型 目前我正在尝试在主视图中使用类似的东
  • 如何使用 Kinect 追踪一个人 (trackingID)

    我想跟踪第一个人 并使用这个人的右手在我制作的应用程序中导航 我可以接管光标 现在我只想跟踪一个人 因此 基本上 当一个人在程序中导航时 有人走在他身后或与这个人一起看 如果他们移动 kinect 不应该识别其他任何人 我怎样才能实现这个
  • 以编程方式将内容添加到滚动查看器,滚动条停止工作

    好吧 我不太熟悉强大的 WPF 但我尝试了一个有趣的项目来跳入其中 我制作了一个简单的 RSS ATOM 提要查看器 它从 RRS 或 ATOM 提要中提取 HTML 并将其粘贴到浏览器控件中 该控件添加到堆栈面板中 这是 ScrollVi
  • 强制加载 WPF 视觉对象的适当方法

    我一直在努力使用打印System Printing http msdn microsoft com en us library system printing aspx命名空间 我终于发现 在使用部分 API 时得到空白结果的原因是Visu
  • WPF - 从 UserControl 发出命令时 CanExecute 不会触发

    我有一个按钮条用户控件 我想在我的大多数表单上使用它 我添加了如下命令 public ICommand Create get return buttonCreate Command set buttonCreate Command valu
  • 对话框结果 WPF

    我正在读一本书 上面写着 而不是设置 DialogResult 用户点击后手动 按钮 您可以将按钮指定为 接受按钮 通过设置 是默认为true 点击那个 按钮自动设置 窗口的DialogResult为true 同样 你可以指定一个按钮 作为
  • WPF:如何以不同的方向书写文本?

    我需要按照下图指定的方向写入文本 事实上 我在这里看到了一些使用文本块并使用 RenderTransform 旋转控件角度的示例 但这不是我真正需要的 我尝试使用图像来做到这一点 但它不太适合 所以我真的不知道如何解决它 如果您查看旁边的图
  • Window.AllowsTransparent 设置为 true 时 wpf 中的运行时错误

    当我设置时 我在运行时抛出异常AllowsTransparency True 我得到一个例外 说WindowStyle不能设置为None if AllowsTransparency设置为 true 即使我明确地说WindowStyle被设定

随机推荐