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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1620847/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 19:27:39  来源:igfitidea点击:

How can I get Visual Studio 2008 Windows Forms designer to render a Form that implements an abstract base class?

c#winformsvisual-studio-2008windows-forms-designer

提问by Oliver Friedrich

I engaged a problem with inherited Controls in Windows Forms and need some advice on it.

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

I do use a base class for items in a List (selfmade GUI list made of a panel) and some inherited controls that are for each type of data that could be added to the list.

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

There was no problem with it, but I now found out, that it would be right, to make the base-control an abstract class, since it has methods, that need to be implemented in all inherited controls, called from the code inside the base-control, but must not and can not be implemented in the base class.

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

When I mark the base-control as abstract, the Visual Studio 2008 Designer refuses to load the window.

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

Is there a way to get the Designer work with the base-control made abstract?

有没有办法让设计器使用抽象的基本控件?

采纳答案by smelch

I KNEW there had to be a way to do this (and I found a way to do this cleanly). Sheng's solution is exactly what I came up with as a temporary workaround but after a friend pointed out that the Formclass eventually inherited from an abstractclass, we SHOULD be able to get this done. If they can do it, we can do it.

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

We went from this code to the problem

我们从这段代码转到问题

Form1 : Form

Problem

问题

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

This is where the initial question came into play. As said before, a friend pointed out that System.Windows.Forms.Formimplements a base class that is abstract. We were able to find...

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

Proof of a better solution

更好解决方案的证明

From this, we knew that it was possible for the designer to show a class that implemented a base abstract class, it just couldn't show a designer class that immediately implemented a base abstract class. There had to be at max 5 inbetween, but we tested 1 layer of abstraction and initially came up with this solution.

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

Initial Solution

初始解决方案

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

This actually works and the designer renders it fine, problem solved.... except you have an extra level of inheritance in your production application that was only necessary because of an inadequacy in the winforms designer!

这实际上有效并且设计器将其呈现得很好,问题解决了......除非您在生产应用程序中具有额外的继承级别,这只是由于 winforms 设计器的不足而需要的!

This isn't a 100% surefire solution but its pretty good. Basically you use #if DEBUGto come up with the refined solution.

这不是一个 100% 万无一失的解决方案,但它非常好。基本上,您习惯于#if DEBUG提出精炼的解决方案。

Refined Solution

精制方案

Form1.cs

表格1.cs

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

MiddleClass.cs

中产阶级.cs

public class MiddleClass : BaseForm
... 

BaseForm.cs

基础表格

public abstract class BaseForm : Form
... 

What this does is only use the solution outlined in "initial solution", if it is in debug mode. The idea is that you will never release production mode via a debug build and that you will always design in debug mode.

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

The designer will always run against the code built in the current mode, so you cannot use the designer in release mode. However, as long as you design in debug mode and release the code built in release mode, you are good to go.

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

The only surefire solution would be if you can test for design mode via a preprocessor directive.

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

回答by u109919

The Windows Forms Designer is creating an instance of the base class of your form/control and applies the parse result of InitializeComponent. That's why you can design the form created by the project wizard without even building the project. Because of this behavior you also can not design a control derived from an abstract class.

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

You can implement those abstract methods and throw an exception when it is not running in the designer. The programmer who derive from the control must provide an implementation that does not call your base class implementation. Otherwise the program would crash.

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

回答by Dave Clemmer

@Smelch, thanks for the helpful answer, as I was running into the same issue recently.

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

Following is a minor change to your post to prevent compilation warnings (by putting the base class within the #if DEBUGpre-processor directive):

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

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

回答by Jan Hettich

I had a similar problem but found a way to refactor things to use an interface in place of an abstract base class:

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

interface Base {....}

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

This may not be applicable to every situation, but when possible it results in a cleaner solution than conditional compilation.

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

回答by Carl G

I'm using the solution in this answerto another question, which links this article. The article recommends using a custom TypeDescriptionProviderand concrete implementation of the abstract class. The designer will ask the custom provider which types to use, and your code can return the concrete class so that the designer is happy while you have complete control over how the abstract class appears as a concrete class.

我正在使用this answerto another question中的解决方案,问题链接了本文。文章推荐使用TypeDescriptionProvider抽象类的自定义和具体实现。设计器将询问自定义提供者使用哪些类型,并且您的代码可以返回具体类,这样设计器就会很高兴,同时您可以完全控制抽象类作为具体类的显示方式。

Update: I included a documented code samplein my answer to that other question. The code there does work, but sometimes I have to go through a clean/build cycle as noted in my answer to get it to work.

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

回答by jucardi

@smelch, There is a better solution, without having to create a middle control, even for debug.

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

What we want

我们想要什么

First, let's define the final class and the base abstract class.

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

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

Now all we need is a Description provider.

现在我们只需要一个Description provider

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);
    }
}

