3

What I did before was make a deep copy of the data object, then write a generic compare method that uses reflector to compares if there is difference betweens the two objects.

So say if I have a SaveButton, a TextBoxA binded with a ViewModel.PropertyA, initialy PropertyA is = "123".

When user typed "1234" in TextBoxA, the PropertyA set method will executes the compare method to find the difference. And enable the Save Button.

But when the user changed the text "1234" back to "123", the Save button will disabled again.

After 1 year, now I wonder is there a better way or easier way to do it? i.e. Is there any framework that will do this kind of stuff? So I don't have write code for deep copy the object, write compares method myself?


The actual UI I had was not that simple only contains TextBox type, that was a UI for edit customer information, thus have DateTime, Collection etc. That's why I wrote deep copy method for cloning the whole object.

King Chan
  • 4,212
  • 10
  • 46
  • 78
  • Does the UI need to be _that_ clever? Why not just have any change enable the Save button and leave it enabled? If the user changes the value back, it's still been "changed." Depending on the business application, it might even be worth tracking that "something changed" even if it was immediately changed back. An audit log can be useful to say "this record was saved again, explicitly, by User123" even if they didn't change anything. – David Dec 21 '11 at 20:04
  • that was one of the requirement my previous manager gave me. I didn't think deep by that time, but I think he doesn't want unnecessary traffic, for some user (with no computer knowledge) will change the text, then change it back, and save it again (they think always need to click save button anyway). One reason I think he told me was so user know that already saved. – King Chan Dec 21 '11 at 20:14

3 Answers3

0

Assuming that these properties on your View Model are raising the PropertyChanged event somehow since the question is tagged with MVVM.

Here's one approach. Write an event handler for your ViewModel's PropertyChanged event. Save original values in a private Dictionary<string, string> only when a property changes. That prevents the need for copying the whole object just in case someone makes an edit. If the property already exists in the dictionary, then you could easily determine if it's been changed back to its original value.

Edit: Oh, I was thinking that the PropertyChangedEventArgs contained new and old values, but it doesn't. So, in order to do this, you'd need to add some extra method call within your View Model's property setters that can evaluate the old and new values of each property.

In order to easily set up enabling and disabling the Save button, there should be a bool property in your view model to which you would bind the Save button's enabled property.

If items are removed from the dictionary whenever the new value matches the original value, then your Save button enabled property could just return true if the dictionary contains any items.

Edit 2: For the collection types, you'd want to have your View bind to ObservableCollection properties on your View Model. The Collection changed event does give you a list of old and new items, so keeping track of the changes within that event handler should be fairly easy.

CoderDennis
  • 13,642
  • 9
  • 69
  • 105
  • 1
    `Dictionary`? so that is assuming his model only has string properties? Because maybe it has numberic fields or checkboxes or other non-string like objects – Ron Sijm Dec 21 '11 at 20:39
  • Personally, I'd use a string to hold a representation of the value. Feel free to make it `Dictionary` if you prefer. In my concept, the values aren't ever being copied back from the dictionary to the model object, so a one way conversion from property value to string works best for me. – CoderDennis Dec 21 '11 at 21:21
  • @Dennis Palmer This seems like a good idea, this really does avoids cloning the whole data object and is faster for comparing the difference. At least doesn't write the cloning method now! Thanks! :) – King Chan Dec 21 '11 at 21:57
0

if the ViewModel is your own object and you can modify it, implement the ICloneable interface so you can make a copy of it.

Next implement the IComparable interface on it, where T is the view model. So its easy to compare

Then I suppose you'd have to make an PropertyChanged event for all properties, and when one is fired make the compare.

I guess its pretty much the same as what you already have now, but if you write the logic based on ICloneable and IComparable at least you only have to write it once

edit: and if you do simply do not want to write your own compare method, there are snippets that automatically compare all properties, such as this post. However, using something like that is a lot slower (performance wise) than writing your own compare function.

Community
  • 1
  • 1
Ron Sijm
  • 8,490
  • 2
  • 31
  • 48
  • I think I forgot mention, actually the deep copy method I wrote was generic method that I can use for deep copy any object (was incomplete, didn't copy some property lol) and so as the compare method, was also a generic method for compare any type of object deeply. – King Chan Dec 21 '11 at 20:49
  • Then I guess you can disregard my answer. Do you allow users to set things such as `ViewModel.Textbox.Text`? Or do you have setters for everything? (so add events [so you can at least detect when something is changed]) – Ron Sijm Dec 21 '11 at 21:17
  • every property in ViewModel has setters and will raise the compares method, that will change the CanSave property, which is binded to the Save button. I just really wonder, there is no such framework to do this kind of stuff? – King Chan Dec 21 '11 at 21:39
0

Sounds like you want state management combined with property change notifications. The state management is really up to you to with how you want to do it. The few concepts that make sense are to use either a backup copy of the object or a Dictionary<string, object> that maps the original properties (property name) to underlying fields (property values).

As for determining if there are any changes, I would utilize the INotifyPropertyChanged interface. This would keep state management and notifications internal to the class. Just implement a wrapper (good practice) called OnPropertyChanged(string propName, object propValue) that sets a boolean array/dictionary (Dict<string, bool>) that would then set whether there are any changes, with a HasChanges property returning true if any properties are changed. Example class:

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

For simplicity sake, I just use SaveValues/RevertValues to save/undo changes. However, those can easily be used to implement the IEditableObject interface (BeginEdit, CancelEdit, EndEdit). The PropertyChanged event can then be subscribed to by whatever form the objects are being bound in (or even to an underlying BindingList, that way only a single instance needs to be subscribed to), which checks for the HasChanges flag and sets the appropriate state of the form.

SPFiredrake
  • 3,852
  • 18
  • 26