E Devitt
E Devitt

Reputation: 67

Exposing inherited events

I am writing several UserControls for other programmers here.

Some Exposed EventHandlers with same name as the base are accepted

(UserControl CodesCombo -> SelectedValueChanged)

some aren't

(UserControl TextBox -> TextChanged)

I'm writing a UserControl which contains a TextBox. I need to expose the TextChanged event to the potential consumers of this control.

I have a similar control based on a ComboBox, and in that I used

public event EventHandler SelectedValueChanged 
    {
    add { cbMain.SelectedValueChanged += value; }
    remove { cbMain.SelectedValueChanged -= value; }
    }         

to expose the "Change" event "SelectedValueChanged", and it works without without any problems.

However, when I try to use this technique in my TextBox based control in a similar way

public event EventHandler TextChanged
    {
    add { tbMain.TextChanged += value; }
    remove { tbMain.TextChanged -= value; }
    }

I get a warning message:

'MyTextBox.TextChanged' hides inherited member 'UserControl.TextChanged'. Use new keyword if hiding was intentional.

I'm not entirely certain what the message means, other than the obvious, but what I do know is that I don't -think- I want to hide anything. I have internal SelectedValueChanged and TextChanged functions (cbMain_SelectedValueChanged, tbMain_TextChanged) which do a few things I need, but I want to allow the consumer to get an Event call on text change as well, just like they do in the ComboBox based one.

Also, I get no "change" event at all in the available list of event in the test program.

I've gotten around this for now by exposing the event as "TextChange"

new public event EventHandler TextChange
    {
    add { tbMain.TextChanged += value; }
    remove { tbMain.TextChanged -= value; }
    }

which gives me an event in the list and seems to work well enough, but I'd prefer to have a general solution to this as we're making several more controls along these lines for our package and I don't think I can get away with having names that are just "off".

Any idea what this message is really telling me, and how I can get my event internally, and also still get the user theirs?

Thanks!

Update: Asked for more specific code:

namespace NCFLSToolbox
    {
    public partial class NCFLSCodesCombo : UserControl
        {
        //Listed in the Events for System.Windows.Forms.ComboBox
        private void cbMain_SelectedValueChanged(object sender, EventArgs e)
            {
            ControlRequiredColoring();
            }

        //Exposed Event for user
        public event EventHandler SelectedValueChanged 
            {
            add { cbMain.SelectedValueChanged += value; }
            remove { cbMain.SelectedValueChanged -= value; }
            }
        }


    public partial class NCFLSTextbox : UserControl
        {
        //Listed in the Events for System.Windows.Forms.TextBox
        private void tbMain_TextChanged(object sender, EventArgs e)
            {
            ControlRequiredColoring();
            }


        //Couldn't expose "TextChanged" by name...
        ////public event EventHandler TextChanged
        ////    {
        ////    add { tbMain.TextChanged += value; }
        ////    remove { tbMain.TextChanged -= value; }
        ////    }

        //...so I exposed "TextChange" instead.
        public event EventHandler TextChange
            {
            add { tbMain.TextChanged += value; }
            remove { tbMain.TextChanged -= value; }
            }
        }
    }

Upvotes: 1

Views: 454

Answers (2)

Brian
Brian

Reputation: 25834

The problem is that TextBox events do not propagate to the parent user control. If you wish them to do so, there are two ways to accomplish this:

1) Have the event on the user control ferry all additionals/removals to the event handler to the UserControl's event handler. This is roughly what your question is trying to do. However, I don't recommend it.

2) Have the event on the TextBox trigger events on the parent user control.

So, just run this code in your constructor, below InitializeComponent():
tbMain.TextChanged += (sender,e)=>OnTextChanged(e);

With this approach, a TextChanged event in the TextBox will call OnTextChanged, which raises a TextChanged event. You cannot invoke base class events directly (hence why the OnTextChanged method is provided).

Edit: tbMain.TextChanged += (sender,e)=>OnTextChanged(e); is semantically equivalent to the below code:

tbMain.TextChanged += OnTbMainTextChanged;
...
} //End of Constructor

private void OnTbMainTextChanged(object sender, EventArgs e)
{
    OnTextChanged(e);
}

The benefit of using a lambda function is that it is more self-contained. Among other benefits, using a self-contained lambda function makes it obvious to future code maintainers that the sender is not being propagated, without requiring the maintainer to navigate to the named method. This is what you want: from the perspective of subscribers to the user control, your use of a TextBox to implement your control is an implementation detail.

Upvotes: 1

Eric Lippert
Eric Lippert

Reputation: 660533

Any idea what this message is really telling me?

First off, understand what it means to hide. Forget about the fact that it's an event for a moment. If we have:

class B     { public void M() { Console.WriteLine("B.M()"); } }
class D : B { public void M() { Console.WriteLine("D.M()"); } }

then we have two methods both called M. If you say:

D d = new D();
B b = d;
d.M(); // D.M();
b.M(); // B.M();

The compiler is warning you that you have two methods, not one, and which one you get depends on the compile-time type of the receiver, NOT the run-time type of the receiver.

Why is that probably not what you want?

Three reasons.

First, many C# programmers come to C# from Java, where methods are automatically overridden. In C# methods have to be virtual to be overridden. The compiler is telling you that you are not getting the semantics you might expect.

Second, this is to mitigate the brittle base class problem. Suppose the author of B gives the D team a DLL assembly containing B without M, and D derives from it, adding method D.M. Then the B team realizes that they could implement M, so they add it to B and give the D team a new assembly. Now the D team should be warned that B.M exists, so that they can decide whether to delete D.M or not.

Third, why are you doing this in the first place? If the base class already has an event that you want, just use it! Don't make a new one that uses the old one; just use the old one. The compiler is telling you that you're definitely doing something strange and probably wrong.

If it is your intention to have two members on two different classes where one hides the other, you tell the compiler "I have thought about this and this is intentional" by putting new on the member. That is "this is a new member, and I meant to make it a new member, not an override of an old member".

Upvotes: 5

Related Questions