20

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!

7
  • I don't see why you are forced to use global variables, but if so then yes there certainly must be a better approach. Maybe factories to create common components combined with composition instead of inheritance?
    – stijn
    Dec 12, 2011 at 8:13
  • Your entire design is flawed. If you already understand what you want to do isn't supported by Visual Studio that should tell you something.
    – Ramhound
    Dec 12, 2011 at 16:18
  • 1
    @Ramhound It is supported by visual studio, just not well. This is how Microsoft tells you to do it. I just find it to be a pain in the A. msdn.microsoft.com/en-us/library/aa983613(v=vs.71).aspx Anyhow, if you have a better idea, I am all eyes. Dec 12, 2011 at 17:14
  • @stijn I suppose I could have a method in each base form that will load the control states into another instance. i.e. LoadStatesToNewInstance(BaseForm instance). I could call it anytime I want to show a new form of that base type. form_I_am_about_to_hide.LoadStatesToNewInstance(this); Dec 12, 2011 at 17:19
  • I am not a .net developer, can you expand on point number 1? I might have a solution Dec 15, 2011 at 19:05

3 Answers 3

6
+50
  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");
            }
        }
    }
}
4
5

I maintain a new WinForms/WPF application based on the MVVM pattern and Update Controls. It started as WinForms, then I created a WPF version because of the marketing importance of a UI that looks good. I thought it would be an interesting design constraint to maintain an application that supported two completely different UI technologies with the same backend code (viewmodels and models), and I have to say I'm quite satisfied with this approach.

In my app, whenever I need to share functionality between parts of the UI, I use custom controls or user controls (classes derived from WPF UserControl or WinForms UserControl). In the WinForms version there is a UserControl inside another UserControl inside a derived class of TabPage, which is finally inside a tab control on the main form. The WPF version actually has UserControls nested one level deeper. Using custom controls, you can easily compose a new UI out of UserControls that you created earlier.

Since I use the MVVM pattern, I put as much program logic as possible into the ViewModel (including the Presentation/Navigation Model) or the Model (depending on whether the code is related to the user interface or not), but since the same ViewModel is used by both a WinForms view and a WPF view, the ViewModel is not allowed to contain code that is designed for WinForms or WPF or that directly interacts with the UI; such code MUST go into the view.

Also in the MVVM pattern, UI objects should avoid interacting with each other! Instead they interact with the viewmodels. For instance a TextBox would not ask a nearby ListBox which item is selected; instead the listbox would save a reference to the currently selected item somewhere in the viewmodel layer, and the TextBox would query the viewmodel layer to find out what is selected right now. This solves your problem about finding out which button was pushed in one form from within a different form. You just share a Navigation Model object (which is part of the application's viewmodel layer) between the two forms and put a property in that object that represents which button was pushed.

WinForms itself doesn't support the MVVM pattern very well, but Update Controls provides its own unique approach MVVM as a library that sits on top of WinForms.

In my opinion, this approach to program design works very well and I plan to use it on future projects. The reasons it works so well are (1) that Update Controls manages dependencies automatically, and (2) that it is usually very clear how you should structure your code: all code that interacts with UI objects belongs in the View, all code that is UI-related but does not HAVE to interact with UI objects belongs in the ViewModel. Quite often you will split your code into two parts, one part for the View and another part for the ViewModel. I had some difficulty designing context menus in my system, but eventually I came up with a design for that too.

I have raving about Update Controls on my blog too. This approach does take a lot of getting used to, though, and in large-scale apps (e.g. if your listboxes contain thousands of items) you may encounter performance problems due to current limitations of automatic dependency management.

3

I am going to answer this even though you have already accepted an answer.

As others have pointed out, I do not understand why you had to use anything static; this sounds like you have been doing something very wrongly.

Anyway, I have had the same problem as you: in my WinForms application I have several forms that share some functionality and some controls. Also, all of my forms already derive from a base form (let's call it "MyForm") which adds framework-level functionality (irrespective of application.) The Forms Designer of Visual Studio supposedly supports editing forms that inherit from other forms, but in practice it works only as long as your forms do nothing more than "Hello, world! -- OK -- Cancel".

What I ended up doing is this: I keep my common base class "MyForm", which is quite complex, and I continue to derive all of my application's forms from it. However, I do absolutely nothing in these forms, so the VS Forms Designer has no problems editing them. These forms consist solely of the code that the Forms Designer generates for them. Then, I have a separate parallel hierarchy of objects that I call "Surrogates" which contain all the application-specific functionality, like initializing the controls, handling events generated by the form and the controls, etc. There is a one-to-one correspondence between surrogate classes and dialogs in my application: there is a base surrogate class which corresponds to "MyForm", then another surrogate class is derived which corresponds to "MyApplicationForm", and then a whole bunch of other surrogate classes are derived, which correspond to each and every different form that my application displays.

Each surrogate accepts as a construction-time parameter a specific type of form, and it attaches itself to it by registering for its events. It also delegates to base, all the way to "MySurrogate" which accepts a "MyForm". That surrogate registers with the "Disposed" event of the form, so that when the form gets destroyed, the surrogate invokes an overridable on itself so that it, and all of its descendants, can perform cleanup. (Deregister from events, etc.)

It has been working fine so far.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.