Saturday, July 25, 2009

Simple Dirty Tracking for WinForms in C#

A common requirement for Windows Forms applications is to track whether or not a user has made changes to a document. Upon closing, the application can check if the document has been changed (considered "dirty") and prompt the user to save. If the document isn't "dirty" - if it hasn't been changed since its last save - the application can forego such a prompt.

A simple way to create this behavior is to track a "dirty" flag for the form, and trap the appropriate event on input controls to catch value changes. Rather than create an event handler for every input control, and write all that code again for the next form, a developer can create a simple helper class - one that assigns the appropriate event handling code for each of a form's input controls, and tracks the "dirty" state on behalf of the form. Such a class can then be reused across many forms and projects.

Here's a sample of such a class that demonstrates this simple technique.

public class FormDirtyTracker
{
private Form _frm;
private bool _isDirty;

// property denoting whether the tracked form is clean or dirty
public bool IsDirty
{
get { return _isDirty; }
set { _isDirty = value; }
}

// methods to make dirty or clean
public void SetAsDirty()
{
_isDirty = true;
}

public void SetAsClean()
{
_isDirty = false;
}

// initialize in the constructor by assigning event handlers
public FormDirtyTracker(Form frm)
{
_frm = frm;
AssignHandlersForControlCollection(frm.Controls);
}

// recursive routine to inspect each control and assign handlers accordingly
private void AssignHandlersForControlCollection(Control.ControlCollection coll)
{
foreach (Control c in coll)
{
if (c is TextBox)
(c as TextBox).TextChanged += new EventHandler(FormDirtyTracker_TextChanged);

if (c is CheckBox)
(c as CheckBox).CheckedChanged += new EventHandler(FormDirtyTracker_CheckedChanged);

// ... apply for other input types similarly ...

// recurively apply to inner collections
if (c.HasChildren)
AssignHandlersForControlCollection(c.Controls);
}
}

// event handlers
private void FormDirtyTracker_TextChanged(object sender, EventArgs e)
{
_isDirty = true;
}

private void FormDirtyTracker_CheckedChanged(object sender, EventArgs e)
{
_isDirty = true;
}

}


The class is instantiated in the form's Load handler like this:

private FormDirtyTracker _dirtyTracker;

public Form1()
{
InitializeComponent();
}

private void Form1_Load(object sender, EventArgs e)
{
// instantiate a tracker to determine if values have changed in the form
_dirtyTracker = new FormDirtyTracker(this);
}



Then when the form is closing, the developer can simply check _dirtyTracker.IsDirty to determine if the user should be prompted to save.

This isn't a particularly sophisticated technique, and considers the document "dirty" with any change - even if the user changes a value back to its original. It does track user input however, is simple to code, and is reusable across multiple forms.

6 comments:

  1. Exactly what I was looking for. Thanks!

    ReplyDelete
  2. Very nice job, dude!

    ReplyDelete
  3. Do you need to have 2 seperate methods FormDirtyTracker_TextChanged and FormDirtyTracker_CheckedChanged? Couldn't you just have one, because they both have the same parameters and do the same thing

    ReplyDelete
    Replies
    1. If the only work is to set the _isDirty property, then your opinion is right.

      But if other things need to be tracked, i.e. which control content has changed and their corresponding logic, then you should implement separate handlers like this in the post.

      Delete
  4. Hi,

    I found this via your code on the CodeProject website and really like your approach. I would like to use it in one of my VB.Net applications. How would I do that? I tried to compile the the CodeProject code, but had trouble. I'm a newbie to .Net and need some guidance. Would appreciate your reply.

    Thanks,

    Mark
    miller.mark.e@sbcglobal.net

    ReplyDelete

Submit a comment?