views:

48

answers:

3

I have a WinForm App(.Net 3.5) that we use in house for data entry...ish, like things. Member Plans, Demographics, etc. The App has a Shell Form with all the navigation and then embeds a UserControl based on there navigation choices.

I need to be able to tell if they have made any changes in any of the controls so that I may pop up a Save reminder if they try and navigate away. What is the best way to go about that? Any and all suggestions are welcome.

I assume it is ridiculous to have hook into an event for each control on each User Control to check if any data was entered, correct?

+2  A: 

If you're using Data Binding, you can implement INotifyPropertyChanged (either by hand or by generating a runtime proxy) in your classes, subscribe to PropertyChanged event and do all the tracking there.

If not, you can recursively iterate over controls on form/usercontrol, subscribe to appropriate events for each control (for example TextChanged for a TextBox, SelectedItemChanged for a ComboBox control, etc.) and forward calls to your tracking routines.

Anton Gogolev
This may seem ridiculous but What do I do with the `PropertyChanged` event, as it pertains to this? I Have it implemented like the link shows and maybe, probably, I am just an idiot but now what? On `UserControl_Leave` check ... what? Sorry if I am just being dense.
Refracted Paladin
+1  A: 

I wouldn't say it would be ridiculous to use events here, but you might get creative with the event registration. On instantiation of a user control, you could loop through all the UI controls within it and, based on the type of UI element (text box vs. label, etc.) you could decide to hook up the changed event of that UI element to a handler.

You could probably get away with only one handler per UI element type (one for text boxes, one for check boxes, etc) since I believe the handlers for those events would need to have different parameters. The logic for those handlers could also be pulled out into its own class, so that you don't have to duplicate the same handling logic in each user control.

Brian Sullivan
+1  A: 

The corollary to what Anton has written is that unless a control is bound to a data source that gives change notifications, you have to hook an event for each control. Different controls have different sets of properties that can change, and sometimes more than one of them can matter, depending on how you've designed your UI.

I maintain a few similar apps and one of the patterns I like to use is an "event service". You can use an IoC container to wire everything up and the main form / subtasks never need to actually talk directly to each other.

The interface looks like this:

public interface IEventService
{
    void RaiseChanged(object sender, EventArgs e);
    event EventHandler Changed;
}

The basic implementation is pretty straightforward but I'll include it anyway:

public class EventService : IEventService
{
    public void RaiseChanged(object sender, EventArgs e)
    {
        EventHandler handler = Changed;
        if (handler != null)
            handler(sender, e);
    }

    public event EventHandler Changed;
}

Define it in your IoC container of choice and set it up to behave as a Singleton, or just implement it as a Singleton if IoC would be too much work; I'll assume you go with the first option. Then your main form code looks like this:

public class MainForm : Form
{
    private bool currentTaskChanged;

    public MainForm()
    {
        InitializeComponent();
        InitializeChangeEvents();
    }

    public void LoadTask(ITask task)
    {
        if (currentTaskChanged)
        {
            // Confirm whether or not to save changes
        }

        // Code to change the current task view here ...
        currentTaskChanged = false;
    }

    private void InitializedChangedEvents()
    {
        IEventService service = IoC.Resolve<IEventService>();
        service.Changed += TaskChanged;
    }

    private void TaskChanged(object sender, EventArgs e)
    {
        currentTaskChanged = true;
    }
}

Then it becomes pretty straightforward to enlist new subtasks/controls:

public class CustomerSetupControl : UserControl
{
    public CustomerSetupControl()
    {
        InitializeComponent();
        InitializeChangeTriggers();
    }

    private void InitializeChangeTriggers()
    {
        IEventService service = IoC.Resolve<IEventService>();
        txtAccountNumber.TextChanged += service.RaiseChanged;
        txtName.TextChanged += service.RaiseChanged;
        chkIsVip.CheckedChanged += service.RaiseChanged;
        // etc.
    }
}

You could theoretically automate some of this by iterating through the entire control tree of a sub-task, but it's kind of difficult. For example, if one of the controls is a CheckBox, hooking the TextChanged event really isn't going to help you. So you kind of need to do this one by one in order to ensure that you're actually detecting the correct changed event for the correct control - and in some cases you may even want to ignore changes on a particular control (i.e. maybe there's a combo box that just changes a filter somewhere without modifying any data).

This isn't exactly the code I use but it's pretty similar. I suggest giving it a try; as I find that it's not really too difficult to maintain, you just have to remember to "register" new controls when you add them.

What's especially nice about this approach is that any control can register with the Event Service; you can easily expand the service to include different types of "public" events which can be used anywhere in the app (just remember to un-register event handlers from any controls that don't live forever like the main form does).

Aaronaught
P.S. Just in case someone's going to look at this and say "That's not how you use DI/IoC!" I know, it's a poor-man's Service Locator, but it's *much* easier to retrofit an existing application with this than rewriting every single control with constructor/property injection, and you can easily disentagle yourself from the Singleton later by moving the "service" assignment into a constructor/property if and when the situation calls for it.
Aaronaught