如何让Visual Studio 2008 Windows窗体设计器呈现实现抽象基类的Form?

Oli*_*ich 98 c# visual-studio-2008 windows-forms-designer winforms

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

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

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

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

有没有办法让Designer使用基础控件制作抽象?

sme*_*lch 97

我知道必须有办法做到这一点(我找到了一种干净利落的方法).盛的解决方案正是我提出的临时解决方案,但在一位朋友指出Form该类最终从一个abstract类继承之后,我们应该能够完成这项工作.如果他们能做到,我们就能做到.

我们从这个代码到问题

Form1 : Form
Run Code Online (Sandbox Code Playgroud)

问题

public class Form1 : BaseForm
...
public abstract class BaseForm : Form
Run Code Online (Sandbox Code Playgroud)

这是最初的问题发挥作用的地方.如前所述,一位朋友指出System.Windows.Forms.Form实现一个抽象的基类.我们找到了......

证明更好的解决方案

从这一点来看,我们知道设计者可以展示一个实现基本抽象类的类,它只是无法显示立即实现基本抽象类的设计器类.中间必须有最多5个,但我们测试了1层抽象,最初提出了这个解决方案.

初步解决方案

public class Form1 : MiddleClass
...
public class MiddleClass : BaseForm
... 
public abstract class BaseForm : Form
... 
Run Code Online (Sandbox Code Playgroud)

这实际上是有效的,设计师将它渲染得很好,问题解决了.....除了你在生产应用程序中有一个额外的继承级别,这只是必要的因为winforms设计师的不足!

这不是一个100%万无一失的解决方案,但它非常好.基本上你#if DEBUG用来提出精致的解决方案.

精致的解决方案

Form1.cs的

#if DEBUG
public class Form1 : MiddleClass
#else 
public class Form1 : BaseForm
#endif
...
Run Code Online (Sandbox Code Playgroud)

MiddleClass.cs

public class MiddleClass : BaseForm
... 
Run Code Online (Sandbox Code Playgroud)

BaseForm.cs

public abstract class BaseForm : Form
... 
Run Code Online (Sandbox Code Playgroud)

这样做只是使用"初始解决方案"中概述的解决方案,如果它处于调试模式.我们的想法是,您永远不会通过调试版本发布生产模式,并且您将始终在调试模式下进行设计.

设计人员将始终针对当前模式中构建的代码运行,因此您无法在发布模式下使用设计器.但是,只要您在调试模式下设计并释放在发布模式下构建的代码,您就可以了.

唯一可靠的解决方案是,如果您可以通过预处理器指令测试设计模式.

  • 您的表单和抽象基类是否具有无参数构造函数?因为我们必须添加所有内容才能让设计师工作以显示从抽象表单继承的表单. (3认同)
  • 据我所知,这对VS 2015无效 (3认同)

juc*_*rdi 73

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

我们想要什么

首先,让我们定义最终类和基本抽象类.

public class MyControl : AbstractControl
...
public abstract class AbstractControl : UserControl // Also works for Form
...
Run Code Online (Sandbox Code Playgroud)

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

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,我们只将TypeDescriptionProvider属性应用于Abastract控件.

[TypeDescriptionProvider(typeof(AbstractControlDescriptionProvider<AbstractControl, UserControl>))]
public abstract class AbstractControl : UserControl
...
Run Code Online (Sandbox Code Playgroud)

就是这样.无需中间控制.

并且提供者类可以在同一解决方案中应用于我们想要的尽可能多的抽象基础.

*编辑* app.config中还需要以下内容

<appSettings>
    <add key="EnableOptimizedDesignerReloading" value="false" />
</appSettings>
Run Code Online (Sandbox Code Playgroud)

感谢@ user3057544的建议.


  • 由于某种原因,@ RobC Designer有点脾气暴躁.我发现在实现此修复后,我必须清理解决方案,关闭并重新启动VS2010,然后重建;*然后*它会让我设计子类. (5认同)
  • 虽然smelch确实有效,但是无法让它在VS 2010中发挥作用.谁知道为什么? (4认同)
  • 值得注意的是,因为此修补程序替换抽象类的基类实例,所以在设计子类时,设计器中为抽象类添加的可视元素将不可用. (3认同)

Dav*_*mer 10

@Smelch,感谢您的回答,因为我最近遇到了同样的问题.

以下是对您的帖子的一个小改动,以防止编译警告(通过将基类放在#if DEBUG预处理器指令中):

public class Form1
#if DEBUG  
 : MiddleClass 
#else  
 : BaseForm 
#endif 
Run Code Online (Sandbox Code Playgroud)


Jan*_*ich 5

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

interface Base {....}

public class MyUserControl<T> : UserControl, Base
     where T : /constraint/
{ ... }
Run Code Online (Sandbox Code Playgroud)

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


P.W*_*.W. 5

我有一些提示给那些说 TypeDescriptionProvider胡安·卡洛斯·迪亚兹(Juan Carlos Diaz)不起作用并且也不喜欢条件编译的:

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

我将针对抽象基本表单的情况介绍我对这个问题的解决方案。假设您有一个BaseForm类,并且您希望基于它的任何表单都是可设计的(这将是Form1)。在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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

我还有两个问题:

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

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

第二个问题。我真的不知道为什么基本表单的控件在 Form1 类中不可设计,并且它们的位置在调整大小后丢失,但我已经解决了(不是一个很好的解决方案 - 如果你知道更好,请写)。我只是手动将 BaseForm 的按钮(应该在右下角)移动到从 BaseForm 的 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
{
}
Run Code Online (Sandbox Code Playgroud)

在这里你有稍微修改过的版本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);
    }
}
Run Code Online (Sandbox Code Playgroud)

就是这样!

您不必向基于您的 BaseForm 的表单的未来开发人员解释任何事情,他们也不必做任何技巧来设计他们的表单!我认为这是最干净的解决方案(除了控件重新定位)。

还有一个提示:

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