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


98

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

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

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

当我将基本控件标记为抽象时,Visual Studio 2008设计器拒绝加载窗口。

有没有办法使Designer与抽象的基本控件一起工作?

Answers:


97

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

我们从这段代码转到了问题

Form1 : Form

问题

public class Form1 : BaseForm
...
public abstract class BaseForm : Form

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

更好的解决方案的证明

由此,我们知道设计师可以显示一个实现基本抽象类的类,而不能显示立即实现基本抽象类的设计器类。中间最多只能有5个,但是我们测试了1个抽象层,最初提出了这个解决方案。

初始解决方案

public class Form1 : MiddleClass
...
public class MiddleClass : BaseForm
... 
public abstract class BaseForm : Form
... 

这实际上可以正常工作,并且设计器可以很好地解决问题。...除了您的生产应用程序中具有额外的继承级别,这仅是由于winforms设计器的不足才有必要!

这不是100%surefire解决方案,但它相当不错。基本上,您通常会使用#if DEBUG经过改进的解决方案。

精制解决方案

Form1.cs

#if DEBUG
public class Form1 : MiddleClass
#else 
public class Form1 : BaseForm
#endif
...

MiddleClass.cs

public class MiddleClass : BaseForm
... 

BaseForm.cs

public abstract class BaseForm : Form
... 

如果它处于调试模式,则仅使用“初始解决方案”中概述的解决方案。这样的想法是,您将永远不会通过调试版本释放生产模式,而将始终以调试模式进行设计。

设计器将始终以当前模式下构建的代码运行,因此您不能在发布模式下使用设计器。但是,只要您在调试模式下进行设计并释放在发布模式下构建的代码,您就可以开始使用。

唯一的安全解决方案是您是否可以通过预处理程序指令测试设计模式。


3
您的表单和抽象基类是否具有无参数构造函数?原因就是我们要做的就是让设计人员展示从抽象表单继承的表单。

很棒!我想我将对实现抽象类的各个类进行所需的修改,然后再次删除临时中级类,如果以后需要进行更多修改,则可以重新添加。解决方法确实有效。谢谢!
neminem 2011年

1
您的解决方案效果很好。我只是简直不敢相信Visual Studio会要求您跳过这些箍以完成如此​​常见的操作。
戴维森

1
但是,如果我使用的不是抽象类的middleClass,那么继承MiddleClass的任何人都不必再实现抽象方法了,这首先就违背了使用抽象类的目的……该如何解决呢?
大流士

1
@ ti034我找不到任何解决方法。因此,我只是使MiddleClass的所谓抽象函数具有一些默认值,这些默认值可以很容易地提醒我重写它们,而无需让编译器抛出错误。例如,如果所谓的抽象方法是返回页面的标题,那么我将使其返回字符串“ Please change the title”。
Darius

74

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

我们想要什么

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

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

现在我们只需要一个Description提供程序

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
...

就是这样。无需中间控件。

并且,在同一解决方案中,provider类可以应用于所需的任意多个Abstract基。

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

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

感谢@ user3057544的建议。



1
这确实对我TypeDescriptionProvider
有用

4
尽管smelch确实有效,但无法在VS 2010中使其正常工作。有人知道为什么吗?
RobC

5
由于某种原因,@ RobC Designer有点脾气暴躁。我发现在实施此修复程序后,我必须清理解决方案,关闭并重新启动VS2010,然后进行重建。那就让我设计子类。
贤者

3
值得注意的是,由于此修复程序将基类的实例替换为抽象类,因此在设计子类时,在Designer中为抽象类添加的可视元素将不可用。
遗忘的贤者

1
这对我有用,但是在构建项目后,我首先必须重新启动VS 2013。@ObliviousSage-感谢您的单挑;在我目前的情况下,至少这不是问题,但仍然是一个值得提防的好问题。
InteXX

10

@Smelch,感谢您的有用回答,因为我最近遇到了同一问题。

