tags:

views:

1278

answers:

7

Hi all,

I'm working on a system were a user can edit existing objects ("Filter" domain objects to be exact) through a GUI. As a UI hint, we only want to enable the save button if the user really modified something to the object. I was wondering if anyone had any experience with this problem and what the best way would be to approach this.

I was thinking about adding an isDirty() flag to the domain object. When a user starts editing a Filter, I would then make a copy, pass it to the GUI and let the user make modifications to the copy. A binding on the isDirty() flag would then enabled/disable the save button. On saving, the differences would then be merged into the original object and persisted.

Additionaly, I was thinking what would happen if a user undos the changes he made to an object. The isDirty() flag should then return false. So I guess the only way to achieve this is to keep the original value of each property inside the domain object.

Any ideas?

+3  A: 

Correct!

Additionally,you can expose two methods: BeginEdit - In this method, your mark your IsDirty Flag to True. Meaning you are doing modification. Call this method when you are about to make modifications

CancelEdit - In this method, reset the IsDirty Flag to False. Meaning you have arborted the edit process and reverted back to the original state. Call this method when cancelling any modifications made.

And once any modifications are persisted, you also reset the IsDirty Flag to False.

I hope this helps.

J Angwenyi
Thanks for all the answers guys. I accepted this answer since it was the first and had the most upvotes.
Christophe Herreman
This sounds an awful lot like CSLA. If you're using .NET I would recommend checking that out. http://www.lhotka.net
justin.m.chase
+1  A: 

If you have a set of objects which are being edited then you'll probably need something more than a boolean flag for isDirty(). This problem is not dissimilar to reference counting, i.e. increment a dirty count on edit and decrement on undo. If you are supporting undo I suspect you are going to wind up with some pretty hairy logic. I would keep it out of your domain objects.

Simon
+1  A: 

Yes, this works well. Rather than undo, I use the IsDirty method to signify that something MIGHT have changed the record and then that triggers my "did the record change logic". I developed my own framework, where every table field is actually a property of an object. Each time a field is written to the objects "isDirty" flag is set. In the "SaveObject" method of the object (actually its a helper class but could easily be in the object, but I wanted the ability to save objects in different manners, like to xml, database, ect.), I check the IsDirty and if its false then I skip the save. This simplifies the logic as each time I had the possibility of changing the object, I call SaveObject and let the framework handle it.

skamradt
+1  A: 

If you are using .NET framework, you may want to take a look at CSLA .NET framework by Rockford Lhotka: http://www.lhotka.net/cslanet/Default.aspx

CSLA is a mature framework which includes object state management (IsDirty), undo functionality, data binding and a lot more, plus it is free and open-source.

Blend Master
+1  A: 

Depending on your domain, you could use equality to test for differences. Keep the original object and make a copy of the object for editing. Anytime an edit may be performed, modify the UI appropriately.

The benefit of this suggestion is that it doesn't stick GUI specific functionality (the isDirty() flag) on your domain objects, but YMMV

ARKBAN
+1  A: 

If you are supporting operation undo at a level of granularity greater than 'undo everything since last save' then I'd suggest an undo stack. When something is edited, it (or it's undo operation functor or delegate) get's added to the stack. When you undo, you simply pop the stack and undo the operation popped. Your isDirty() flag is then just a check if the undo stack contains items, rather than extra storage and logic to update.

workmad3
Thanks for the suggestion. I'm wondering where the undo stack would live. Is it a separate object in a UndoManager or some sort, where the stack is linked to the domain object you are editing?
Christophe Herreman
+1  A: 

There are a couple of interfaces that you could implement that help with change tracking and undo: INotifyPropertyChanged and IEditableObject. Both of these interfaces allow the object to play nice with databinding.

public class Person : INotifyPropertyChanged, IEditableObject
{
    private bool isDirty;

    public bool IsDirty
    {
        get { return isDirty; }
    }

    private string firstname = string.Empty;

    public string Firstname
    {
        get { return firstname; }
        set
        {
            if (firstname == value) return;
            firstname = value;
            NotifyPropertyChanged("Firstname");
        }
    }

    private string lastname = string.Empty;

    public string Lastname
    {
        get { return lastname; }
        set
        {
            if (lastname == value) return;
            lastname = value;
            NotifyPropertyChanged("Lastname");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public void NotifyPropertyChanged(string propertyName)
    {
        isDirty = true;

        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    private bool inTrans;
    private Person copy;

    public void BeginEdit()
    {
        if (!inTrans)
        {
            if (copy == null)
                copy = new Person();

            copy.isDirty = isDirty;
            copy.Firstname = Firstname;
            copy.Lastname = Lastname;


            inTrans = true;
            isDirty = false;
        }
    }

    public void CancelEdit()
    {
        if (inTrans)
        {
            isDirty = copy.isDirty;
            Firstname = copy.Firstname;
            Lastname = copy.Lastname;

            inTrans = false;
        }
    }

    public void EndEdit()
    {
        if (inTrans)
        {
            copy = null;
            inTrans = false;
        }
    }
}
Adrian