OK, let's start from some example, but first of all, I want to have the techniques of delegation or events to a host control which would work not only with Forms, but also with WPF, and, more generally, in all cases where you have composed event-enabled members which should remain private. I discussed it in Solution 1.
Let's consider some Forms example, a user control with some private controls in it, and expose some events. Let's consider the case where it is created traditionally, with the designer:
partial class HostControl {
private void InitializeComponent() { }
private System.Windows.Forms.Label labelTitle;
private System.Windows.Forms.TextBox textBoxTitle;
private System.Windows.Forms.Panel panelSpacer;
private System.Windows.Forms.Button buttonSubmit;
}
public partial class HostControl : UserControl {
public HostControl() {
InitializeComponent();
Setup();
}
}
I prefer not touching auto-generated code. Last partial declaration is where the developer's code is added, but I prefer touch it only to the bare minimum. I just added the call to
Setup
, a method I am going to put in yet another partial declaration for the same class, to separate the code we are writing with our own hands. In this way, designer will not touch it.
Also, I want to close the discussion of using the designer for working with events. We should not use it and write everything in code. As soon as we do such things with designer, we immediately make it a manual work and can forget every possibility to automate our work. No, only through the code. This is true for many other aspects, but our goal is different right now. We only need to introduce some events of
HostControl
the way the developer of the form can handle events of the children.
To start with, let's try to do it ad hoc, without using any utility, and see it we can find a better way later. For example:
public partial class HostControl : UserControl {
void Setup() {
this.buttonSubmit.Click += (sender, eventArgs) => {
if (SubmitButtonClick != null) SubmitButtonClick.Invoke(sender, eventArgs);
};
this.textBoxTitle.TextChanged += (sender, eventArgs) => {
if (TitleChanged != null) TitleChanged.Invoke(sender, eventArgs);
};
this.panelSpacer.MouseMove += (sender, eventArgs) => {
if (PanelMouseMove != null) PanelMouseMove.Invoke(sender, eventArgs);
};
}
public event EventHandler<EventArgs> SubmitButtonClick;
public event EventHandler<EventArgs> TitleChanged;
public event EventHandler<MouseEventArgs> PanelMouseMove;
}
That's it. From this moment, the developer of the form can use the events
SubmitButtonClick
,
TitleChanged
or
PanelMouseMove
of the host control to handle the events of the children. Moreover, one can add multiple handlers, and we can add more "exposed events" to the same controls. The problem is solved. I think it is not bad already. I added just three lines per event per control (could be 4 or 5 lines, depending on formatting style, even 1).
I only want to show how it might look for .NET v.2.0, where lambda style could not be used, but anonymous methods were already introduced. (I think we can reasonable assume that .NET versions prior to v.2.0 do not exist.) For this version,
Setup
should be re-written in a little longer way:
void Setup() {
this.buttonSubmit.Click += delegate(object sender, EventArgs eventArgs) {
if (SubmitButtonClick != null) SubmitButtonClick.Invoke(sender, eventArgs);
};
this.textBoxTitle.TextChanged += delegate(object sender, EventArgs eventArgs) {
if (TitleChanged != null) TitleChanged.Invoke(sender, eventArgs);
};
this.panelSpacer.MouseMove += delegate(object sender, MouseEventArgs eventArgs) {
if (PanelMouseMove != null) PanelMouseMove.Invoke(sender, eventArgs);
};
}
I think this is also not too bad. Where is "a lot of work"?
Now, I want to illustrate that this approach is nearly optimal.
What can we use for a utility to make it more universal. First of all, we can abstract out the events of the host control. Let's see:
public static class EventDelegationUtility<ARG> where ARG : EventArgs {
public static void Invoke(EventHandler<ARG> eventInstance, object sender, ARG eventArgs) {
if (eventInstance != null)
eventInstance.Invoke(sender, eventArgs);
}
}
First of all, note that it
eventInstance
is of the type of the delegate instance (don't mix up with delegate types); and we need to use it to pass an instance of the event object representing event of the host control. Is it possible. Yes! but only because this code should be used in the code of the host control, nowhere else. That's why we can do it for "implementing events".
Can we also abstract our "target event instances", such as
buttonSubmit.Click
,
panelSpacer.MouseMove
?
No! We cannot write something like void
Delegate(ref EventHandler<EventArgs> implementingEvent, EventHandler<EventArgs> targetEvent, …)
and then pass event instance like
buttonSubmit.Click
. We can only work with events in the declaring class, otherwise the compiler will tell use that we only can use it "on the left of '=+' or '-=' operator". For the good reasons. This is one of the fool-proof features of event instances, as opposed to the "regular" delegate instances. Please see my past answer:
Since we have multicast delegates, why do we need events?[
^].
So, let's forget it and see how the use of the utility shown in the last sample will look like:
void Setup() {
this.buttonSubmit.Click += (sender, eventArgs) => {
EventDelegationUtility<EventArgs>.Invoke(this.SubmitButtonClick, sender, eventArgs);
};
this.textBoxTitle.TextChanged += (sender, eventArgs) => {
EventDelegationUtility<EventArgs>.Invoke(this.TitleChanged, sender, eventArgs);
};
this.panelSpacer.MouseMove += (sender, eventArgs) => {
EventDelegationUtility<MouseEventArgs>.Invoke(this.PanelMouseMove, sender, eventArgs);
};
}
Is it really much better? I would not say so. It's event a bit longer. Some would say it's better, but I would not say it justifies the effort.
Now, can we abstract our the children types? Well, we can, but that would lead to too many boring declarations. It could pay off if we need to write many user controls of the same set of type and handle the same set of events many times. This is not very usual work, so it should not be done on a universal level. If this repetitive work is the case, yes, it could be done, but just see how it might look:
internal static void ExposeClick(Control target, EventHandler<EventArgs> implementor) { }
internal static void ExposeTextChanged(TextBox target, EventHandler<EventArgs> implementor) { }
internal static void ExposeMouseMove(Control target, EventHandler<MouseEventArgs> implementor) { }
internal static void ExposeMouseUp(Control target, EventHandler<MouseEventArgs> implementor) { }
internal static void ExposeMouseDown(Control target, EventHandler<MouseEventArgs> implementor) { }
Can you see where it goes? The problems is that the major difference between the methods is just the name of event. There is no way to abstract it out: it is not the instance of anything, and this is not the type to be abstracted out using generics. This approach can only payoff it we handle limited set of events dictated by concrete application and re-use it many times. This is not very typical, can be decided only on the application level only in some cases.
Conclusion: the approach shown first is still the best.
—SA