【问题标题】:How to prevent PropertyChanged from firing while controls are being initialized如何防止在初始化控件时触发 PropertyChanged
【发布时间】:2013-05-19 08:41:31
【问题描述】:

这个问题已经让人头疼了一段时间,它阻碍了项目的推进。考虑一个 WPF XAML 表单,其中控件绑定到 ViewModel。 (我正在使用 Caliburn.Micro MVVM 框架和实体框架来处理数据)。 Shell 调用 Initialize() 方法从数据库加载表单数据并设置 PropertyChanged 事件处理程序。有一个IsDirty 标志来跟踪表单中是否有更改的数据。 IsDirty 属性绑定了一个“保存”按钮,以便在数据更改时启用它。

// Sample code; forms have many controls....

// this is the property that the controls are bound to
public Entity BoundData { get; set; }

public void Initialize()
{
    // this is an example line where I query the database from the Entity Framework ObjectContext...
    BoundData = objectContext.DataTable.Where(entity => entity.ID == 1).SingleOrDefault();

    // this is to cause the form bindings to retrieve data from the BoundData entity
    NotifyOfPropertyChange("BoundData");

    // wire up the PropertyChanged event handler
    BoundData.PropertyChanged += BoundData_PropertyChanged;

    IsDirty = false;
}

void BoundData_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    IsDirty = true;
}

// implementation of the IsDirty flag
public bool IsDirty
{
    get
    {
        return _isDirty;
    }
    set
    {
        _isDirty = value;
        NotifyOfPropertyChange("IsDirty");
    }
}

问题是BoundData_PropertyChanged 事件处理程序由于Initialize() 方法完成后从数据库初始化的表单而被命中。因此,IsDirty 标志设置为 true,并且启用了 Save 按钮,即使表单刚刚加载并且用户没有更改任何内容。我错过了什么?当然,这是一个常见问题,但我一直无法找到一个好的解决方案。这是我的第一个 MVVM 项目,所以我完全有可能遗漏了一些基本概念。

更新: 澄清一下,我认为问题在于我需要能够挂钩在所有绑定完成更新时将触发的事件或回调,因此我可以连接启动 PropertyChanged 事件处理程序。

【问题讨论】:

  • 我知道这个问题很久以前就被问过了,你现在可能已经解决了。我认为我可能对您的问题有解决方案,但是您提供给我的代码示例中没有足够的代码来确定。您是否有任何在代码隐藏中处理的事件会更改这些属性的值(包括 XAML 中的事件)?如果是这样,那么您可以在更新对象中的属性之前检查相关控件的 IsLoaded 属性以查看绑定是否已完成。还有一个 Loaded 事件,用于在绑定完成时触发的控件。
  • 有一个关于这种情况的XKCD卡通; redtetrahedron - 你解决了吗?我现在面临同样的问题....

标签: c# wpf mvvm caliburn.micro


【解决方案1】:

您可以做的一件事可能会有所帮助,那就是设置触发更改的属性,如下所示:

    public virtual bool Prop1
    {
       get
       {
            return _prop1;
       }
       set
       {
            if (_prop1 != value)
            {
                _prop1 = value;
                NotifyOfPropertyChange("IsDirty");
            }
       }

这样,事件只会在值实际更改时触发,而不仅仅是冗余设置。这当然假设您的情况下该值实际上没有改变。

【讨论】:

  • 实体框架生成的实体中的属性实际上确实有 if's 保护设置器,以便它们仅在更改时触发。关键是表单正在初始化,所以所有字段的值都在变化……它们一开始没有任何价值。
  • 明白了。那么如何跟踪实体的HasChanges 属性而不是尝试自己跟踪呢?
  • 或者,您只能在数据在其回调方法中异步加载后添加事件处理程序。
  • 我不熟悉任何回调方法来告诉我数据何时完成加载...是在绑定上还是在实体框架中?谢谢
  • 应该在实体框架中。您可以在进行 EF 调用的地方发布代码吗?通常,只要您实现对象而不是延迟加载所有内容,EF 调用就会同步返回数据。
【解决方案2】:

我建议你设计一个通用的方法调用。即类似于ValueChanged 事件处理程序(自定义事件处理程序),它又调用BoundData_PropertyChanged。这意味着,只要控件发生更改,您就可以调用此 ValueChanged 方法/处理程序。

例如

    void ValueChanged(object sender, EventArgs e)
    {
    BoundData_PropertyChanged();
    }

   textBox.TextChanged += new System.EventHandler(ValueChanged);

语法可能不准确,但您可以猜到我在这里提出的建议。

【讨论】:

  • 谢谢,但在所有绑定都初始化之前,我看不出这是如何阻止事件触发的。
【解决方案3】:

这个解决方案可能不是万无一失的,但它在我使用的有限测试用例中对我有用。

在第一次输入时,EntityKey 值将为空。检查一下。

/// <summary>
/// Log the invoice status change
/// </summary>
/// <param name="value">The value that the invoice status is changing to</param>
partial void OnInvoiceStatusValueChanging(string value)
{
    var newStatus = ConvertInvoiceStatus(value);
    if (this.EntityKey != null && InvoiceStatus != newStatus)
    {
        AddNewNote(string.Format("Invoice status changing from [{0}] to [{1}]", InvoiceStatus.GetDescription(), newStatus.GetDescription()));
    }
}

/// <summary>
/// Log the invoice status change
/// </summary>
partial void OnInvoiceStatusValueChanged()
{
    if (this.EntityKey != null)
        AddNewNote(string.Format("Invoice status changed to [{0}]", InvoiceStatus.GetDescription()));
}

【讨论】:

    【解决方案4】:

    我知道这个问题很古老,但我遇到了完全相同的问题并且很难弄清楚。我对 WPF/MVVM 很陌生,可能不知道谷歌或问的正确事情。这是我的解决方案。希望它可以帮助某人。

    我有一个 IsDirty 标志,与原始帖子中的标志几乎相同。唯一的区别是我添加了一个命令,在视图完成渲染时将其重置为 false。基本思路来自Notify ViewModel when View is rendered/instantiated

    在视图中触发 Loaded 事件:

    <UserControl x:Class="MyUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Loaded">
            <i:InvokeCommandAction Command="{Binding Path=OnLoadedCommand}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
    

    然后在事件触发时在视图模型中将 IsDirty 标志设置为 false。

    public ICommand OnLoadedCommand { get; private set; }
    
    // Constructor
    public MyUserControlViewModel()
    {
        OnLoadedCommand = new DelegateCommand(OnLoaded);
    }
    
    public void OnLoaded()
    {
      // Ignore any PropertyChanged events that fire 
      // before the UserControl is rendered.  
       IsDirty = false; 
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-01-07
      • 2017-04-28
      • 1970-01-01
      • 2013-12-10
      • 2022-11-16
      • 2015-08-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多