我在使用Windows Forms中继承控件时遇到了问题,需要一些建议。
我为自制GUI列表中的项目使用了一个基类以及一些针对每种类型数据的继承控件。
一开始没有问题,但现在发现将基类控件变成抽象类是正确的决定,因为它有一些方法需要在所有继承控件中实现并从基类控件内部的代码中调用,但不能在基类中实现。
当我将基类控件标记为抽象类时,Visual Studio 2008设计工具无法加载窗口。
有没有办法让设计工具与抽象化的基类控件配合工作?
我在使用Windows Forms中继承控件时遇到了问题,需要一些建议。
我为自制GUI列表中的项目使用了一个基类以及一些针对每种类型数据的继承控件。
一开始没有问题,但现在发现将基类控件变成抽象类是正确的决定,因为它有一些方法需要在所有继承控件中实现并从基类控件内部的代码中调用,但不能在基类中实现。
当我将基类控件标记为抽象类时,Visual Studio 2008设计工具无法加载窗口。
有没有办法让设计工具与抽象化的基类控件配合工作?
我知道一定有方法可以做到这一点(我找到了一种干净的方法)。Sheng的解决方案正是我想到的一个临时解决办法,但是一个朋友指出Form
类最终会继承自一个抽象类,我们应该能够完成这个任务。如果他们能做到,我们也能做到。
我们从这段代码到这个问题。
Form1 : Form
public class Form1 : BaseForm
...
public abstract class BaseForm : Form
这正是最初的问题所在。如前所述,一位朋友指出System.Windows.Forms.Form
实现了一个抽象的基类。我们能够找到...
继承层次结构:
public **abstract** class MarshalByRefObject
)
由此,我们知道设计器能够显示一个实现了基本抽象类的类,但它不能显示一个立即实现基本抽象类的设计器类。中间最多只有5个抽象层级,但我们测试了1个抽象层级并最初提出了这个解决方案。
public class Form1 : MiddleClass
...
public class MiddleClass : BaseForm
...
public abstract class BaseForm : Form
...
这个方法实际上是有效的,并且设计师可以正常渲染它,问题解决了...除非你在生产应用中有一个额外的继承级别,这只是因为winforms设计师的不足而必要!
这并不是百分之百的万无一失的解决方案,但它相当不错。基本上,您使用#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
...
这段代码的作用是只有在调试模式下才使用“初始解决方案”中概述的方法。其想法是您永远不会通过调试构建发布生产模式,并且您始终会在调试模式下进行设计。
设计者始终会针对以当前模式构建的代码运行,因此您不能在发布模式下使用设计者。但是,只要您在调试模式下设计并发布在发布模式下构建的代码,就可以正常运行。
唯一可靠的解决方案是,如果您可以通过预处理器指令测试设计模式。
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(typeof(AbstractControlDescriptionProvider<AbstractControl, UserControl>))]
public abstract class AbstractControl : UserControl
...
就是这样。不需要中间控制。
而且,提供者类可以应用于同一解决方案中我们想要的许多抽象基类。
***编辑*** 还需要在app.config中添加以下内容。
<appSettings>
<add key="EnableOptimizedDesignerReloading" value="false" />
</appSettings>
感谢 @user3057544 的建议。
AbstractControlDescriptionProvider
类中添加 XML 文档注释头(以 /// 开始的行)会破坏 VS 2022 中的设计器,和未使用属性标签的效果相同。TypeDescriptionProvider
。 - Adrian Botor@Smelch,感谢您的有用答案,因为我最近也遇到了同样的问题。
以下是对您的帖子进行的轻微更改,以防止编译警告(通过将基类放在#if DEBUG
预处理器指令中):
public class Form1
#if DEBUG
: MiddleClass
#else
: BaseForm
#endif
针对那些认为Juan Carlos Diaz的TypeDescriptionProvider无法使用且不喜欢条件编译的人,我提供了一些小技巧:
首先,你可能需要重新启动Visual Studio,这样你代码中的更改才能在表单设计器中生效(我不得不这样做,简单的重建并不能解决问题 - 或者并非每次都有效)。
现在我会介绍我的解决方案,以抽象基础Form为例。假设你有一个BaseForm类,并希望任何基于它的窗体都可以进行设计(这将是Form1)。像Juan Carlos Diaz所展示的TypeDescriptionProvider对我也没有用。以下是我如何使其工作的方法,结合了smelch的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
}
}
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表单设计器中进行编辑,然后再将其改回。与条件编译相比,我更喜欢这个技巧,因为它不太可能忘记并发布错误的版本。
我曾遇到类似的问题,但找到了一种重构方法,用接口替代抽象基类:
interface Base {....}
public class MyUserControl<T> : UserControl, Base
where T : /constraint/
{ ... }
TypeDescriptionProvider
和抽象类的具体实现。设计师将询问自定义提供程序要使用哪些类型,您的代码可以返回具体类,以便设计师满意,同时您完全控制抽象类如何显示为具体类。我有一个关于Juan Carlos Diaz解决方案的提示。它对我非常有效,但也存在一些问题。当我启动VS并进入设计模式时,一切都很好。但是在运行解决方案、停止和退出后,再尝试进入设计模式,异常就会不断出现,直到重新启动VS。 但是我找到了解决方法 - 只需将以下内容添加到您的app.config中即可。
<appSettings>
<add key="EnableOptimizedDesignerReloading" value="false" />
</appSettings>
由于抽象类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
实现。因此,在使用此技术时要小心。
Windows Forms 设计器正在创建您的窗体/控件的基类实例,并应用 InitializeComponent
的解析结果。这就是为什么您可以设计由项目向导创建的表单而无需构建项目的原因。由于这种行为,您也无法设计从抽象类派生的控件。
您可以实现这些抽象方法,并在不运行设计器时抛出异常。从控件派生的程序员必须提供一个不调用您的基类实现的实现。否则程序将崩溃。
您可以在不插入单独的类的情况下有条件地编译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
关键字仅防止类的运行时实例化),这个方法就可以工作。