如何在控件初始化期间防止PropertyChanged触发

4
这个问题已经困扰了一段时间,阻碍了项目的进展。考虑一个 WPF XAML 表单,其中的控件绑定到 ViewModel 上。(我正在使用 Caliburn.Micro MVVM 框架和 Entity Framework 进行数据处理)。一个 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,保存按钮被启用,即使表单刚刚加载并且用户还没有更改任何内容。我错过了什么吗?毫无疑问,这是一个常见的问题,但我一直找不到一个好的解决方案。这是我的第一个MVVM项目,所以很可能我漏掉了一些基本概念。
更新:为了澄清,我认为问题在于我需要能够连接到一个事件或回调,当所有绑定完成更新时触发,这样我就可以连接 PropertyChanged 事件处理程序。

我知道这个问题很久以前就被提出了,你可能已经解决了。我认为我可能有解决方案,但是你所提供的代码示例中没有足够的代码让我确认。你的代码后台是否有处理更改这些属性值的事件(包括XAML中的事件)?如果有,在更新对象的属性之前,你可以检查相关控件的IsLoaded属性以查看绑定是否完成。此外,控件还有一个Loaded事件,在绑定完成时触发。 - user3308241
这种情况下有一个XKCD漫画;redtetrahedron - 你解决了吗?我现在也面临同样的问题.... - andrew
4个回答

0

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

首次进入时,EntityKey值将为null。请检查此项。

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

0

我知道这个问题很古老,但我遇到了完全相同的问题,并且花费了很长时间才解决。我对WPF/MVVM还比较陌生,可能不知道正确的搜索或提问方式。这是我的解决方案,希望能帮助到某些人。

我有一个几乎与原始帖子中相同的IsDirty标志。唯一的区别是我添加了一个命令,在视图完成渲染时将其重置为false。基本思路来自当视图被渲染/实例化时通知ViewModel

在视图中触发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; 
}

0
您可以尝试设置触发更改的属性,方法如下:

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

这样,只有在值实际上发生了改变而不是被多余地设置时,事件才会触发。当然,这假定在您的情况下值实际上没有改变。


明白了。那么,你觉得跟踪实体的 HasChanges 属性比自己尝试跟踪它更好吗? - Grant H.
1
或者,您可以在数据已经异步加载并在其回调方法中添加事件处理程序。 - Grant H.
我不熟悉任何回调方法来告诉我数据何时加载完成...它是在绑定还是实体框架中?谢谢。 - redtetrahedron
应该使用Entity Framework。您可以发布一下您调用EF的代码吗?通常情况下,只要您将对象实例化并且不是惰性加载所有内容,EF调用会同步返回数据。 - Grant H.
我刚刚编辑了我的代码示例,并添加了注释以指出哪一行是EF查询。如果有任何区别,它使用的是ObjectContext的EF 4。 - redtetrahedron
显示剩余3条评论

0
我建议您设计一个通用的方法调用。例如像ValueChanged事件处理程序(自定义事件处理程序),它反过来调用BoundData_PropertyChanged。这意味着,每当控件发生更改时,您都可以调用此ValueChanged方法/处理程序。
例如:
    void ValueChanged(object sender, EventArgs e)
    {
    BoundData_PropertyChanged();
    }

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

语法可能不准确,但你可以猜测我在这里提出了什么。


谢谢,但我不明白这如何防止事件在所有绑定初始化之后才触发。 - redtetrahedron

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接