简体   繁体   English

如何在初始化控件时阻止PropertyChanged触发

[英]How to prevent PropertyChanged from firing while controls are being initialized

This problem has been causing headaches for a while, and it is preventing the project from going forward. 这个问题已经引起了一段时间的麻烦,并且阻止了项目的进展。 Consider a WPF XAML form with controls bound to a ViewModel. 考虑一个WPF XAML表单,其中包含绑定到ViewModel的控件。 (I'm using the Caliburn.Micro MVVM framework and Entity Framework for the data). (我正在使用Caliburn.Micro MVVM框架和Entity Framework来获取数据)。 An Initialize() method is called by the shell to load the form's data from the database and set up PropertyChanged event handlers. shell调用Initialize()方法从数据库加载表单的数据并设置PropertyChanged事件处理程序。 There is an IsDirty flag that tracks whether there is changed data in the form. 有一个IsDirty标志,用于跟踪表单中是否有更改的数据。 There is a "Save" button bound to the IsDirty property so that it is enabled when the data has changed. 有一个“保存”按钮绑定到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");
    }
}

The problem is that the BoundData_PropertyChanged event handler is hit due to the form being initialized from the database AFTER the Initialize() method is finished. 问题是,由于在Initialize()方法完成后从数据库初始化表单,因此BoundData_PropertyChangedBoundData_PropertyChanged事件处理程序。 So, the IsDirty flag is set to true, and the Save button is enabled, even though the form has just loaded and the user has not changed anything. 因此, IsDirty标志设置为true,并且启用了“保存”按钮,即使表单刚刚加载且用户没有更改任何内容。 What am I missing? 我错过了什么? Surely this is a common problem but I have been unable to find a good solution. 当然这是一个常见的问题,但我一直无法找到一个好的解决方案。 This is my first MVVM project so it is entirely possible I am missing some basic concept. 这是我的第一个MVVM项目,所以我完全有可能错过一些基本概念。

UPDATE: To clarify, I believe the issue is that I need to be able to hook into an event or callback that will fire when all of the bindings are done updating, so I can then wire up the PropertyChanged event handlers. 更新:为了澄清,我认为问题是我需要能够挂钩一个事件或回调,当所有绑定都完成更新时将触发,因此我可以连接PropertyChanged事件处理程序。

One thing that you can do that might help is to set up the property triggering the change like so: 您可以做的一件事可能有帮助,就是设置触发更改的属性,如下所示:

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

That way, the event will only trigger if the value was actually changed, and not just set redundantly. 这样,只有在实际更改了值时才会触发事件,而不仅仅是冗余设置。 This of course assumes that the value is not actually changing in your case. 这当然假设在您的情况下该值实际上没有变化。

I propose you to design a generic method call. 我建议你设计一个通用的方法调用。 Ie something like ValueChanged event handler (custom event handler), which in turn call the BoundData_PropertyChanged. 就像ValueChanged事件处理程序(自定义事件处理程序),它反过来调用BoundData_PropertyChanged。 This means, you can call this ValueChanged method/handler whenever there is a change in control. 这意味着,只要控件发生更改,就可以调用此ValueChanged方法/处理程序。

Eg 例如

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

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

Syntax may not be accurate but you can guess what I am proposing here. 语法可能不准确,但你可以猜到我在这里提出的建议。

This solution may not be infallible, but it is working for me in the limited test cases I have utilised. 这个解决方案可能不是绝对可靠的,但它在我使用的有限测试用例中对我有用。

On first entry the EntityKey value will be null. 在第一次输入时,EntityKey值将为null。 Check for this. 检查一下。

/// <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()));
}

I know this question is ancient, but I had the exact same issue and had a very hard time figuring it out. 我知道这个问题很古老,但我遇到了同样的问题,很难搞清楚。 I'm pretty new to WPF/MVVM and probably didn't know the right things to Google or ask. 我是WPF / MVVM的新手,可能不知道谷歌或者问的正确的事情。 Here's my solution. 这是我的解决方案。 Hopefully it helps somebody. 希望它可以帮助某人。

I have an IsDirty flag almost identical to the one in the original post. 我的IsDirty标志几乎与原帖中的标志相同。 The only difference is that I added a command to reset it to false when the view finishes rendering. 唯一的区别是我在视图完成渲染时添加了一个命令将其重置为false。 The basic idea came from Notify ViewModel when View is rendered/instantiated 当呈现/实例化View时 ,基本思想来自Notify ViewModel

Fire a Loaded event in the View: 在视图中触发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>

Then in the view model set the IsDirty flag to false when the event fires. 然后在视图模型中,在事件触发时将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; 
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM