Click here to Skip to main content
15,885,782 members
Please Sign up or sign in to vote.
5.00/5 (1 vote)
See more: , +
Recently I have been developing some WinForms Controls for my company. Sometimes I inherit an already existing control and sometimes it's a composite control with different other controls on it. However, I have a problem, or rather a question.
Now my problem lies in how to properly encapsulate a Control contained in a composite user control?
Let's say I have ControlHost and ControlChild. ControlHost is a composite control that hosts ControlChild. Now an important event in ControlChild is the ControlChildEvent. So I create an Event in ControlHost and have it fired when ControlChild raises it's event? This seems like a lot of work, and if ControlChild ever gets a new event (or Property, Method etc.) I need to change ControlHost too, and that's not really what I want!
So I could just have a (ReadOnly) Property in ControlHost that exposes ControlChild, but in this case the user of ControlHost could alter the appearance of ControlChild in ways that my ControlHost can't cope with...

What is a proper way of dealing with this in Controls? Should I really expose every method of ControlChild to ControlHost and should I change ControlHost when ControlChild changes?

Thanks.
Posted
Comments
Sergey Alexandrovich Kryukov 13-Mar-12 21:13pm    
Hm... Even though I already though on this problem, I must say the question is generally really interesting and important, so I voted 5.

I did not want to frustrate you, but I'm afraid that my answer won't say anything principally new to you. Just the opposite, it may look more like some justification of your thinking about this matter, not anything very new. Even though this is not much about any new ideas, I just hope my reasoning might help you to see the problem more clearly... Anyway, there is something to think about -- some more, something which can make the work more automated.

Thank you for giving a matter for interesting discussion.
--SA
Sander Rossel 14-Mar-12 14:48pm    
Thanks for the answer and the upvote! Your answer didn't at all frustrate me. It was a good answer deserving my 5 (and some univoter's univote...).
Designing Controls is difficult. If anywhere in the future you find anything that might be of use let me know ;)
Sergey Alexandrovich Kryukov 14-Mar-12 15:16pm    
Thank you. As to the uni-voters... it's funny... last days, someone comes, votes 1 for my 5-10 answers or so in a row and go, until the next day. A secret hater, kind of. There are no correlation with the content of the questions/answers, they just go by continues block... :-)
--SA
Sander Rossel 14-Mar-12 15:55pm    
It's sad... You would think people would be more mature, but I guess they aren't :)
Sergey Alexandrovich Kryukov 14-Mar-12 19:28pm    
:-)

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:

C#
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(); //see below
    } //HostControl
} //class HostControl


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:

C#
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);
        };
    } //Setup

    public event EventHandler<EventArgs> SubmitButtonClick;
    public event EventHandler<EventArgs> TitleChanged;
    public event EventHandler<MouseEventArgs> PanelMouseMove;

} //class HostControl


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:

C#
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);
    };
} //Setup


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:

C#
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);
    } //Invoke
} //EventDelegationUtility


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:
C#
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);
    };
} //Setup

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:
C#
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) { /* ... */ }
// yes, "internal"; using this approach in the universal-layer library would not pay off, I think...


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
 
Share this answer
 
v14
Comments
Sander Rossel 16-Mar-12 19:43pm    
Thanks for the very extensive answer! I am reading your code and realize I still use the pre-.NET 2 approach...
I've always used the Addhandler button1.Click, AddressOf Button1_Click syntax.
Of course using a lambda would make much more sense (in this case)! Addhandler button1.Click, Sub(sender, e) RaiseEvent ButtonClicked(sender, e)
In almost every other situation I do use lambda's, except for events.
So that's a first thing I could improve to at least shorten my code base and make it more readable and clear.
The "why do we have Events?" question was very interesting. I tried it out and was mind blown.
It seems there is much I can still learn about delegates. I can use them correctly, but what's going on 'under the covers' is quite complex!

For the following part excuse my C# code, I don't write C# very often :)
One question though. You do:
this.buttonSubmit.Click += (sender, eventArgs) => {
if (SubmitButtonClick != null) SubmitButtonClick.Invoke(sender, eventArgs);
};

Now what if the user of your Control would do the following:
private void SubmitButtonClick(Object sender, EventArgs e)
{
Button btn = (Button)sender;
btn.Size = new Size(1000, 1000);
btn.Click += (s, ea) => { ...}
}

They now have full control of the Button again and can mess up the entire Control.
They could now even hook up to the Events we've been trying to hide :)

Would it not be more appropriate to do this following?
this.buttonSubmit.Click += (sender, eventArgs) => {
if (SubmitButtonClick != null) SubmitButtonClick.Invoke(this, eventArgs); ' this, instead of sender!
};

Also, when reading about the Events and that they cannot be called in derived classes I thought of the OnEventRaised overridable methods many Controls have.
For example:

this.buttonSubmit.Click += (sender, eventArgs) => {
if (SubmitButtonClick != null) {OnSubmitButtonClick(this, e); SubmitButtonClick.Invoke(sender, eventArgs);}
};

override void OnSubmitButtonClick(Object sender, EventArgs e){}

This way derived classes have some control over Events before they fire.
Luckily this is not an issue I have to deal with right now, but it makes the handling and delegating of events even harder :)

Thanks, I have gained some new insights into the handling of events and delegates.
Sergey Alexandrovich Kryukov 16-Mar-12 22:18pm    
Let me overview your considerations in brief, and later on I explain things in more detail.

0) As to v.2.0. It's not just lambda. The methods of event handling I explained are based on anonymous methods; the were introduced in v.2.0 and C# v.2. More advanced and easy to use grammar also uses lambda and type inference, introduced in C# v.3.

1) Your concern about "this" instead of "sender" is correct. First, you can do it, too (you can use either "sender" or "this", but expected practice is using "sender", original one); second, it will really provide deeper hiding the child objects inside the host object. True, you can get the access to the button when the event is handled in a form for the first time. Third, so what? What's so bad about it? You can add another event handler to the button? -- this is valid. Change button color -- why not? I mean, even without a single event, you can get access to all child controls; you know how? Via HostControl.Controls. And throw Reflection, you can get all instances of all object members and invoke any private methods. If so, why bothering too much?

Also, you messed up with "private void SubmitButtonClick(Object sender, EventArgs e)". The name SubmitButtonClick is the event instance member of HostControl, it has nothing to do with the function declaration you've shown. But your idea is correct.

By the way, did you pay attention for this interesting fact. Suppose you have a delegate type. Then declare a delegate instance of this delegate type. What is the type of this delegate instance? Not this delegate type! This is some unrelated class supporting Invoke, GetInvocationList. Use GetType() is view it through reflection -- seeing is believing.

2) There is no such thing as OnEventRaised. Probably you mean methods like OnPaint, OnMove, OnMouseDown, etc. Here is the thing: these methods are designed not for calling them in your derived class. They are designed to be overridden in it. The purpose is this: they provide the mechanism alternative to the events. For example, to implement graphic rendering in some (terminal) Control class, you use one of the two ways: a) handle Paint event, b) override OnPaint method (if you don't call base.OnPaint, the event won't be raised at all).

--SA
Sander Rossel 17-Mar-12 5:49am    
If this is your brief explanation you should really write a book on the subject :)

You make a valid point with sender and this. You could always get a reference to the inner Button Control. It's just a bit harder than when you make a public property out of it (which is basically an invitation to edit it).

I know OnEventRaised does not exist. I was simply referring to OnPaint, OnMove etc. as you mentioned. I just noticed the OnClick of a Button fires before the Event though, so I should've said OnEventRaising. And indeed, not calling base.OnClick results in no Click Event being fired.

I am not sure what you mean with a delegate instance is not a the type of the declared instance.
When I say, for example (VB):
Dim a As New Action(Sub() Console.WriteLine(String.Empty))
Dim at As Type = a.GetType

at is now the type System.Action, while Action is a delegate type. It's base class is System.MulticastDelegate.
This is what I expected, but according to you I should not see System.Action?

What I also just found is the Custom Event keyword in VB. It generates the following code:
Public Custom Event Test As EventHandler
AddHandler(value As EventHandler)

End AddHandler

RemoveHandler(value As EventHandler)

End RemoveHandler

RaiseEvent(sender As Object, e As System.EventArgs)

End RaiseEvent
End Event

Couldn't find the C# equivalent. If you use this you have to implement the invoking yourself (in RaiseEvent(Object, EventArgs)) or the event will not be raised, even when using RaiseEvent.
Looking forward to your further comments.
Sergey Alexandrovich Kryukov 18-Mar-12 15:03pm    
This is a different story; if I got it correctly, the C# equivalent is:

event System.EventHander<MyEventArgsType> SomeEvent {
add {/*...*/}
remove {/*...*/}
}

There two methods are similar to property getter and setter.

Again, let me come to your post later; I did not get time to read it carefully yet; I'll do it later; and you also can look through my latest comments and put your concerns together; if I still did not explain or address any of them, please comment on it below, OK?

