Best Design for Windows Forms with Common Functionality

cinheritancenetobject-oriented-designwinforms

In the past, I have used inheritance to allow the extension of Windows forms in my application. If all of my forms would have common controls, artwork, and functionality, I would create a base form implementing the common controls and functionality and then allow other controls to inherit from that base form. However, I have run into a few problems with that design.

  1. Controls can only be in one container at a time, so any static controls you have will be tricky. For example: Suppose you had a base form called BaseForm which contained a TreeView which you make protected and static so that all of the other (derived) instances of this class can modify and display the same TreeView. This would not work for multiple classes inheriting from BaseForm, because that TreeView can only be in one container at a time. It would likely be on the last form initialized. Though every instance could edit the control, it would only display in one at a given time. Of course, there are work-arounds, but they are all ugly. (This seems to be a really bad design to me. Why can't multiple containers store pointers to the same object? Anyhow, it is what it is.)

  2. State between forms, that is, button states, label text, etc., I have to use global variables for and reset the states on Load.

  3. This isn't really supported by Visual Studio's designer very well.

Is there a better, yet still easily maintainable design to use? Or is form inheritance still the best approach?

Update
I went from looking at MVC to MVP to the Observer Pattern to the Event Pattern. Here is what I am thinking for the moment, please critique:

My BaseForm class will only contain the controls, and events connected to those controls. All events that need any sort of logic to handle them will pass immediately to the BaseFormPresenter class. This class will handle the data from the UI, perform any logical operations, and then update the BaseFormModel. The Model will expose events, which will fire upon state changes, to the Presenter class, which it will subscribe (or observe) to. When the Presenter receives the event notification, it will perform any logic, and then the Presenter will modify the View accordingly.

There will only be one of each Model class in memory, but there could potentially be many instances of the BaseForm and therefore the BaseFormPresenter. This would solve my problem of synchronizing each instance of the BaseForm to the same data model.

Questions:

Which layer should store stuff like, the last pressed button, so that I can keep it highlighted for the user (like in a CSS menu) between forms?

Please criticize this design. Thanks for your help!

Best Answer

  1. I don't know why you need static controls. Maybe you know something I don't. I've used a lot of visual inheritance but I've never seen static controls to be necessary. If you have a common treeview control, let every form instance have its own instance of the control, and share a single instance of the data bound to the treeviews.

  2. Sharing control state (as opposed to data) between forms is also an unusual requirement. Are you sure FormB really needs to know about the state of buttons on FormA?Consider the MVP or MVC designs. Think of each form as a dumb "view" that knows nothing about the other views or even the application itself. Supervise each view with a smart presenter/controller. If it makes sense, a single presenter can supervise several views. Associate a state object with each view. If you have some some state which needs to be shared between views, let the presenter(s) mediate this (and consider databinding - see below).

  3. Agreed, Visual Studio will give you headaches. When considering form or usercontrol inheritance, you need to carefully weigh the benefits against the potential (and probable) cost of wrestling with the form designer's frustrating quirks and limitations. I suggest keeping form inheritance to a minimum - use it only when the payoff is high. Keep in mind that, as an alternative to subclassing, you can create common "base" form and simply instantiate it once for each would-be "child" and then customize it on-the-fly. This makes sense when differences between each version of the form are minor compared to the shared aspects. (IOW: complex base form, only-slighty-more-complex child forms)

Do make use of usercontrols when it helps you prevent significant duplication of UI development. Consider usercontrol inheritance but apply the same considerations as for form inheritance.

I think the most important advice I can offer is, if you don't currently employ some form of the view/controller pattern, I strongly encourage you to start doing so. It forces you to learn and appreciate the benefits of loose-couping and layer separation.

response to your update

Which layer should store stuff like, the last pressed button, so that I can keep it highlighted for the user...

You can share state between views much like you would share state between a presenter and its view. Create a special class SharedViewState. For simplicity you can make it a singleton, or you can instantiate it in the main presenter and pass it to all views (via their presenters) from there. When the state is associated with controls, use data binding where possible. Most Control properties can be data-bound. For example the BackColor property of a Button could be bound to a property of your SharedViewState class. If you do this binding on all forms which have identical buttons, you can highlight Button1 on all forms just by setting SharedViewState.Button1BackColor = someColor.

If you aren't familiar with WinForms databinding, hit MSDN and do some reading. It's not difficult. Learn about INotifyPropertyChanged and you're halfway there.

Here's a typical implementation of a viewstate class with the Button1BackColor property as an example:

public class SharedViewState : INotifyPropertyChanged
{
    // boilerplate INotifyPropertyChanged stuff
    public event PropertyChangedEventHandler PropertyChanged;
    protected void NotifyPropertyChanged(string info)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }

    // example of a property for data-binding
    private Color button1BackColor;
    public Color Button1BackColor
    {
        get { return button1BackColor; }
        set
        {
            if (value != button1BackColor)
            {
                button1BackColor = value;
                NotifyPropertyChanged("Button1BackColor");
            }
        }
    }
}
Related Topic