我怎样才能得到Visual Studio 2008 Windows窗体devise器来呈现一个实现抽象基类的窗体?
我在Windows窗体中遇到了inheritance控件的问题,需要一些build议。
我使用List中的项目的基类(由面板制成的自制GUI列表)和一些可以添加到列表中的每种types的数据的inheritance控件。
没有问题,但现在我发现,把基控制devise成抽象类是正确的,因为它有方法,需要在所有的inheritance控件中实现,从内部的代码调用基本控制,但是不能也不能在基类中实现。
当我将基本控件标记为抽象时,Visual Studio 2008devise器拒绝加载窗口。
有没有一种方法让devise师使用基础控件进行抽象工作?
我知道必须有一种方法来做到这一点(我find了一个干净的方法)。 Sheng的解决scheme正是我想到的临时解决方法,但在朋友指出Form
类最终从一个abstract
类inheritance之后,我们应该能够完成这个工作。 如果他们能做到的话,我们可以做到。
我们从这个代码到这个问题
Form1 : Form
问题
public class Form1 : BaseForm ... public abstract class BaseForm : Form
这是最初的问题发挥的地方。 如前所述,一位朋友指出System.Windows.Forms.Form
实现了一个抽象的基类。 我们能够find…
certificate更好的解决scheme
-
inheritance层次:
- System.Object的
- System.MarshalByRefObject (
public **abstract** class MarshalByRefObject
)- System.ComponentModel.Component
- System.Windows.Forms.Control的
- System.Windows.Forms.ScrollableControl
- System.Windows.Forms.ContainerControl
- System.Windows.Forms.Form中
- System.Windows.Forms.ContainerControl
- System.Windows.Forms.ScrollableControl
- System.Windows.Forms.Control的
- System.ComponentModel.Component
- System.MarshalByRefObject (
- System.Object的
由此我们知道,devise者可能会展示一个实现了基本抽象类的类,它不能显示立即实现基本抽象类的devise器类。 中间最多只能有5个,但我们testing了1层抽象,最初提出了这个解决scheme。
初始解决scheme
public class Form1 : MiddleClass ... public class MiddleClass : BaseForm ... public abstract class BaseForm : Form ...
这实际上是有效的,devise师把它渲染好,问题解决了….除了你的生产应用程序有一个额外的inheritance级别,这只是因为winformsdevise者的不足而必须的!
这不是一个100%肯定的解决scheme,但非常好。 基本上你使用#if DEBUG
来提出改进的解决scheme。
精制解决scheme
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 ...
如果它处于debugging模式,那么这只是使用“初始解决scheme”中概述的解决scheme。 这个想法是,你将永远不会通过一个debugging版本发布生产模式,你总是会以debugging模式进行devise。
devise者将总是运行在当前模式下build立的代码,所以你不能在发布模式下使用devise器。 但是,只要您在debugging模式下进行devise并释放内置在发布模式下的代码,您就可以开始使用了。
唯一可靠的解决scheme是通过预处理指令来testingdevise模式。
@smelch,有一个更好的解决scheme,而不必创build一个中间控制,即使是debugging。
我们想要什么
首先,我们来定义最终的类和基本的抽象类。
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 ...
就是这样。 不需要中间控制。
而提供者类可以应用于同样的解决scheme中的任意数量的抽象基础。
*编辑*还需要在app.config中
<appSettings> <add key="EnableOptimizedDesignerReloading" value="false" /> </appSettings>
感谢@ user3057544的build议。
@Smelch,谢谢你的回答,最近我遇到了同样的问题。
以下是对您的post进行的小改动,以防止编译警告(通过将基类置于#if DEBUG
预处理器指令中):
public class Form1 #if DEBUG : MiddleClass #else : BaseForm #endif
我有一个类似的问题,但find了一种方法来重构的东西来使用一个接口,而不是抽象的基类:
interface Base {....} public class MyUserControl<T> : UserControl, Base where T : /constraint/ { ... }
这可能并不适用于所有情况,但是如果可能的话,它将产生比条件编译更清晰的解决scheme。
我在这个答案中使用了解决scheme的另一个问题,链接这篇文章 。 文章build议使用一个自定义的TypeDescriptionProvider
和抽象类的具体实现。 devise人员会询问自定义提供程序使用哪种types,并且您的代码可以返回具体的类,以便devise人员在您完全控制抽象类如何显示为具体类时获得快乐。
更新:我在其他问题的答案中包含了一个logging的代码示例 。 代码在那里工作,但有时我必须经历一个清理/生成循环,正如在我的答案中指出的那样。
我有一个胡安·卡洛斯·迪亚兹解决scheme的小费。 它对我很好,但有一些问题。 当我开始VS并进入devise师一切工作正常。 但是在运行解决scheme之后,然后停止并退出,然后尝试inputdevise器,一次又一次地出现exception,直到重新启动VS. 但是我find了解决scheme – 所做的一切就是添加到你的app.config
<appSettings> <add key="EnableOptimizedDesignerReloading" value="false" /> </appSettings>
由于抽象类public abstract class BaseForm: Form
给出了一个错误,避免了devise者的使用,所以我使用了虚拟成员。 基本上,我没有宣布抽象方法,而是尽可能地用最小的方法来声明虚拟方法。 以下是我所做的:
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
,我用虚拟成员“伪造”这个行为来避免抽象。 devise师不再抱怨,一切正常。
这是一个更具可读性的解决方法,但由于它不是抽象的,我必须确保所有的DataForm
子类都具有它们的displayFields
实现。 因此,使用这种技术时要小心。
对于那些说胡安·卡洛斯·迪亚兹(Juan Carlos Diaz)的TypeDescriptionProvider
不起作用,并且不喜欢条件编译的人,我有一些提示:
首先,您可能需要重新启动Visual Studio,才能使您的代码在表单devise器中工作(我不得不简单的重build工作 – 或者不是每次都不行)。
我将针对抽象基础表格的情况介绍我的这个问题的解决scheme。 假设你有一个BaseForm
类,并且你希望基于它的任何表单都是可devise的(这将是Form1
)。 Juan Carlos Diaz提供的TypeDescriptionProvider
也不适合我。 这是我如何使它的工作,通过join它的MiddleClass解决scheme(通过锡),但没有#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表单devise工作。 然后,将第二个中间类分配给BaseForm
的TypeDescriptionProvider
。 没有条件编译。
我还有两个问题:
- 问题1:在devise器(或某些代码)中更改Form1后,再次发出错误(当再次尝试在devise器中打开它时)。
- 问题2:在devise器中更改Form1的大小时,BaseForm的控件放置不正确,并且表单在表单devise器中closures并重新打开。
第一个问题(你可能没有,因为这是困扰我在我的项目在其他地方,通常会产生一个“不能转换typesX到typesX”例外)。 我通过比较types名称 (FullName)而不是比较types(见下面)在TypeDescriptionProvider
解决了这个问题。
第二个问题。 我真的不知道为什么基本窗体的控件在Form1类中不是可devise的,并且在resize后它们的位置会丢失,但是我已经做了很多工作(不是一个好的解决scheme – 如果你知道的更好,请写)。 我只是手动将BaseForm的button(应该在右下angular)移动到从BaseForm: BeginInvoke(new Action(CorrectLayout));
Load事件asynchronous调用的方法中的正确位置BeginInvoke(new Action(CorrectLayout));
我的基类只有“确定”和“取消”button,所以情况很简单。
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的表单的未来开发人员解释任何事情,他们不必做任何devise表单的技巧! 我认为这是最干净的解决scheme(控件重新定位除外)。
还有一个提示:
如果由于某种原因,devise者仍然拒绝为你工作,你总是可以在代码文件中做public class Form1 : BaseForm
的public class Form1 : BaseFormMiddle1
(或BaseFormMiddle2
),在VS表单devise器中进行编辑然后再把它改回来 我更喜欢这个条件编译的技巧,因为它不太可能忘记和释放错误的版本 。
Windows窗体devise器正在创build窗体/控件的基类的实例,并应用InitializeComponent
的分析结果。 这就是为什么您可以devise由项目向导创build的表单,而无需构build项目。 由于这种行为,你也不能devise一个从抽象类派生的控件。
您可以实现这些抽象方法,并在devise器中未运行时引发exception。 从控制派生的程序员必须提供一个不调用基类实现的实现。 否则,程序会崩溃。
你可以在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
关键字因此只阻止类的运行时实例化)。