103

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

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

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

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

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

4

10 回答 10

99

我知道必须有一种方法可以做到这一点(我找到了一种干净利落的方法)。盛的解决方案正是我作为临时解决方法提出的,但在一位朋友指出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
...

中间类.cs

public class MiddleClass : BaseForm
... 

BaseForm.cs

public abstract class BaseForm : Form
... 

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

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

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

于 2010-03-09T01:29:14.790 回答
81

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

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

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

在同一个解决方案中,提供者类可以应用于我们想要的任意数量的抽象基。

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

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

感谢@user3057544 的建议。


于 2013-07-15T18:29:32.400 回答
11

@Smelch,感谢您提供有用的答案,因为我最近遇到了同样的问题。

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

public class Form1
#if DEBUG  
 : MiddleClass 
#else  
 : BaseForm 
#endif 
于 2010-04-14T18:27:28.980 回答
5

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

interface Base {....}

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

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

于 2011-01-22T07:17:47.750 回答
5

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

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

我将针对抽象基表单的情况提出我对这个问题的解决方案。假设您有一个BaseForm类,并且您希望任何基于它的表单都是可设计的(这将是Form1)。胡安·TypeDescriptionProvider卡洛斯·迪亚兹(Juan Carlos Diaz)提出的也对我不起作用。这是我通过将其与 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 表单设计器工作所必需的。然后你将第二个中产阶级分配给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
{
}

在这里你有稍微修改过的版本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 表单设计器中对其进行编辑,然后再将其更改回来。我更喜欢这个技巧而不是条件编译,因为它不太可能忘记和发布错误的版本

于 2014-05-05T12:30:17.947 回答
3

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

更新:我在对其他问题的回答中包含了一个记录在案的代码示例。那里的代码确实有效,但有时我必须经历一个干净/构建周期,如我的回答中所述才能让它工作。

于 2012-09-14T10:07:47.090 回答
3

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

  <appSettings>
   <add key="EnableOptimizedDesignerReloading" value="false" />
  </appSettings>
于 2015-01-10T11:51:02.360 回答
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. 因此,在使用这种技术时要小心。

于 2014-01-19T18:43:24.890 回答
1

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

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

于 2009-10-25T14:03:14.840 回答
-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因此关键字仅阻止类的运行时实例化),则此方法有效。

于 2013-10-17T20:31:27.760 回答