[C#] 我怎样才能让Visual Studio 2008 Windows窗体设计器渲染一个实现抽象基类的窗体?


Answers

@smelch,有一个更好的解决方案,无需创建中间控件,即使是调试。

我们想要什么

首先,我们定义最终的类和基本的抽象类。

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属性应用于Abastract控件。

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

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

提供者类可以在同一个解决方案中应用于尽可能多的抽象基础。

*编辑*还需要在app.config中

<appSettings>
    <add key="EnableOptimizedDesignerReloading" value="false" />
</appSettings>

感谢@ user3057544的建议。

Question

我在Windows窗体中遇到了继承控件的问题,需要一些建议。

我确实使用List中的项目的基类(由面板制作的自制GUI列表)和一些可以添加到列表中的每种类型数据的继承控件。

它没有问题,但我现在发现,将基控制设置为抽象类是正确的,因为它有方法,需要在所有继承的控件中实现,从内部的代码调用基本控制,但不能也不能在基类中实现。

当我将基础控件标记为抽象时,Visual Studio 2008 Designer拒绝加载窗口。

有没有一种方法让设计师使用基础控件进行抽象工作?




我有一个胡安卡洛斯迪亚兹解决方案的小费。 它对我很好,但它有一些问题。 当我开始VS并进入设计师一切正常。 但是在运行解决方案之后,然后停止并退出,然后尝试输入设计器,一次又一次地出现异常,直到重新启动VS. 但我找到了解决方案 - 所做的一切就是将以下内容添加到您的app.config中

  <appSettings>
   <add key="EnableOptimizedDesignerReloading" value="false" />
  </appSettings>



我有一个类似的问题,但找到了一种重构方法来使用接口来代替抽象基类:

interface Base {....}

public class MyUserControl<T> : UserControl, Base
     where T : /constraint/
{ ... }

这可能不适用于所有情况,但在可能的情况下,它会产生比条件编译更清晰的解决方案。




对于那些认为Juan Carlos Diaz的TypeDescriptionProvider不起作用并且不喜欢条件编译的人,我有一些提示:

首先,您可能必须重新启动Visual Studio才能使您的代码中的更改在表单设计器中工作(我不得不简单重建不起作用 - 或者不是每次都可以)。

我将针对抽象基础表格的情况介绍我的这个问题的解决方案。 假设你有一个BaseForm类,并且你希望基于它的任何表单都是可设计的(这将是Form1 )。 Juan Carlos Diaz提供的TypeDescriptionProvider也不适合我。 下面是我如何通过将它与MiddleClass解决方案(通过smelch)结合起来,但没有#if DEBUG条件编译和一些更正:

[TypeDescriptionProvider(typeof(AbstractControlDescriptionProvider<BaseForm, BaseFormMiddle2>))]   // BaseFormMiddle2 explained below
public abstract class BaseForm : Form
{
    public BaseForm()
    {
        InitializeComponent();
    }

    public abstract void SomeAbstractMethod();
}


public class Form1 : BaseForm   // Form1 is the form to be designed. As you see it's clean and you do NOTHING special here (only the the normal abstract method(s) implementation!). The developer of such form(s) doesn't have to know anything about the abstract base form problem. He just writes his form as usual.
{
    public Form1()
    {
        InitializeComponent();
    }

    public override void SomeAbstractMethod()
    {
        // implementation of BaseForm's abstract method
    }
}

注意BaseForm类的属性。 然后,你只需要声明TypeDescriptionProvider两个中间类 ,但不用担心,它们对于Form1的开发者来说不可见的和不相关的 。 第一个实现抽象成员(并使基类非抽象)。 第二个是空的 - 它只是需要VS表单设计工作。 然后,将第二个中间类分配给BaseFormTypeDescriptionProvider没有条件编译。

我还有两个问题:

  • 问题1:在设计器(或某些代码)中更改Form1后,它再次发出错误(当再次尝试在设计器中打开它时)。
  • 问题2:当Form1的大小在设计器中更改并且窗体关闭并在窗体设计器中再次打开时,BaseForm的控件放置不正确。

第一个问题(你可能没有,因为它在我的项目中几乎在其他地方困扰着我,通常会产生“无法将X类型转换为X类型”异常)。 我通过比较类型名称 (FullName)而不是比较类型(见下文)在TypeDescriptionProvider解决了它。

第二个问题。 我真的不知道为什么基本窗体的控件在Form1类中不可设计,并且在调整大小后它们的位置会丢失,但我已经实现了它(不是一个很好的解决方案 - 如果您知道更好,请写)。 我只是手动将BaseForm的按钮(它应该在右下角)移动到正确的位置,该方法与BaseForm: BeginInvoke(new Action(CorrectLayout)); Load事件异步调用的方法中BeginInvoke(new Action(CorrectLayout)); 我的基类只有“确定”和“取消”按钮,因此情况很简单。

class BaseFormMiddle1 : BaseForm
{
    protected BaseFormMiddle1()
    {
    }

    public override void SomeAbstractMethod()
    {
        throw new NotImplementedException();  // this method will never be called in design mode anyway
    }
}


class BaseFormMiddle2 : BaseFormMiddle1  // empty class, just to make the VS designer working
{
}

在这里你有TypeDescriptionProvider的稍微修改版本:

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

    public override Type GetReflectionType(Type objectType, object instance)
    {
        if (objectType.FullName == typeof(TAbstract).FullName)  // corrected condition here (original condition was incorrectly giving false in my case sometimes)
            return typeof(TBase);

        return base.GetReflectionType(objectType, instance);
    }

    public override object CreateInstance(IServiceProvider provider, Type objectType, Type[] argTypes, object[] args)
    {
        if (objectType.FullName == typeof(TAbstract).FullName)  // corrected condition here (original condition was incorrectly giving false in my case sometimes)
            objectType = typeof(TBase);

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

就是这样!

您不必向基于BaseForm的表单的未来开发人员解释任何内容,他们也不必为设计表单做任何窍门! 我认为这是最清晰的解决方案(控件重新定位除外)。

还有一个提示:

如果由于某种原因,设计者仍然拒绝为你工作,你可以随时做一些简单的技巧,在代码文件中将public class Form1 : BaseFormpublic class Form1 : BaseFormMiddle1 (或BaseFormMiddle2 ),并在VS表单设计器中对其进行编辑然后再改回来。 我更喜欢这种条件编译技巧,因为它不太可能忘记和释放错误的版本




您可以在不插入单独的类的情况下在abstract关键字中进行条件编译:

#if DEBUG
  // Visual Studio 2008 designer complains when a form inherits from an 
  // abstract base class
  public class BaseForm: Form {
#else
  // For production do it the *RIGHT* way.
  public abstract class BaseForm: Form {
#endif

    // Body of BaseForm goes here
  }

这样做的前提是BaseForm没有任何抽象方法(因此abstract关键字只能阻止类的运行时实例化)。