【问题标题】:A better way to allow user to save only if the data changed?仅在数据更改时才允许用户保存的更好方法?
【发布时间】:2011-12-21 20:01:13
【问题描述】:

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

假设我有一个 SaveButton,一个与 ViewModel.PropertyA 绑定的 TextBoxA,初始 PropertyA 为 = "123"。

当用户在 TextBoxA 中输入“1234”时,PropertyA 的 set 方法会执行 compare 方法来找出差异。并启用保存按钮。

但是当用户将文本“1234”改回“123”时,保存按钮将再次禁用。

一年后,现在我想知道有没有更好或更简单的方法来做到这一点? 即是否有任何框架可以做这种事情?所以我没有写代码来深拷贝对象,自己写比较方法?


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

【问题讨论】:

  • 用户界面是否需要那么聪明?为什么不进行任何更改启用“保存”按钮并使其保持启用状态?如果用户将值更改回来,它仍然被“更改”。根据业务应用程序,甚至可能值得跟踪“某事已更改”,即使它立即被改回。审核日志可以用来说明“此记录已由 User123 再次明确地保存”,即使他们没有更改任何内容。
  • 这是我以前的经理给我的要求之一。那时我并没有深思熟虑,但我认为他不想要不必要的流量,因为某些用户(没有计算机知识)会更改文本,然后将其更改回来,然后再次保存(他们认为总是需要无论如何单击保存按钮)。我认为他告诉我的一个原因是让用户知道它已经保存了。

标签: c# wpf silverlight mvvm


【解决方案1】:

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

这是一种方法。为 ViewModel 的 PropertyChanged 事件编写事件处理程序。仅当属性更改时,将原始值保存在私有 Dictionary<string, string> 中。这可以防止复制整个对象以防万一有人进行编辑。如果该属性已存在于字典中,那么您可以轻松确定它是否已更改回其原始值。

编辑:哦,我在想PropertyChangedEventArgs 包含新旧值,但事实并非如此。因此,为了做到这一点,您需要在 View Model 的属性设置器中添加一些额外的方法调用,以评估每个属性的旧值和新值。

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

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

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

【讨论】:

  • Dictionary<string, string>?所以假设他的模型只有字符串属性?因为它可能有数字字段或复选框或其他非字符串对象
  • 就个人而言,我会使用字符串来保存值的表示形式。如果您愿意,请随意Dictionary<string, object>。在我的概念中,值不会从字典复制回模型对象,因此从属性值到字符串的单向转换最适合我。
  • @Dennis Palmer 这似乎是个好主意,这确实避免了克隆整个数据对象并且比较差异更快。至少现在不写克隆方法了!谢谢! :)
【解决方案2】:

如果 ViewModel 是您自己的对象并且您可以修改它,请实现 ICloneable 接口以便您可以复制它。

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

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

我猜它和你现在已经拥有的差不多,但是如果你根据ICloneableIComparable编写逻辑,至少你只需要写一次

编辑:如果您只是不想编写自己的比较方法,可以使用 sn-ps 自动比较所有属性,例如 this post。但是,使用类似的东西比编写自己的比较函数要慢得多(在性能方面)。

【讨论】:

  • 我想我忘了提,实际上我写的深拷贝方法是通用方法,我可以用它来深拷贝任何对象(不完整,没有复制一些属性哈哈)等等方法,也是深入比较任何类型对象的通用方法。
  • 那我猜你可以无视我的回答。您是否允许用户设置诸如ViewModel.Textbox.Text 之类的东西?或者你有一切的二传手? (所以添加事件[这样你至少可以检测到什么时候发生了变化])
  • ViewModel 中的每个属性都有设置器,并将引发 compares 方法,这将更改绑定到 Save 按钮的 CanSave 属性。我真的很想知道,没有这样的框架来做这种事情吗?
【解决方案3】:

听起来您希望将状态管理与属性更改通知结合起来。状态管理真的取决于你想怎么做。少数几个有意义的概念是使用对象的备份副本或将原始属性(属性名称)映射到基础字段(属性值)的Dictionary<string, object>

至于确定是否有任何更改,我将使用 INotifyPropertyChanged 接口。这将使状态管理和通知保持在类内部。只需实现一个名为 OnPropertyChanged(string propName, object 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 标志并设置适当的状态表格。

【讨论】:

    猜你喜欢
    • 2014-02-18
    • 1970-01-01
    • 1970-01-01
    • 2012-01-30
    • 1970-01-01
    • 2021-10-15
    • 2019-01-18
    • 2013-11-30
    • 2017-06-03
    相关资源
    最近更新 更多