--SA
Sander Rossel 18-Mar-12 15:49pm    
Actually I think we should leave it at this. You've helped me more than I'd hoped.
I will be going on a vacation in two days and I'll be gone for two weeks. On my vacation there is no chance of me reading your answers and when I get back I'll have a whole lot more of reading to do (like 100's of emails).
So thanks for the help. I really appreciate it! And it's been very interesting :)
I think the way of exposing an event of a ControlChild to the interface of ControlHost you have described as "a lot of work" is the most appropriate.

Perhaps you can do a little better using the following technique: you can create some interfaces representing each of the nested controls and implement them all in the host control. It won't make a mechanism reducing amount of development work (actually, will requires some extra work), but it can improve maintenance of the code, reduce the likeness of the mistakes like "forgot to expose some important event". It can only make the process of doing this "a lot of work" more easy and regular, just because you can use explicit interface implementation, and then part of that "typing" work will be done by the Intellisense.

Other than that, I would advice you to follow the way you already explained by yourself.

The alternative would be to expose the reference to the nested controls (children). I'm sure you understand why this is not very good as you mention encapsulation. Yes, it would be certain violation of encapsulation and sometime can be used for quick-and-dirty solution. Yes, quite quick compared to "honest" approach, and, well, dirty enough. As I don't like quick-and-dirty at all, I want to wrap this part up. I only need to add that it would also make the property of child controls not accessible to the Designer, but to me the Designer has very little value; I try to use the Designer only for general layout and, for example, never use it to set up event handlers.

However, this question is interested and important, so it makes me interested to think some more about it: is it possible to create some framework which could highly improve the development of nesting user controls? Perhaps there is something to think about.

—SA
 
Share this answer
 
Comments
Sander Rossel 14-Mar-12 14:44pm    
Thanks for the answer! The Interface is a good idea I hadn't thought of. Other than that you are, indeed, not telling me anything I didn't yet know. Although having what you think you know justified is sometimes just as good :)
As for nesting Controls and only showing certain Properties, perhaps the following articles might be of help.
http://www.codeproject.com/Articles/37830/Designing-Nested-Controls (Actually this was the article Henry linked in an answer to the first question and first post I made on CP).
http://www.codeproject.com/Tips/56028/Hiding-inherited-properties
It seems possible to hide or disable certain Properties of a nested Control. Although it's still a lot of work. And I think it only works in the Property Grid. Anyway, don't know how to combine them or make something out of it that could be used to make nesting controls really easy. But I think it's interesting nonetheless.
Sergey Alexandrovich Kryukov 14-Mar-12 15:13pm    
You are welcome.
I have thought a bit more on the topic. Everything is reduced to the fact you need to have an event on the host control anyway. Ideally, you need a shortcut in the implementation of it. It's easy to make some universal generic "EventExposer" which sets up some internals of the event invocation, pretty much as you described. The generic parameter could be just one, the type "where PARAMTYPE: System.EventArgs". Logical, isn't it. The usage by the developer of the host user control must logically look like:

EventExposerDelegateImplementation <MyEventArgsType>(
System.EventHandler<MyEventArgsType> event,
Control implementingControl); //but who will guaranteed it really implements appropriate event?
//Needs run-time check, which is bad.

Just some thoughts. The concern is that it's hardly possible to make the usage simple (shorter?) then the straightforward implementation you have described. Well, something to think about some more...
--SA
Sander Rossel 14-Mar-12 15:48pm    
Perhaps to make it truly 'easy' one could create something with a designer surface that uses Reflection to find Public Events, Methods and Properties of a nested Control and let's the user pick which ones to expose after which it generates the code for you (maybe using a designer like partial file)... But that sounds like something I don't want to get into :)
Sergey Alexandrovich Kryukov 14-Mar-12 19:24pm    
I don't think so, in this case. Wait a bit, I'll right some more on this topic. Don't be too excited, just because, I would show how to improve it and even try to argue that you could not improve it anymore, so the gain is questionable. You see, the reason is that what you called "too much work" is actually just one extra line per event per class (or 3-4, depending on how to format it :-), without developing of any utility.

It's just limited by the syntax and the set of possible combinations of types. Any way, the thing is: every utility should not look "clever", it should pay-off to be justified.

OK, please wait for a while, I'll try and see more...
--SA
Sergey Alexandrovich Kryukov 16-Mar-12 11:34am    
Completed. Please see. I think I basically analyzed this problem. First approach is still the best, but if implemented in a right way. Please see my second answer.
--SA

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900