views:

1152

answers:

8

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.

+5  A: 

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.

Bob Nadler
Unless there is a detail that we don't have I think that it is this simple... This is what I was going to say.
Brian ONeil
This doesn't answer the question. I want ICustomerName to be the *only* option for accessing the UserControl's variable. The idea is that a developer doesn't have to "just remember" to cast it. I've edited the question to indicate one way to do this, but I'm looking for other ways that don't require a base form.
Kyralessa
A: 

you could as well do as Bob said but assign all your member variables in the constructor, then you have it in one place.

Botz3000
That still leaves the programmer the option to absentmindedly use the UserControl reference instead of the interface reference. I want the interface reference to be the only member referring to the UserControl, if at all possible.
Kyralessa
+3  A: 

After you add the UserControl using the designer, you can set GenerateMember to false in the Properties window to suppress generation of a member.

You could then use some other technique in the constructor to assign your cName reference, e.g.:

foreach(Control control in this.Controls)
{
    cName = control as ICustomerName;
    if (cName != null) break;
}

cName would then be the only reference to the UserControl.

Joe
This is the first *good* possibility I've seen. GenerateMember didn't even occur to me. To my mind, a better way to do it than the way you describe is to use: cName = this.Controls["controlName"] as ICustomerName;It has the definite weakness of breaking if the control is renamed, though, so it's still not a perfect solution. The reason I don't like the casting solution you mention is that I might have more than one of the same customer UserControl on the same form. In fact, in the project I'm working on, I know of at least one definite case like that.
Kyralessa
Yes this was meant to be just one example of a tehnique to obtain the desired reference from the Controls collection. If you have more than one instance of a control, you're right that you'll need to use some other characteristic of the control, e.g. the name, to distinguish them. The "best" solution will depend on your app.
Joe
+5  A: 

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.

Joe White
Actually, it seems like I'd only have to do it once for any UserControl, and then interject this new member-hiding class in the inheritance tree between UserControl and my various derived controls. If that were the case, it wouldn't be an unbearable amount of work. On the other hand, any amount of work is too much if you don't really have to do it.
Kyralessa
That is some very good information.
hmcclungiii
A: 

It almost seems like you want to implement a mediator pattern. Instead of having to deal with each of the bazillion UserControls directly, you'd interact with them through the mediator. Each mediator would define the slim interface you want to see from each control. This would reduce the overall complexity by making your design more explicit and concise. For example, you wouldn't need the 20 properties and 50 methods available on one of your controls. Instead you'd deal with the mediator for that control which defines the 2 properties and 5 methods you really care about. Everything would still show up in the designer, but other parts of your app would not be interacting with those controls -- they'd interact with the mediators.

One of the big advantages to this approach is it greatly simplifies your maintenance. If you decide the MyCrappyUserControl needs to be rewritten because the implementation is bad, you just need to update the mediator class for that control. All the other classes that interact with the control do so through the mediator and would be unchanged.

Ultimately it comes down to discipline: you and your team need to be disciplined enough to use the mediators/interfaces/whatever instead of the directly hitting the controls. Institute an over the shoulder code review by a leader programmer if your team is on the low end of the discipline scale.

Mike Post
I'm looking for a solution that won't require that much discipline, because I know what happens when a solution requires discipline from the average programmer. We have too many things to think about and remember already; a "you just have to remember" solution is no solution.
Kyralessa
This would just become part of your standard design for the code your shop produces. If you're turning programmers loose into your code without first briefing them on your design, this problem of UserControls exposing too many properties is going to be the least of your concerns.
Mike Post
Dude, I'm not the guy in charge. I can't enforce coding standards on others. I'm trying to find a subtle but effective way to improve design without forcing people to do things a certain way. To lead by example rather than by edict, perhaps.
Kyralessa
Not every programming problem has a technical solution. Sometimes the right answer is a people solution.
Mike Post
I agree in general. But this programming problem has a lot of technical solutions. Some are better than others.
Kyralessa
+1  A: 

You could write an extension method that would allow you to return any controls on the form that implement an interface.

public static class FormExtensions
{
    public static IDictionary<string, T> GetControlsOf<T>(this Form form) 
           where T: class
    {
        var result = new Dictionary<string, T>();
        foreach (var control in form.Controls)
        {
            if ((control as T) != null)
                result.Add((control as T).Tag, control as T);
        }
        return result;
    }
}

Then in your form you could call it whereever you want by:

this.GetControlsOf<ICustomerName>()["NameOfControlHere"];

In the event that it returns more than one user control you would need to handle that some how, perhaps by adding Tag property to the interface to uniquely keep track of each user control or something, like so

public partial class UserControl1 : UserControl, ICustomerName
{
     public string Tag { get { return this.Name; } }
}

