【问题标题】:How can I get Visual Studio 2008 Windows Forms designer to render a Form that implements an abstract base class?如何让 Visual Studio 2008 Windows 窗体设计器呈现实现抽象基类的窗体?
【发布时间】:2010-12-09 21:38:48
【问题描述】:

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

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

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

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

有没有办法让 Designer 使用抽象的基本控件?

【问题讨论】:

    标签: c# winforms visual-studio-2008 windows-forms-designer


    【解决方案1】:

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

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

    【讨论】:

    • 很遗憾,但这就是它的完成方式。希望有一个正确的方法来做到这一点。
    • 有更好的办法,看Smelch的回答
    【解决方案2】:

    我知道必须有一种方法可以做到这一点(我找到了一种干净利落的方法)。盛的解决方案正是我想出的临时解决方法,但在一位朋友指出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% 万无一失的解决方案,但它相当不错。基本上你使用#if DEBUG 来提出完善的解决方案。

    精细化解决方案

    Form1.cs

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

    MiddleClass.cs

    public class MiddleClass : BaseForm
    ... 
    

    BaseForm.cs

    public abstract class BaseForm : Form
    ... 
    

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

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

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

    【讨论】:

    • 您的表单和抽象基类是否有无参数构造函数?因为这就是我们必须添加的所有内容,以使设计人员能够展示从抽象表单继承的表单。
    • 效果很好!我想我只需对实现抽象类的各种类进行所需的修改,然后再次删除临时中间类,如果以后需要进行更多修改,我可以将其添加回来。解决方法确实有效。谢谢!
    • 您的解决方案效果很好。我简直不敢相信 Visual Studio 需要你跳过这样的障碍才能做一些如此常见的事情。
    • 但是如果我使用不是抽象类的中间类,那么继承中间类的人就不必再实现抽象方法,这首先违背了使用抽象类的目的...如何解决这个问题?
    • @ti034 我找不到任何解决方法。所以我只是让来自中间类的所谓抽象函数有一些默认值,可以很容易地提醒我重写它们,而不会让编译器抛出错误。例如,假设抽象的方法是返回页面的标题,那么我将使它返回一个字符串“请更改标题”。
    【解决方案3】:

    @Smelch,感谢您的帮助,因为我最近遇到了同样的问题。

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

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

    【讨论】:

      【解决方案4】:

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

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

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

      【讨论】:

      • 您能否提供更完整的代码示例?我正在努力更好地理解您的设计,我还将把它翻译成 VB。谢谢。
      • 我知道这是旧的,但我发现这是最简单的解决方案。由于我仍然希望我的界面与 UserControl 相关联,因此我在界面中添加了一个 UserControl 属性,并在需要直接访问它时引用它。在我的接口实现中,我扩展了 UserControl 并将UserControl 属性设置为this
      【解决方案5】:

      我将this answer 中的解决方案用于另一个问题,该问题链接this article。文章推荐使用自定义TypeDescriptionProvider 和抽象类的具体实现。设计者会询问自定义提供者要使用哪些类型,并且您的代码可以返回具体类,以便设计者在您完全控制抽象类如何显示为具体类时感到高兴。

      更新:我在对其他问题的回答中加入了a documented code sample。那里的代码确实有效,但有时我必须按照我的回答中所述经历一个清理/构建周期才能使其工作。

      【讨论】:

        【解决方案6】:

        @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 的建议。


        【讨论】:

        • 这对我也有用,因为我使用的是 CF 3.5,没有TypeDescriptionProvider
        • 无法让它在 VS 2010 中工作,尽管 smelch 确实有效。有人知道为什么吗?
        • @RobC Designer 出于某种原因有点脾气暴躁。我发现在实施此修复后,我必须清理解决方案,关闭并重新启动 VS2010,然后重新构建; 那么它会让我设计子类。
        • 值得注意的是,由于此修复将基类的实例替换为抽象类,因此在设计器中为抽象类添加的可视元素在设计时将可用子类。
        • 这对我有用,但我首先必须在构建项目后重新启动 VS 2013。 @ObliviousSage - 感谢您的提醒;在我目前的情况下,至少这不是一个问题,但仍然值得提防。
        【解决方案7】:

        您可以在 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 关键字只会阻止类的运行时实例化),则此方法有效。

        【讨论】:

          【解决方案8】:

          由于抽象类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 以使错误在被遗忘时变得明显。
          【解决方案9】:

          对于那些说 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 表单设计器工作所必需的。然后将 second 中产阶级分配给 TypeDescriptionProviderBaseForm无条件编译。

          我还有两个问题:

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

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

          第二个问题。我真的不知道为什么基本表单的控件在 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
          {
          }
          

          这里有 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 : BaseForm 更改为public class Form1 : BaseFormMiddle1(或BaseFormMiddle2),在VS 表单中编辑它设计师,然后再改回来。我更喜欢这个技巧而不是条件编译,因为它不太可能忘记和发布错误的版本

          【讨论】:

          • 这解决了我在 VS 2013 中使用 Juan 的解决方案时遇到的问题;在重新启动 VS 时,控件现在始终加载。
          【解决方案10】:

          我有一个关于 Juan Carlos Diaz 解决方案的提示。它对我很有用,但有点问题。当我启动 VS 并输入设计器时,一切正常。但是在运行解决方案后,停止并退出它,然后尝试进入设计器,一次又一次出现异常,直到重新启动VS。 但我找到了解决方案 - 要做的就是将下面添加到您的 app.config 中

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2011-06-07
            • 2021-08-20
            • 1970-01-01
            • 1970-01-01
            • 2016-10-30
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多