I wouldn't say this is a design fault, there are valid reasons for the main form to want to listen to what an object is doing. One scenario I've encountered is displaying status messages to the user to indicate what background processes are doing, or what multiple controls are doing in a multi-threaded app that lets you have multiple screens/"pages" open at once.
In the Composite UI Application Block, the basic equivalent of a dependency injection container wires up events when its instantiating objects in the same work item (a work item is just an object container for a group of related user controls). It does this by scanning for special attributes such as [EventPublication("StatusChanged")]
on events and [EventSubscription("StatusChanged")]
on public methods. One of my applications uses this functionality so that a user control instantiated way down in the innards of the application can broadcast status information (such as "Loading customer data...45%") without knowing that that data is going to end up in the main form's status bar.
So a UserControl can do something like this:
public void DoSomethingInTheBackground()
{
using (StatusNotification sn = new StatusNotification(this.WorkItem))
{
sn.Message("Loading customer data...", 33);
// Block while loading the customer data....
sn.Message("Loading order history...", 66);
// Block while loading the order history...
sn.Message("Done!", 100);
}
}
...where the StatusNotification
class has an event
with the a signature like
[EventPublication("StatusChanged")]
public event EventHandler<StatusEventArgs> StatusChanged;
... and the above Message()
and Dispose()
methods on that class invoke that event appropriately. But that class didn't explicitly have that event hooked up to anything. The object instantiator will have automatically hooked up the events to anybody with a subscription attribute of the same name.
So the MainForm has an event handler that looks something like this:
[EventSubscription("StatusChanged", ThreadOption=ThreadOption.UserInterface)]
public void OnStatusChanged(object sender, StatusEventArgs e)
{
this.statusLabel.Text = e.Text;
if (e.ProgressPercentage != -1)
{
this.progressBar.Visible = true;
this.progressBar.Value = e.ProgressPercentage;
}
}
... or some such. It's more complicated than that since it will rotate through multiple status notifications for a given number of seconds since multiple user controls can be broadcasting status messages around the same time.
So to recreate this behavior without actually switching over to CAB (which, to be honest, is much more complicated than I think it really needs to be), you could either have a MessageNotificationService
object that you pass around your application or that you turn into a static/singleton object (I usually avoid this approach since it's harder to test), OR you could have you sub usercontrols be instantiated by a factory class that does the event wiring up for you. Objects could register with the factory by attributes of your own creation or by explicitly calling methods that say "hey, anytime you create an object with an event of this signature, I want to know about it."
Just be careful to have whatever class you implement unhook the events when an object gets disposed because it's stupid easy in this scenario to end up with something that won't get garbage collected.
Hope this helps!