You can then drag and drop the user controls onto your form from the designer. Tag will always return the name of your control, which will allow you to directly access the control through the IDictionary's interface. You're developers could put whatever unique identifier they want in the name of the control, and it would carry through to the interface.

Also, it should be noted that this approach will ALSO allow you to use this on ALL forms in your solution.

The only other thing you would need to do is set your GenerateMember to false.

Joseph
Very interesting. It's similar to Joe's solution, but a bit more elegant. It has the same weakness as his (multiple controls that have the same interface), but I think passing in the UserControl name would alleviate that, though it's still a problem if the control name is changed.
Kyralessa
@Kyralessa actually there is no problem with multiple controls, you can use a dictionary instead, and index it based off the name of the control. Then you can remove the extra tag all together. I'll modify my sample to illustrate.
Joseph
If you're going to have Tag return the Name property, why not just use Name?
Kyralessa
Because the Name property is not part of the interface. If, however, you only want to use it for the dictionary purposes, then you could drop the Tag property all together. Note that if you use it as a read only property as I've illustrated above, it won't show up in the designer, so you can instructor your developers to use the Name property only and everything will work out correctly.
Joseph
+4  A: 

'I want ICustomerName to be the only option for accessing the UserControl's variable. The idea is that a developer doesn't have to "just remember" to cast it.'

The problem you are having is that you have two completely divergent uses for your form and the controls it hosts. There is no trick built into Visual Studio or winforms which solves this neatly for you. It may be possible, but there is a much cleaner and object oriented way to separate the two methods of interacting with the controls.

If you want to hide the fact that these objects inherit from UserControl, and just want to treat them as IDoSomeThingYouShouldDealWith, you need to separate the logic that deals with the presentation concerns (designer + UI logic) from your business logic.

Your form class, should rightly deal with the controls as UserControls, docking, anchoring etc etc, nothing special here. You should put all the logic that needs to deal with ICustomerName.FirstName = etc into a completely separate class. This class doesn't care or know about fonts and layout, it just knows that there is another instance that can present a customer name; or a DateTime as a 'date of birth choosing' control properly etc.

This is a really lame example, but I have to go right now. You should be able to get the idea covered here in more detail:

public interface ICustomerName
    {
        void ShowName(string theName);
    }

    public partial class Form1 : Form, ICustomerName
    {
        public Form1()
        {
            InitializeComponent();
        }

        #region ICustomerName Members

        public void ShowName(string theName)
        {
            //Gets all controls that show customer names and sets the Text propert  
            //totheName
        }

        #endregion
    }

    //developers program logic into this class 
    public class Form1Controller
    {
        public Form1Controller(ICustomerName theForm) //only sees ICustomerName methods
        {
            //Look, i can't even see the Form object from here
            theForm.ShowName("Amazing Name");
        }
    }
Noel Kennedy
Noel, I know all about Model-View-Controller, Model-View-Presenter, and the rest. But I'm not at liberty to change the app that radically any time soon. I'm looking for something that will work with the app as it is. There are already several possibilities listed here that can do what I want, to varying degrees of completeness and ease of use. If what I'm looking for here were *easy* to do, I'd have already figured out how to do it, and I wouldn't have bothered to post a question about it on StackOverflow.
Kyralessa
Hi Kyralessa, thanks for the feedback. Not sure why you are looking for a more complex solution, which I'm sure you would agree, are hacks or workarounds to greater or lesser extent. If you are able to (and I accept you might not be at liberty), I would try to find a way to refactor to a UI pattern, even if you can't go to it straight away. For example, you could create the controller in the constructor of the view, which is not so far awat from what Joe is suggesting. You can also move accross functionality and logic into the controller bit by bit so there would be no big bang.
Noel Kennedy
It's a work project, not a personal project, and people aren't always free in work projects to refactor to our heart's content. And I'm not specifically looking for a complex solution. I'm looking for a good solution, and it's OK if it's complex as long as it's good. I'm also looking for a solution to a very specific problem. My question isn't "How do I fix this app?" My question is "How do I access UserControls only through an interface, but still have them show up in the Designer?"
Kyralessa
A: 

Assume that MyUserControl is defined like this:

class MyUserControl : UserControl, IMyInterface
{
    // ...
}

Then in your form, you should have something like this:

public class MyForm : Form
{
    IMyInterface cName;

    public MyForm()
    {
        InitializeComponent();

        cName = new MyUserControl();
        Controls.Add((UserControl)cName);
    }
}

This way, cName is the only way to access this instance of our usercontrol.

M. Jahedbozorgan
This doesn't let you see MyUserControl in the designer though.
Mike Post
If you put this code in the designer file, you will be able to see the control. The problem is that your changes will be lost if it is regenerated. This can be solved using a macro.
M. Jahedbozorgan
This is exactly the solution I proposed in my original question. As such, it's no help at all.
Kyralessa