Click here to Skip to main content
15,867,568 members
Articles / Programming Languages / C#
Article

Inheriting a Form from an Abstract Class (and Making it Work in the Designer)

Rate me:
Please Sign up or sign in to vote.
3.10/5 (13 votes)
20 Jan 2008CPOL6 min read 85.1K   431   28   14
Visual Studio's and SharpDevelop's Designers can't directly edit Forms inherited from abstract classes. Several workarounds are possible. In this article, a simple and quite confortable one is presented.

Introduction

For those who work with .NET inheritance, it is quite common to write abstract classes in order to store portions of code which will be shared by sibling subclasses and therefore enforce the code reuse. Obviously, sibling subclasses may include System.Windows.Forms.Form classes; for example, an application using an MVC pattern may have forms inheriting from an abstract class implementing a View. Most commonly, base abstract classes for forms can be useful to share common behaviours between forms (a title, a set of buttons or methods and so on).

Problems appear when you try to edit a Form inherited from an abstract class with Visual Studio Designer: an error message fires, stating that "The designer must create an instance but it cannot because the type is declared as abstract". The same error is fired by SharpDevelop, revealing that this is not just a Visual Studio bug but, probably, a Framework architectural issue.

AbstractForms

Delphi's and Borland Builder C++'s designers perfectly handle abstract based forms but .NET doesn't. Moreover, Microsoft doesn't plan to solve the inability to cope with abstract forms in upcoming Visual Studio versions.

To solve this problem, a few workarounds can be used: in this article I will introduce the one I consider to be the most comfortable to use, but which is neither the best nor the most elegant one.

The trick consists in inheriting the form from an abstract class only in release builds and in writing a suitable concrete version of it which can be used only at design time and in debug builds.

Background

Understanding the reason for this behaviour may not be strictly necessary but it could be nevertheless interesting.

We could believe that, in order to edit a Form, the Designer just needs to directly instantiate it. Far from it, the error message clearly states that the Designer needs to instantiate the base class rather than the derived one but, being the first one abstract, no instance of it can be created.

As a matter of fact, when the Designer handles with Forms, it first uses Reflection to create an instance of the base class (System.Windows.Forms.Form), then it parses the InitalizeComponent method of the derived class and it finally applies all the derived class's properties on the base class's instance, hence allowing the user to modify the derived class. In short, the user always works with a base class modified on-the-fly.

In his excellent article Brian Pepin lists some of the fixes Microsoft took into consideration and explains in details why none of them have been adopted.

Approaches

Some articles propose to change the OO architecture in order to prevent the problem from appearing.

The widespread solution is to avoid the use of abstract methods (which would tag the entire base class as abstract) and use instead empty virtual methods. Doing that, the class behaves like abstract, but works in the Designer too. The main problem with this approach is related to the differences between virtual and abstract methods.

Virtual methods allow subclasses to override the base method (eventually, empty) with their own implementation, while abstract ones, which are implicitly virtual, strictly require the class to provide an implementation using the same signature. Using virtual methods may lead the developer to incidentally forget to provide an implementation: in this case, no alert by the compiler would be fired. A further suggestion could be, then, to always write virtual methods throwing a NotImplementedException to remind the developer to override the method. It would happen only at run-time and you may agree that a solution enforced by the compiler would be much more comfortable.

It's not generally a good idea to change the architecture in order to just get around a problem: an object-oriented hierarchy is supposed to have been chosen with a strong, clear and correct design architecture in mind and an approach which can preserve the exact hierarchy is generally a better approach.

Brian Pepin (again) suggests the best solution which both preserves the exact OO design and completely solves the problem. Unfortunately, Brian's approach only works with Visual Studio Whidbey (2005) and, even if it's almost perfect and really elegant, it's considerably less immediate than the following solution. Let me stress again that Brian's solution is the real good one. The one described here should be considered just an enhanced version of the virtual methods solution, with the only advantage of being very simple and of working on every Visual Studio version.

The Solution

The weakness of the virtual methods solution is the lack of compiler's alerts in the case of a missing method implementation. As stated in the introduction, you may provide two classes, one abstract and the other one concrete, to be used respectively for build and for debug releases. Instead of writing two separate files, it's easier to use an #if compiler directive which switches between the two class definitions:

C#
namespace CodeProject.AbstractForms
{
    #if DEBUG
        public class AbstractForm : Form
        {
    #else
        public abstract class AbstractForm: Form
        {
    #endif
        
        #if DEBUG
            public virtual void MyMethod()
            {
                throw new NotImplementedException();
            }
        #else
            public abstract void MyMethod();
        #endif
        
        }
}

You can switch between Debug and Build releases in Visual Studio with a bunch of clicks (few more with SharpDevelop), but you should remember to recompile your project and close and reopen your form in order to inform the Designer about the changes.

With this simple trick, a release build will throw a compiling error for a missing overridden method, while in debug mode every form will be perfectly editable.

Implementing the Approach with SharpDevelop

Visual Studio is smart enough to correctly deal with curly brackets, ignoring open parenthesis in not enabled portions of code; SharpDevelop is less efficient and throws errors declaring not closed brackets.

Since I'm a big SharpDevelop's fan, I found it very frustrating. The only workaround I managed to obtain for SharpDevelop is not that elegant, but it's perfectly working. The trick is avoiding curly brackets to appear inside a #if statement, like in the next sample code:

C#
namespace CodeProject.AbstractForms
{
    public partial 
    #if RELEASE
        abstract
    #endif
    class AbstractForm : Form
    {
        public AbstractForm()
        {
            InitializeComponent();
        }

        public
            #if DEBUG
            virtual 
            
            void MyMethod()
            {
                throw new NotImplementedException();
            }
            #else
            abstract void MyMethod();
            #endif
    }
}

Using the Code

In the sample code, the form DerivedForm inherits from AbstractForm, which is concrete in debug builds and abstract in release ones. AbstractForm defines respectively MyMethod as a virtual method (throwing an NotImplementedException) or an abstract method.

You can comment the #define directive in the first line of the sample code in order to disable the implementation of MyMethod().

C#
#define IMPLEMENTMYMETHOD

#if IMPLEMENTMYMETHOD
        public  override void MyMethod()
        {
            //Do something
            MessageBox.Show("MyMethod called");
        }
#endif

You can verify that in debug mode, both DerivedForm and AbstractCode can be successfully modified in Visual Studio Form Designer. As explained, if MyMethod() is not implemented in the derived class, a release build compilation fails.

In DerivedClass there's a little piece of code doing some reflection in order to find out which version of MyMethod() will be called:

C#
private void CallMyMethod_Click(object sender, EventArgs e)
{
    Type type = this.GetType();
    MethodInfo[] methods = type.GetMethods();
 
    foreach (MethodInfo info in methods)
    {
        if (info.Name == "MyMethod")
        {
 
            if (info.DeclaringType.FullName == 
                    "CodeProject.AbstractForms.AbstractForm")
            {
                // MyMethod implemented only 
                // in the base class as virtual
            }
            else
            {
                // MyMethod implemented in the derived class.
            }
            info.Invoke(this, null);
            break;
        }
    }    
}

History

  • 2008.09.01 First version of the article
  • 2008.15.01 Fixed the wrong Whidbey version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Italy Italy
I'm a freelance software developer working on C#, PHP and CakePHP. At the moment I'm mainly working for an Italian software house on a quite big software based on .NET, PostGreSQL and NHibernate.

Comments and Discussions

 
GeneralA similar solution Pin
sdstorm27-Feb-10 4:48
sdstorm27-Feb-10 4:48 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.