以下是对您的帖子进行的微小更改,以防止出现编译警告(通过将基类放入#if DEBUG预处理器指令中):

public class Form1
#if DEBUG  
 : MiddleClass 
#else  
 : BaseForm 
#endif 

5

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

interface Base {....}

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

这可能并不适用于所有情况,但在可能的情况下,与条件编译相比,它可以提供更干净的解决方案。


1
您能否提供更完整的代码示例?我试图更好地了解您的设计,并且还将把它翻译成VB。谢谢。
InteXX

我知道这是旧的,但是我发现这是最少的解决方案。由于我仍然希望将界面绑定到UserControl,因此我UserControl在界面中添加了一个属性,并在需要直接访问它时引用了该属性。在我的界面实现中,我扩展了UserControl并将UserControl属性设置为this
chanban

3

我在另一个问题的答案中使用的是解决方案,问题链接了本文。本文建议使用TypeDescriptionProvider抽象类的自定义和具体实现。设计人员将询问定制提供程序以使用哪种类型,并且您的代码可以返回具体的类,以便您可以完全控制抽象类如何显示为具体的类,并使设计人员满意。

更新:我在回答另一个问题时包括了一个文档化的代码示例。那里的代码确实有效,但是有时我必须按照答案中所述进行清理/构建周期才能使其正常工作。


3

对于有人说TypeDescriptionProviderJuan Carlos Diaz 的by不起作用并且也不喜欢条件编译的人,我有一些建议:

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

我将针对抽象基本Form的情况提出我的解决方案。假设您有一个BaseForm类,并且希望基于它的任何形式都是可设计的(这将是Form1)。在TypeDescriptionProvider胡安·卡洛斯·迪亚斯所呈现并没有为我工作也。通过将它与MiddleClass解决方案(通过熔焊)结合起来,但没有#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表单设计器只需要工作即可。然后你分配第二中产阶级到TypeDescriptionProviderBaseForm没有条件编译。

我还有两个问题:

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

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

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

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表单设计器中对其进行编辑,然后再次将其更改回。与条件编译相比,我更喜欢这种技巧,因为它不太可能忘记并释放错误的版本


1
这解决了我在VS 2013中使用Juan的解决方案时遇到的问题;在重新启动VS时,控件现在将持续加载。
卢克·梅雷特

3

我对Juan Carlos Diaz解决方案有一个建议。它对我来说很棒,但是存在一些问题。当我启动VS并进入Designer时,一切正常。但是在运行解决方案之后,请停止并退出它,然后尝试进入设计器,该异常反复出现,直到重新启动VS。但是我找到了解决方案-要做的所有事情都添加到您的app.config中

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

2

由于抽象类public abstract class BaseForm: Form会产生错误并避免使用设计器,因此我使用了虚拟成员。基本上,我没有声明抽象方法,而是以尽可能小的主体声明了虚拟方法。这是我所做的:

public class DataForm : Form {
    protected virtual void displayFields() {}
}

public partial class Form1 : DataForm {
    protected override void displayFields() { /* Do the stuff needed for Form1. */ }
    ...
}

public partial class Form2 : DataForm {
    protected override void displayFields() { /* Do the stuff needed for Form2. */ }
    ...
}

/* Do this for all classes that inherit from DataForm. */

由于DataForm应该使用抽象成员作为抽象类,所以displayFields我将这种行为与虚拟成员“伪造”在一起以避免抽象。设计师不再抱怨,一切对我来说都很好。

这是一种更具可读性的解决方法,但是由于它不是抽象的,因此我必须确保的所有子类都DataForm具有的实现displayFields。因此,使用此技术时要小心。


这就是我去的。我只是在基类中抛出NotImplementedException,以使该错误在被遗忘时变得显而易见。
肖恩·罗恩

1

Windows窗体设计器正在创建窗体/控件的基类的实例,并应用的解析结果InitializeComponent。这就是为什么您可以设计由项目向导创建的表单,而无需构建项目的原因。由于这种行为,您也不能设计从抽象类派生的控件。

您可以实现这些抽象方法,并在设计器中未运行它时引发异常。从控件派生的程序员必须提供不调用您的基类实现的实现。否则程序将崩溃。


可惜,但是那是怎么做的。希望有一个正确的方法来做到这一点。
奥利弗·弗里德里希

有更好的方法,请参阅Smelch的答案
艾伦·赖斯

-1

您可以有条件地在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因此关键字仅阻止类的运行时实例化)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.