Abstract UserControl inheritance in Visual Studio designer
Asked Answered
C

9

43
abstract class CustomControl : UserControl 
{
    protected abstract int DoStuff();
}

class DetailControl : CustomControl
{
    protected override int DoStuff()
    { 
        // do stuff
        return result;
    }
}

I dropped a DetailControl in a form. It renders correctly at runtime, but the designer displays an error and won't open because the base user control is abstract.

For the moment, I'm contemplating the following patch, which seems pretty wrong to me, as I want the child classes to be forced to implement the method.

class CustomControl : UserControl 
{
    protected virtual int DoStuff()
    {
        throw new InvalidOperationException("This method must be overriden.");
    }
}

class DetailControl : CustomControl
{
    protected override int DoStuff()
    { 
        // do stuff
        return result;
    }
}

Anyone has a better idea on how to work my way around this problem?

Chace answered 25/7, 2011 at 13:47 Comment(2)
I had similar recently and went for having a separate interface that provided the methods I wanted. I also tried changing the base class to UserControl when I needed to edit it but it was messy.Kingbolt
Possible duplicate of How can I get Visual Studio 2008 Windows Forms designer to render a Form that implements an abstract base class?Owain
T
21

You can use a TypeDescriptionProviderAttribute to provide a concrete design-time implementation for your abstract base class. See http://wonkitect.wordpress.com/2008/06/20/using-visual-studio-whidbey-to-design-abstract-forms/ for details.

Teem answered 25/7, 2011 at 13:59 Comment(2)
It works only when I open Visual Studio for the first time. Once I make any change to code and rebuild the project it stops working. Anyway thanks.Aquarius
@Aquarius it's possible that was a bug in devenv which has since been fixed. I'm using VS2017 right now and I can open the designer, close the designer, make a code change, recompile the project and the designer will still open.Aylward
J
60

What we want

First, let's define the final class and the base abstract class.

public class MyControl : AbstractControl
...
public abstract class AbstractControl : UserControl // Also works for Form
...

Now all we need is a Description provider.

public class AbstractControlDescriptionProvider<TAbstract, TBase> : TypeDescriptionProvider
{
    public AbstractControlDescriptionProvider()
        : base(TypeDescriptor.GetProvider(typeof(TAbstract)))
    {
    }

    public override Type GetReflectionType(Type objectType, object instance)
    {
        if (objectType == typeof(TAbstract))
            return typeof(TBase);

        return base.GetReflectionType(objectType, instance);
    }

    public override object CreateInstance(IServiceProvider provider, Type objectType, Type[] argTypes, object[] args)
    {
        if (objectType == typeof(TAbstract))
            objectType = typeof(TBase);

        return base.CreateInstance(provider, objectType, argTypes, args);
    }
}

Finally we just apply a TypeDescriptionProvider attribute to the Abstract control.

[TypeDescriptionProvider(typeof(AbstractControlDescriptionProvider<AbstractControl, UserControl>))]
public abstract class AbstractControl : UserControl
...

And that's it. No middle control required.

And the provider class can be applied to as many Abstract bases as we want in the same solution.

Javier answered 15/7, 2013 at 18:35 Comment(6)
This worked for me, although in my case I have two levels of abstract classes between my concrete subclass and UserControl. For both of the abstract classes I needed to provide the TypeDescriptionProvider with a TBase of UserControl. I renamed TBase to TFirstConcreteBase for explicit clarity for whoever comes after me. I had hoped the two original TBase declarations would chain together to get the right concrete base, but not so. Thanks for the help, @Juan.Hag
You also need to recompile your projekt and close Visual Studio for the designer message to go awayPsychopathology
I tried this using an abstract control that also uses generics. I initially thought maybe it was working, but ultimately I couldn't get Designer to open either the abstract base control or the derived controls. Closing and reopening VS made it able to forms containing the derived class. Finally decided to punt and not make my base class abstract!Gain
This worked for me until I closed Visual Studio. After closing and opening VS again, I see the same error.Aquarius
and it is useful to put the following key in the config file. <packages> ... <add key="EnableOptimizedDesignerReloading" value="false" /> </packages>Haystack
Unfortunately this doesn't work on generic abstract user controls, which is also addressed in https://mcmap.net/q/206356/-abstract-generic-usercontrol-inheritance-in-visual-studio-designer/2505186, which is referring to exactly this answer. AND it has the drawback of not showing the controls that already were added in the abstract user control. So for me the only solution was using an intermediate user control, like described in https://mcmap.net/q/206357/-generic-base-class-for-winform-usercontrolPalomino
T
21

