在Visual Studio设计器中抽象的UserControl继承

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

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

我在表单中放置了一个DetailControl。运行时它可以正确地呈现,但是设计师显示出错并且因为基础用户控件是抽象的无法打开。

目前,我正在考虑以下补丁,这似乎对我来说非常不正确,因为我想让子类被迫实现该方法。

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;
    }
}

有没有更好的方法来解决这个问题?


1
我最近也遇到了类似的问题,我选择使用单独的接口来提供我所需要的方法。当我需要编辑时,我尝试将基类更改为UserControl,但效果不太理想。 - Deanna
9个回答

60

我们想要什么

首先,让我们定义最终类和基础抽象类。

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

现在我们所需要的就是一个描述提供者

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);
    }
}

最后我们只需要在抽象控件上应用TypeDescriptionProvider属性即可。

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

就是这样,不需要中间控制。

并且提供者类可以应用于同一解决方案中我们想要的多个抽象基类。


4
虽然在我的情况下,我的具体子类和UserControl之间有两个级别的'抽象'类,但这对我起作用了。 对于这两个'抽象'类,我需要向TypeDescriptionProvider提供一个TBase为UserControl的参数。 我将'TBase'重命名为'TFirstConcreteBase',以便更明确地告知后续的读者。 我曾希望两个原始的'TBase'声明可以链接在一起,以获取正确的具体基础类,但事实并非如此。感谢@Juan的帮助。 - JMD
2
很高兴我能帮到@JMD。 - jucardi
3
为了让设计器消息消失,您还需要重新编译您的项目并关闭Visual Studio。 - Alex
1
这个对我有效,直到我关闭了Visual Studio。重新打开VS后,我看到同样的错误。 - Elephant
1
不幸的是,这在通用抽象用户控件上不起作用,这也在https://dev59.com/J5jga4cB1Zd3GeqPOr9e中得到了解决,该链接正是指向这个答案。而且它的缺点是不能显示已经添加到抽象用户控件中的控件。因此,对我来说唯一的解决方案是使用一个中间用户控件,就像https://dev59.com/PHRB5IYBdhLWcg3wQVSV#677630中所描述的那样。 - Tobias Knauss
显示剩余2条评论

21

1
它只在我第一次打开Visual Studio时有效。一旦我对代码进行任何更改并重新构建项目,它就会停止工作。不管怎样,谢谢。 - Elephant
@Elephant 可能是 devenv 中的一个错误,现在已经修复了。我现在正在使用 VS2017,我可以打开设计器,关闭设计器,进行代码更改,重新编译项目,设计器仍然会打开。 - Coxy

6
另一种解决方法是使用预处理指令。
#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

    ...
  }

请查看此链接以获取有关此主题的更多信息:从抽象类继承表单(并在设计器中使其工作)
相同的解决方案也在这个MSDN论坛帖子中以更简洁的方式提到:UserControl,Inherited Control,Abstract class,(C#)
也许不是最干净的解决方案,但这仍然是我找到的最短的解决方案。

3

即使这个问题已经存在多年,我还是想添加自己的发现。

如果您不想改动抽象基类,您可以使用以下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;
    }
}

这样,你的表单继承自一个非抽象的基础表单,并在设计器中显示!你仍然保留了抽象表单,但只是在继承层次结构上再高一级。有点奇怪,不是吗?


3
这并没有帮助,因为它消除了拥有抽象基类的好处,即你需要实现方法的要求。抛出异常只是在运行时执行这一要求。 - Tom Bogle
1
是的,这并不理想。但是你仍然可以像计划的那样保留抽象类以在整个应用程序中使用。而这个中间帮助类,只是为了解决这个IDE限制,甚至可以放置在同一个.cs文件中,以使它更接近于你没有使用它的情况:也就是在该文件中实现所有抽象方法,唯一的区别是你必须做两次:抛出和真正的方法。有更好的选择吗? - Andrew

2
我无法使“Nicole Calinoiu”的解决方案工作。但是在Visual Studio中有另一种简单的方法:
  1. 创建新项目
  2. 添加新元素'userControl',例如添加一个按钮
  3. 添加新元素'userControl' Inhereted UserControl,然后选择继承的userControl。
更多详情请参考:'http://www.codeproject.com/Articles/20845/How-to-derive-from-a-parent-form

1
以下是一个通用的解决方案,对我大多数情况都有效。它基于另一个答案中的article。有时它会起作用,我可以设计我的UserControl,然后稍后打开文件,它会给出“设计器必须创建类型为'MyApp.UserControlBase'的实例,但它无法创建,因为该类型被声明为抽象。”我认为我可以通过清理、关闭VS、重新打开VS和重建来修复它。现在它似乎表现良好。祝你好运。
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);
        }
    }
}

这似乎非常适用于在设计器中编辑抽象UI,但当我从抽象类继承并尝试编辑子类时,我会收到关于无法实例化抽象基类的相同错误。我做了什么蠢事吗? - Akuma
2
我有同样的问题。尝试一些类似于构建解决方案、打开/编辑派生控件(无论成功与否)、关闭派生控件、清理解决方案、关闭 Visual Studio、重新打开 Visual Studio 和解决方案、重建解决方案的操作。对我来说,成功或失败是间歇性的。 - Carl G
+1 是的,在声明类型提供程序后进行清理和重新打开工作。 - yoel halb

0
我只需将抽象基类转换为具体类,通过将“抽象”方法定义为虚拟方法,并在其中抛出异常,以防任何淘气的派生类尝试调用基本实现。
例如:
    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  ;)
        }

这个和一个真正的抽象基类的主要实际区别在于,当调用基本实现时会得到运行时错误 - 而如果Base实际上是抽象的,编译器将不允许意外调用Base类实现。对我来说这并不是什么大问题,这使我可以使用设计师而不必担心其他人建议的更复杂和耗时的解决方法...

PS - Akuma - 你应该能够在设计师中编辑你的抽象UI类。我现在没有时间检查这个问题,但我的理解是,设计师只需要实例化基类。只要你正在设计的类的基类是具体的,那么设计的类是什么并不重要。


6
这难道不和我最初在问题中发布的那个补丁完全一样吗? - user356178

0

我在UWP中是新手,它让我抓狂。我没想到为UserControl创建一个抽象基类。我走了另一条路。我创建了一个非XAML的Helper类...HBase。每个View,比如VContract,都有一个对应的Helper叫做HContract。所有特定代码都放在那里。原本ViewModel VMContract和View VContract之间的对话现在通过HContract进行。我们可以通过IHBase来强制规定HWhatever的行为。这并不完全回答OP的问题,但展示了一种替代的方式。所有的Views现在基本上只是外壳。你选择将x:Bind到VContract还是HContract是你自己决定的。最后,我选择了VContract的方式,但我想那是一个错误。

在设计模式下UWP的异常问题现在很容易修复:

if (false == Windows.ApplicationModel.DesignMode.DesignModeEnabled)
{
            HContract = new HContract(this);
//   Put code here that fails in Design mode but must at run time               
}

0

我在我的自定义控件中解决了这个UWP问题。

我的情况

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));

}

声明

< MyCustomControl:MainModel ShowLabel=True />

解决方案

只需在通用资源中覆盖一个虚拟样式即可。

<Style TargetType="local:MainModel" />

敬礼,

塞缪尔


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接