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).