You can use a TypeDescriptionProviderAttribute to provide a concrete design-time implementation for your abstract base class. See http://wonkitect.wordpress.com/2008/06/20/using-visual-studio-whidbey-to-design-abstract-forms/ for details.

Teem answered 25/7, 2011 at 13:59 Comment(2)
It works only when I open Visual Studio for the first time. Once I make any change to code and rebuild the project it stops working. Anyway thanks.Aquarius
@Aquarius it's possible that was a bug in devenv which has since been fixed. I'm using VS2017 right now and I can open the designer, close the designer, make a code change, recompile the project and the designer will still open.Aylward
S
6

Another way to solve this is using pre-processing directives.

#if DEBUG
  public class UserControlAdmonEntidad : UserControl, IAdmonEntidad
#else
  public abstract class UserControlAdmonEntidad : UserControl, IAdmonEntidad
#endif
  {
    ...
    #if DEBUG
    public virtual object DoSomething()
    {
        throw new NotImplementedException("This method must be implemented!!!");
    }
    #else
    public abstract object DoSomething();
    #endif

    ...
  }

See this link for more information regarding this topic: Inheriting a Form from an Abstract Class (and Making it Work in the Designer)

The same solution was also mentioned in this MSDN forum thread, in a briefer way: UserControl, Inherited Control, Abstract class, (C#)

Maybe is not the cleaner solution, but it's still the shortest I have found.

Sufism answered 23/6, 2012 at 22:47 Comment(0)
Y
3

Even though this question is years old, I'd like to add what I've found.

If you don't want to touch your abstract base class, you can do this hack:

abstract class CustomControl : UserControl 
{
    protected abstract int DoStuff();
}

class BaseDetailControl : CustomControl
{
    protected override int DoStuff()
    {
        throw new InvalidOperationException("This method must be overriden.");
    }
}

class DetailControl : BaseDetailControl
{
    protected override int DoStuff()
    { 
        // do stuff
        return result;
    }
}

This way, your form inherits from a non-abstract base form and it's displayed in the designer! And you keep your abstract form, but only one more level up in the inheritance. Strange, isn't it?

Yeryerevan answered 20/11, 2014 at 12:34 Comment(2)
This doesn't really help because is eliminates the benefit of having an abstract base class, the requirement that you implement the method. Throwing an exception only enforces the requirement at run time.Gain
Yes, it's not ideal. But you can still keep your abstract class to be used throughout your application as you planned. And this intermediate helper class, just used for this IDE limitation, can even be placed in the same .cs file to somehow make it closer to what you would do without it: that is, implement all abstract methods in that file, being the only difference that you have to do it twice: the throwing one and the real one. Is there a better option?Yeryerevan
C
2

I couldn't make work the solution of 'Nicole Calinoiu'. But there is an other easy way directly in visual studio:)

  1. Create new project
  2. Add new element 'userControl' and add one button for example
  3. Add new element 'userControl' Inhereted UserControl then select the inhereted userControl.

More details here : 'http://www.codeproject.com/Articles/20845/How-to-derive-from-a-parent-form

Ciapha answered 21/1, 2015 at 15:57 Comment(0)
G
1

The following is a generic solution that works for me, mostly. It is based on the article from another answer. Sometimes it will work, and I can design my UserControl, and then later I'll open the file and it will give the "The designer must create an instance of type 'MyApp.UserControlBase' but it cannot because the type is declared as abstract." I think I can fix it by cleaning, closing VS, reopening VS, and rebuilding. Right now it seems to be behaving. Good luck.

namespace MyApp
{
    using System;
    using System.ComponentModel;

    /// <summary>
    /// Replaces a class  of <typeparamref name="T"/> with a class of
    /// <typeparamref name="TReplace"/> during design.  Useful for
    /// replacing abstract <see cref="Component"/>s with mock concrete
    /// subclasses so that designer doesn't complain about trying to instantiate
    /// abstract classes (designer does this when you try to instantiate
    /// a class that derives from the abstract <see cref="Component"/>.
    /// 
    /// To use, apply a <see cref="TypeDescriptionProviderAttribute"/> to the 
    /// class <typeparamref name="T"/>, and instantiate the attribute with
    /// <code>SwitchTypeDescriptionProvider<T, TReplace>)</code>.
    /// 
    /// E.g.:
    /// <code>
    /// [TypeDescriptionProvider(typeof(ReplaceTypeDescriptionProvider<T, TReplace>))]
    /// public abstract class T
    /// {
    ///     // abstract members, etc
    /// }
    /// 
    /// public class TReplace : T
    /// {
    ///     // Implement <typeparamref name="T"/>'s abstract members.
    /// }
    /// </code>
    /// 
    /// </summary>
    /// <typeparam name="T">
    /// The type replaced, and the type to which the 
    /// <see cref="TypeDescriptionProviderAttribute"/> must be
    /// applied
    /// </typeparam>
    /// <typeparam name="TReplace">
    /// The type that replaces <typeparamref name="T"/>.
    /// </typeparam>
    class ReplaceTypeDescriptionProvider<T, TReplace> : TypeDescriptionProvider
    {
        public ReplaceTypeDescriptionProvider() :
            base(TypeDescriptor.GetProvider(typeof(T)))
        {
            // Nada
        }

        public override Type GetReflectionType(Type objectType, object instance)
        {
            if (objectType == typeof(T))
            {
                return typeof(TReplace);
            }
            return base.GetReflectionType(objectType, instance);
        }

        public override object CreateInstance(
            IServiceProvider provider,
            Type objectType,
            Type[] argTypes,
            object[] args)
        {

            if (objectType == typeof(T))
            {
                objectType = typeof(TReplace);
            }

            return base.CreateInstance(provider, objectType, argTypes, args);
        }
    }
}
Graph answered 26/9, 2012 at 4:57 Comment(3)
This seems to work great for editing the abstract UI in the designer but when I inherit from the abstract class and try to edit the sub class I get the same error about not being able to instantiate the abstract base class. Am I doing something stupid?Kasper
I have the same problem. Try something like building solution, opening/editing the derived control (whether successful or not), closing the derived control, cleaning the solution, closing visual studio, reopen visual studio and solution, rebuild solution. The success/failure for me is intermittent.Graph
+1 Yes cleaning and reopening after declaring the type provider workedLudwigg
A
0

I just make the abstract base class into a concrete one by defining the "abstract" methods as virtual, and throwing an exception in them, just in case any naughty derived classes try to call Base implementation.

e.g.

    class Base : UserControl
    {
        protected virtual void BlowUp()
        {
            throw new NotSupportedException("This method MUST be overriden by ALL derived classes.");
        }

    class Derived : Base
    {
        protected override void BlowUp()
        {
            // Do stuff, but don't call base implementation,
            // just like you wouldn't (can't actually) if the Base was really abstract. 
            // BTW - doesn't blow up any more  ;)
        }

The main practical difference between this and a real abstract base class is you get run time errors when calling the base implementation - whereas if the Base was actually abstract, the compiler would disallow an accidental calls to the Base class implementation. Which isn't a big deal for me and allows me to use the designer without worrying about more complex and time consuming work arounds suggested by others...

PS - Akuma - you should be able to edit your abstract UI class in the designer. I don't have time to check this right now, but it is my understanding that the designer only needs to instantiate the BASE class. As long as the base of the class you are designing is concrete, it doesn't matter what the designed class is.

Amitosis answered 14/11, 2012 at 15:7 Comment(1)
Isn't this exactly the same as the patch I posted initially in my question?Chace
T
0

I resolved this issue in UWP in my custom control.

My Case

public abstract class BaseModel : DependencyObject 

{
...
}

public class MainModel : BaseModel

{

public bool ShowLabel

{
    get{ return (bool)GetValue(ShowLabelProperty); }
    set{ SetValue(ShowLabelProperty, value) }
}

public static readonly DependencyProperty ShowLabelProperty =

    DependencyProperty.Register("ShowLabel",typeof(bool), typeof(MainModel), new PropertyMetadata(false));

}

Declaration

< MyCustomControl:MainModel ShowLabel=True />

Solution

Just override a dummy style in the generic resources.

<Style TargetType="local:MainModel" />

Regards,

Samuel

Triturable answered 24/4, 2017 at 12:58 Comment(0)
T
0

I was new to this in UWP and it was driving me nuts. I didn't think of an abstract base class for a UserControl. I went in a different direction. I created a non-xaml Helper class ... HBase. Each View, say VContract, had a corresponding Helper called HContract. All the speciality code for each view was lodged there. The conversations that would have been between the ViewModel VMContract and View VContract now pass through HContract. We can enforce how HWhatever behaves with IHBase. It's not truly an answer to the OP's question, but does show an alternative approach. All the Views are now basically shells. Whether you x:Bind to the VContract or HContract is a decision for you to make. I chose the VContract way and in the end I think that was a mistake.

The UWP problem with exceptions in Design Mode is now easily fixed with:

if (false == Windows.ApplicationModel.DesignMode.DesignModeEnabled)
{
            HContract = new HContract(this);
//   Put code here that fails in Design mode but must at run time               
}
Tutelage answered 18/8, 2017 at 21:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.