Finally we just apply a TypeDescriptionProvider attribute to the Abastract control.

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

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

And that's it. No middle control required.

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

And the provider class can be applied to as many Abstract bases as we want in the same solution.

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

* EDIT *Also the following is needed in the app.config

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

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

Thanks @user3057544 for the suggestion.

感谢@user3057544 的建议。



回答by Peter Gluck

You could just conditionally compile in the abstractkeyword without interposing a separate class:

您可以只在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
  }

This works provided that BaseFormdoesn't have any abstract methods (the abstractkeyword therefore only prevents the runtime instantiation of the class).

这个工程提供了BaseForm不具有任何抽象方法(将abstract因此关键字仅阻止类的运行时实例化)。

回答by Gabriel L.

Since the abstract class public abstract class BaseForm: Formgives an error and avoid the use of the designer, I came with the use of virtual members. Basically, instead of declaring abstract methods, I declared virtual methods with the minimum body as possible. Here's what I've done :

由于抽象类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. */

Since DataFormwas supposed to be an abstract class with the abstract member displayFields, I "fake" this behavior with virtual members to avoid the abstraction. The designer doesn't complain anymore and everything works fine for me.

由于DataForm应该是具有抽象成员的抽象类,因此displayFields我使用虚拟成员“伪造”了这种行为以避免抽象。设计师不再抱怨,一切对我来说都很好。

It's a workaround more readable, but since it's not abstract, I have to make sure that all child classes of DataFormhave their implementation of displayFields. Thus, be careful when using this technique.

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

回答by P.W.

I've got some tips for people who say the TypeDescriptionProviderby Juan Carlos Diaz is not working and don't like the conditional compilation neither:

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

First of all, you may have to restart Visual Studiofor the changes in your code to work in the form designer (I had to, simple rebuild didn't work - or not every time).

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

I will present my solution of this problem for the case of abstract base Form. Let's say you have a BaseFormclass and you want any forms based on it to be designable (this will be Form1). The TypeDescriptionProvideras presented by Juan Carlos Diaz didn't work for me also. Here is how I made it work, by joining it with the MiddleClass solution (by smelch), but without the #if DEBUGconditional compiling and with some corrections:

我将针对抽象基本表单的情况介绍我对这个问题的解决方案。假设您有一个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
    }
}

Notice the attribute on the BaseForm class. Then you just have to declare the TypeDescriptionProviderand two middle classes, but don't worry, they are invisible and irrelevant for the developer of Form1. The first one implements the abstract members (and makes the base class non abstract). The second one is empty - it's just required for the VS form designer to work. Then you assign the secondmiddle class to the TypeDescriptionProviderof BaseForm. No conditional compilation.

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

I was having two more problems:

我还有两个问题:

  • Problem 1:After changing Form1 in designer (or some code) it was giving the error again (when trying to open it in designer again).
  • Problem 2:BaseForm's controls was placed incorrectly when the Form1's size was changed in designer and the form was closed and reopened again in the form designer.
  • 问题 1:在设计器(或某些代码)中更改 Form1 后,它再次给出错误(尝试再次在设计器中打开它时)。
  • 问题 2:当在设计器中更改 Form1 的大小并且在表单设计器中关闭并重新打开表单时,BaseForm 的控件放置不正确。

The first problem (you may not have it because it's something that haunts me in my project in few another places and usually produces a "Can't convert type X to type X" exception). I solved it in the TypeDescriptionProviderby comparing the type names(FullName) instead of comparing the types (see below).

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

The second problem. I don't really know why the base form's controls are not designable in Form1 class and their positions are lost after resize, but I've worked it around (not a nice solution - if you know any better, please write). I just manually move the BaseForm's buttons (which should be in bottom-right corner) to their correct positions in a method invoked asynchronously from Load event of the BaseForm: BeginInvoke(new Action(CorrectLayout));My base class has only the "OK" and "Cancel" buttons, so the case is simple.

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

And here you have the slightly modified version of TypeDescriptionProvider:

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

And that's it!

就是这样!

You don't have to explain anything to the future developers of forms based on your BaseForm and they don't have to do any tricks to design their forms! I think it's the most clean solution it can be (except for the controls repositioning).

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

One more tip:

还有一个提示:

If for some reason the designer still refuses to work for you, you can always do the simple trick of changing the public class Form1 : BaseFormto public class Form1 : BaseFormMiddle1(or BaseFormMiddle2) in the code file, editing it in the VS form designer and then changing it back again. I prefer this trick over the conditional compilation because it's less likely to forget and release the wrong version.

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

回答by user3057544

I have a tip for Juan Carlos Diaz solution.It works great for me, but was some problem with it. When I start VS and enter designer everything works fine. But after run the solution, then stop and exit it and then try to enter designer the exception appears again and again until restarting VS. But I found the solution for it - everything to do is add below to your app.config

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

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