dirty_clock

Quick and dirty “dirty checking” for Windows form, C#

While designing a CRF, little short-cuts can save you lots and lots of time.  This time-saver is straight-forward code-wise and should fit right into your C# code without much modification.

During CRF design time, I often have CRFs that have dozens and dozens of input controls (radio buttons, dropdowns, listboxes, textboxes…).  I’d like to add OnChange handlers to all of these input controls so that I know when a change has been made. Basically, what it comes down to is I want to know when the form is dirty, but I don’t want to add all the handlers by hand.  Fortunately, I didn’t have to and neither do you!

The code below is a recursive function which traverses the Control tree.  Whenever it locates a control that can be classified as an input control, it attaches an event handler.

void  AddOnChangeHandlerToInputControls(Control ctrl)
{
    foreach (Control subctrl in ctrl.Controls)
    {
        if (subctrl is TextBox)
            ((TextBox)subctrl).TextChanged +=
                new EventHandler(InputControls_OnChange);
        else if(subctrl is CheckBox)
            ((CheckBox)subctrl).CheckedChanged +=
                new EventHandler(InputControls_OnChange);
        else if(subctrl is RadioButton)
            ((RadioButton)subctrl).CheckedChanged +=
                new EventHandler(InputControls_OnChange);
        else if(subctrl is ListBox)
            ((ListBox)subctrl).SelectedIndexChanged +=
                new EventHandler(InputControls_OnChange);
        else if(subctrl is ComboBox)
            ((ComboBox)subctrl).SelectedIndexChanged +=
                new EventHandler(InputControls_OnChange);
        else
        {
            if (subctrl.Controls.Count > 0)
                this.AddOnChangeHandlerToInputControls(subctrl);
        }
    }
}

Keep in mind the recursion is necessary, because the Controls property field only lists a control’s immediate children. Those immediate children may have children of their own. That’s right… exactly like a family tree! The use of recursion creates an elegant way to traverse the control’s control tree.

I’ve only picked up on the simplest input controls. It should be straight-forward to extend this for other controls like a DateTimePicker or a CheckedListBox (although the CheckedListBox is a little tricky because it uses a different type of event handler).

void InputControls_OnChange(object sender,  ItemCheckEventArgs e)
{
    // Do something to indicate the form is dirty like:
    // this.formIsDirty = true;
}

All the event handler does is set a flag which indicates the form is dirty.  How you process the dirty flag is up to you, and there you have it! Hopefully, you’ve saved a few hours of manually adding event handlers!

Comments 10

  1. Avatar

    Thanks, I think I’ll steal this for my own nefarious purposes.

    Actually, I’m going to use this to detect the ‘isDirty’ on each tab change (36 tabs, 300+ controls, yes, I know…)

    Thanks for providing the code!

  2. Avatar

    Hmm, trying to work out an issue with your code. I get a “No overload for ‘InputControls_OnChange’ matches delegate ‘System.EventHandler” error. Still debugging it….using .net 2.0. Any clue?

  3. Avatar

    Figured it out. Instead of the handler you have above, using:

    void InputControls_OnChange(object sender, EventArgs e)

    does the trick. I’ll update you with anything else I find to round out your post. Cheers!

  4. Avatar
  5. Avatar

    I implemented a similary is dirty logic but it is dynamic and based on the event. It uses some reflection so it may be a bit slower then your implementation, but it allows you to add new events without code changes. Let me know what you think:

    private List _dirtyEvents = new List();
    _dirtyEvents.Add(new DirtyEvent(“TextChanged”));
    _dirtyEvents.Add(new DirtyEvent(“SelectedIndexChanged”));

    private void recurseControls(Control.ControlCollection Controls)
    {
    foreach (Control c in Controls)
    {
    if (c is INotifyPropertyChanged)
    {
    ((INotifyPropertyChanged)c).PropertyChanged += new PropertyChangedEventHandler(DirtyFormBase_PropertyChanged);

    }
    for (int i = 0; i < _dirtyEvents.Count; i++)
    {
    EventInfo ei = c.GetType().GetEvent(_dirtyEvents[i].EventName);
    if (ei != null)
    {
    Delegate textChangedDelegate = Delegate.CreateDelegate(ei.EventHandlerType, typeof(DirtyFormBase).GetMethod(“GenericHandler”, BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.IgnoreCase));
    ei.AddEventHandler(c, textChangedDelegate);
    }
    }

    if (c.HasChildren)
    recurseControls(c.Controls);
    }
    }

    private static void GenericHandler(object sender, EventArgs args)
    {
    DirtyFormBase form = (DirtyFormBase)((Control)sender).FindForm();
    if (!form.SuspendIsDirtyCheck)
    {
    form.IsDirty = true;
    }
    }

    This is a bit more code as it is a base class for all forms that need dirty logic. If you are interested let me know and I will e-mail the class.

  6. Avatar

    All this is handled on text change event of a textbox or selected index changed of drop downs. as i am assigning values to controls on grid cell double click, that time also the change event get fired, i only want to track that is user made some changes in existing values or not. then only i want to prompt to save changes.
    can anyone guide me?

  7. Avatar

    This is a very good post. As you said, a straight forward time saver. This should go to the new blog.

    Anthony, I’d like to see your whole class.

    email guillaume(dot)jay(at)gmail(dot)com

    Thanks !

  8. Avatar
  9. Avatar
    1. Avatar Post
      Author

      AddOnChangeHandlerToInputControls() needs to be called from somewhere. One option is when the form loads, and it depends on what result you’re looking for.

Leave a Reply

Your email address will not be published. Required fields are marked *

19 + 20 =

This site uses Akismet to reduce spam. Learn how your comment data is processed.