Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UserControl as an interface, but visible in the Designer

So we have a C# WinForms project with a Form that contains a bazillion UserControls. Each UserControl naturally exposes all the UserControl methods, properties, etc. in addition to its own specific members.

I've been thinking that one way to reduce the complexity of dealing with these UserControls is to access them through an interface. So instead of drag-and-drop to put the UserControl on the form, something like this in the constructor:

public class MyGiantForm
{
    ICustomerName cName;

    public MyForm()
    {
        InitializeComponent();

        var uc = new SomeCustomerNameUserControl();
        this.Controls.Add(uc);
        cName = uc;
    }
}

SomeCustomerNameUserControl implements ICustomerName, naturally, and ICustomerName contains the specific properties I really care about (say, FirstName and LastName). In this way I can refer to the UserControl through the cName member and, instead of being bowled over by all the UserControl members, I get only those in ICustomerName.

All well and good, but the problem is that if I do it this way, I can't see SomeCustomerNameUserControl in the Designer. Does anybody know I way I can do this but still see the UserControl on the form's design surface?

EDIT: One way to do this, which isn't overly complicated, is to put the controls on a base form. By default (in C#) the control members are private. Then I create a property for each control exposing it through the interface.

However, I'd be interested in some other way to do this, even if it's more complex. There seems to be some way to do it with IDesignerHost, but I can't find any applicable examples.

like image 914
Ryan Lundy Avatar asked Apr 24 '09 01:04

Ryan Lundy


2 Answers

If SomeCustomerNameUserControl is defined like this:

class SomeCustomerNameUserControl : UserControl, ICustomerName
{
}

You can still drop this control in the designer (which creates someCustomerNameUserControl1) and do this whenever you need to:

ICustomerName cName = someCustomerNameUserControl1;

Maybe I'm missing something, but I think it's that simple.

like image 83
Bob Nadler Avatar answered Sep 19 '22 09:09

Bob Nadler


There's a way to accomplish what you want -- hiding the members you don't want to see -- but make it apply automatically, without requiring others' cooperation in terms of them using a custom interface. You can do it by reintroducing all the members you don't want to see, and tagging them with attributes.

This is what Windows Forms does when, for example, a base-class property doesn't mean anything for a particular descendant. For example, Control has a Text property, but a Text property is meaningless on, say, a TabControl. So TabControl overrides the Text property, and adds attributes to its override saying "By the way, don't show my Text property in the Property Grid or in Intellisense." The property still exists, but since you never see it, it doesn't get in your way.

If you add an [EditorBrowsable(EditorBrowsableState.Never)] attribute to a member (property or method), then Intellisense will no longer show that member in its code-completion lists. If I'm understanding your question correctly, this is the big thing you're trying to achieve: make it hard for application code to use the member by accident.

For properties, you probably also want to add [Browsable(false)] to hide the property from the Property Grid, and [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] to prevent the designer from writing the property's value to the .designer.cs file.

These will make it very difficult to accidentally use the method/property. They're still not a guarantee, though. If you do need a guarantee, then throw in an [Obsolete] attribute too, and build with "Treat warnings as errors" -- then you're taken care of.

If the base member is virtual, you probably want to override it, and have your override simply call base. Don't throw an exception, since the overridden member will probably be called by the base class during the normal course of events. On the other hand, if the base member isn't virtual, then you want to use "new" instead of "override", and you can decide whether your implementation should call base, or just throw an exception -- nobody should be using your reintroduced member anyway, so it shouldn't matter.

public class Widget : UserControl
{
    // The Text property is virtual in the base Control class.
    // Override and call base.
    [EditorBrowsable(EditorBrowsableState.Never)]
    [Browsable(false)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    [Obsolete("The Text property does not apply to the Widget class.")]
    public override string Text
    {
        get { return base.Text; }
        set { base.Text = value; }
    }

    // The CanFocus property is non-virtual in the base Control class.
    // Reintroduce with new, and throw if anyone dares to call it.
    [EditorBrowsable(EditorBrowsableState.Never)]
    [Browsable(false)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    [Obsolete("The CanFocus property does not apply to the Widget class.")]
    public new bool CanFocus
    {
        get { throw new NotSupportedException(); }
    }

    // The Hide method is non-virtual in the base Control class.
    // Note that Browsable and DesignerSerializationVisibility are
    // not needed for methods, only properties.
    [EditorBrowsable(EditorBrowsableState.Never)]
    [Obsolete("The Hide method does not apply to the Widget class.")]
    public new void Hide()
    {
        throw new NotSupportedException();
    }
}

Yes, this is a fair bit of work, but you only have to do it once... per member, per class... umm, yeah. But if those base-class members really don't apply to your class, and having them there will cause confusion, then it may be worth going to the effort.

like image 41
Joe White Avatar answered Sep 20 '22 09:09

Joe White