允许用户仅在数据更改时保存的更好方法

本文关键字:保存 方法 更好 数据 许用户 用户 | 更新日期: 2023-09-27 18:18:28

我之前所做的是对数据对象进行深度复制,然后编写一个通用的比较方法,该方法使用反射器比较两个对象之间是否存在差异。

假设我有一个SaveButton,一个TextBoxA和ViewModel绑定。PropertyA, initial PropertyA is = "123".

当用户在TextBoxA中输入"1234"时,PropertyA set方法将执行比较方法来查找差异。并启用保存按钮。

但是当用户将文字"1234"改回"123"时,保存按钮将再次失效。

1年后,现在我想知道是否有更好或更简单的方法来做到这一点?也就是说,有什么框架可以做这种事情吗?我不需要写代码来深度复制对象,自己写比较方法?


我拥有的实际UI并不是那么简单,只包含TextBox类型,这是一个用于编辑客户信息的UI,因此有DateTime, Collection等。这就是为什么我写了深度复制方法来克隆整个对象。

允许用户仅在数据更改时保存的更好方法

假设您的视图模型上的这些属性正在以某种方式引发PropertyChanged事件,因为问题被标记为MVVM

这里有一个方法。为ViewModel的PropertyChanged事件编写一个事件处理程序。只有当属性发生变化时,才将原始值保存在私有Dictionary<string, string>中。这就避免了为了防止有人进行编辑而复制整个对象的需要。如果属性已经存在于字典中,那么您可以很容易地确定它是否被更改回其原始值。

编辑:哦,我认为PropertyChangedEventArgs包含新的和旧的值,但它没有。因此,为了做到这一点,你需要在视图模型的属性设置器中添加一些额外的方法调用,可以计算每个属性的旧值和新值。

为了方便地设置启用和禁用保存按钮,在视图模型中应该有一个bool属性,您可以将保存按钮的启用属性绑定到该属性。

如果每当新值与原始值匹配时,项就从字典中删除,那么如果字典包含任何项,则Save按钮启用属性可以返回true。

编辑2:对于集合类型,您希望将视图绑定到视图模型上的ObservableCollection属性。Collection changed事件确实为您提供了一个旧项和新项的列表,因此在该事件处理程序中跟踪更改应该相当容易。

如果ViewModel是你自己的对象,你可以修改它,实现iclonable接口,这样你就可以复制它。

接下来在其上实现IComparable接口,其中T是视图模型。所以比较

很容易

那么我想你必须为所有属性创建一个PropertyChanged事件,当一个被触发时进行比较。

我猜它与你现在已经拥有的基本相同,但如果你基于ICloneableIComparable编写逻辑,至少你只需要编写一次

如果你只是不想编写自己的比较方法,有一些代码片段可以自动比较所有的属性,比如这篇文章。然而,使用这样的东西比编写自己的比较函数要慢得多(性能方面)。

听起来您希望将状态管理与属性更改通知相结合。状态管理实际上取决于您想要如何做。少数几个有意义的概念是使用对象的备份副本或将原始属性(属性名)映射到底层字段(属性值)的Dictionary<string, object>

至于确定是否有任何更改,我将使用INotifyPropertyChanged接口。这将使状态管理和通知保持在类的内部。只需实现一个包装器(良好的做法)称为OnPropertyChanged(字符串propName,对象propValue),设置一个布尔数组/字典(Dict<string, bool>),然后设置是否有任何变化,如果任何属性被改变,HasChanges属性返回true。示例类:

public class TestClass : INotifyPropertyChanged
{
    private Dictionary<string, object> BackingStore = new Dictionary<string,object>();
    private Dictionary<string, bool> Changes = new Dictionary<string, bool>();
    private string _testString;
    public string TestString
    {
        get { return _testString; }
        set { _testString = value; OnPropertyChanged("TestString", value); }
    }
    private bool HasChanges { get; set; }
    public event PropertyChangedEventHandler PropertyChanged;
    public TestClass(string value)
    {
        _testString = value;
        SaveValues();
    }
    public void SaveValues()
    {
        // Expensive, to use reflection, especially if LOTS of objects are going to be used. 
        // You can use straight properties here if you want, this is just the lazy mans way.
        this.GetType().GetProperties().ToList().ForEach(tProp => { BackingStore[tProp.Name] = tProp.GetValue(this, null); Changes[tProp.Name] = false; });
        HasChanges = false;
    }
    public void RevertValues()
    {
        // Again, you can use straight properties here if you want. Since this is using Property setters, will take care of Changes dictionary.
        this.GetType().GetProperties().ToList().ForEach(tProp => tProp.SetValue(this, BackingStore[tProp.Name], null));
        HasChanges = false;
    }
    private void OnPropertyChanged(string propName, object propValue)
    {
        // If you have any object types, make sure Equals is properly defined to check for correct uniqueness.
        Changes[propName] = BackingStore[propName].Equals(propValue);
        HasChanges = Changes.Values.Any(tr => tr);
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propName));
    }
}

为了简单起见,我只使用SaveValues/RevertValues来保存/撤销更改。然而,这些可以很容易地用于实现IEditableObject接口(BeginEdit, CancelEdit, EndEdit)。然后,PropertyChanged事件可以被对象绑定到的任何形式订阅(甚至可以订阅到底层的BindingList,这样只需要订阅一个实例),它会检查HasChanges标志并设置窗体的适当状态。