Windows窗体中通用事件处理程序的解决方法

很久以前,我注意到Visual Studio的Windows窗体编辑器不支持包含generics类型参数的事件。 例如,像这样的事件

public event EventHandler<ListEventArgs> MyStrangeEvent { add { ... } remove { ... } } 

哪里

 public class ListEventArgs : EventArgs { List args; } 

甚至没有出现在Visual Studio的属性管理器的事件列表中。 现在,这是一个有点人为的例子,可以通过重写类及其事件轻松修改以在Visual Studio中工作。 但是,我目前正在开发一个项目,由于兼容性原因我无法更改某些类。 我唯一能做的就是改变用户控件的事件。 此控件的事件当前如下所示:

 public event EventHandler<Plane.DrawingErrorEventArgs> DrawingError { add { _Plane.DrawingError += value; } remove { _Plane.DrawingError -= value; } } 

请注意,无法更改基础Plane类(由_Plane实例表示,它是受保护的字段)。 它的DrawingError事件及其EventArgs类型在Plane类中声明如下:

 public class Plane where T : ISurface { ... public event EventHandler DrawingError = null; ... public class DrawingErrorEventArgs : EventArgs { ... /* Uses T */ ... } } 

当然,Visual Studio的Windows窗体编辑器不会显示我的用户控件的任何事件。 我一直在寻找一些解决方法来让它们再次显示,但却找不到真正有效的解决方法。 以下是我尝试过的一些事情:

  1. 创建了一个inheritance自Plane的MyPlane类,并使用了它: public event EventHandler DrawingError ... 由于我不知道的原因,事件仍未显示在编辑器中。 也许这是由于事件的参数,其中一些仍然是通用的。 找到下面的最小工作示例。
  2. 创建了一个辅助类,它定义了EventHandler<Plane.DrawingErrorEventArgs>EventHandler之间的隐式转换运算符,其中GDIPlane只是一个inheritance自Plane的虚拟类。 这在某种程度上有效,但重复事件调用,因为转换会创建新的事件处理程序,这些处理程序将传递给_Plane,无法正确删除/取消注册。
  3. 试图从EventHandler<Plane.DrawingErrorEventArgs>inheritance,这显然不起作用,因为EventHandler被密封。

有没有其他方法可以在Windows窗体编辑器中再次显示我的事件?

最好的问候Andreas

编辑:1的最小工作示例:

 public interface ISurface { } public class GDISurface : ISurface { } public class Plane where T : ISurface { public event EventHandler DrawingError = null; public class DrawingErrorEventArgs : EventArgs { T stuff; } } public class TestControl : UserControl { public class GDIPlane : Plane { } GDIPlane _Plane = null; public event EventHandler DrawingError { add { _Plane.DrawingError += value; } remove { _Plane.DrawingError -= value; } } } 

单击TestControl实例时,DrawingError不会显示在属性管理器中的事件列表中。

EDIT2:这是TestControl的DrawingError事件没有显示的原始问题(没有任何变通方法):

 public interface ISurface { } public class GDISurface : ISurface { } public class Plane where T : ISurface { public event EventHandler DrawingError = null; public class DrawingErrorEventArgs : EventArgs { T stuff; } } public class TestControl : UserControl { Plane _Plane = null; public event EventHandler<Plane.DrawingErrorEventArgs> DrawingError { add { _Plane.DrawingError += value; } remove { _Plane.DrawingError -= value; } } } 

这是Visual Studio特有的行为,原因在于EventHandler <>没有在其’TEventArgs’上指定协方差(它会施加看似愚蠢的限制),并且工具没有对您的代码执行足够的自省输出一个合适的类型(即使你在构造控件时留下了类型数据的踪迹。)因此,似乎VS不支持通用事件属性。 您可以考虑在Microsoft Connect上提交功能请求,我不建议将其作为错误提交,因为他们可能会“按设计”标记并关闭它。

作为一般规则,如果您需要在事件上使用generics类型参数,并且需要对它们进行设计时支持(这是不同的实现问题),那么您需要将它们包装在特定于表示的外观中(例如“额外的代码层”)以方便设计时需求“。)

就个人而言,我会减少你现在玩的通用打字,看起来有点过分,如果你不理解通用类型中的协方差/逆变,它可能会让你在某些时候陷入困境,比如现在。

但是,要解决您的问题:

考虑使用可以在非generics属性中传输数据的自定义事件args类,还可以使用非generics的EventHandler事件/属性。 然后,理解事件的“类型”将从generics类型参数转移出来,而非负责使用非generics事件args。 如果事件args的“类”不足,您可以添加一个属性来传达事件类型(或数据类型),以便接收代码可以正确解释它(当然,假设其他人不知道它)手段。):

 public class DataEventArgs : EventArgs { //public string EventTypeOrPurpose { get; set; } public object Data { get; set; } } 

这通常仅用于通过事件链传送数据,通常按如下方式实现:

 public class DataEventArgs : EventArgs { public T Data { get; set; } } 

不幸的是,这也有一个协方差问题,要解决它你真的想要更像这样的东西:

 public interface IDataArgs { T Data { get; } } public class DataEventArgs : EventArgs, IDataArgs { public DataEventArgs(T data) { _data = data; } private T _data; public T Data { get { return _data; } } } 

即便如此, 这些通用版本仍无法解决Visual Studio的局限性 ,这只是您已经向我们展示的更合适的替代forms。

更新:根据要求,这是一个“目的建立的外观”在最基本的意义上可能是什么样子 。 请注意,在这种情况下,usercontrol充当外观层,因为它将委托暴露给底层对象模型。 用户控件没有直接访问底层对象模型(从消费者/设计者的角度来看)。

请注意,除非您在应用程序的整个生命周期中处理这些用户控件,否则不必对事件处理程序进行引用跟踪(这样做只是为了确保根据提供的委托删除正确的委托,该委托包含在一个闭包/委托中,如你看下面。)

另外值得注意的是,我没有测试运行此代码,除了validation设计器在放到表单上时在属性网格中显示DrawingError

 namespace SampleCase3 { public interface ISurface { } public class GDISurface : ISurface { } public class Plane where T : ISurface { public event EventHandler DrawingError; public class DrawingErrorEventArgs : EventArgs { T stuff; } } public class TestControl : UserControl { private Plane _Plane = new Plane(); // requires initialization for my own testing public TestControl() { } // i am adding this map *only* so that the removal of an event handler can be done properly private Dictionary.DrawingErrorEventArgs>> _cleanupMap = new Dictionary.DrawingErrorEventArgs>>(); public event EventHandler DrawingError { add { var nonGenericHandler = value; var genericHandler = (EventHandler.DrawingErrorEventArgs>)delegate(object sender, Plane.DrawingErrorEventArgs e) { nonGenericHandler(sender, e); }; _Plane.DrawingError += genericHandler; _cleanupMap[nonGenericHandler] = genericHandler; } remove { var nonGenericHandler = value; var genericHandler = default(EventHandler.DrawingErrorEventArgs>); if (_cleanupMap.TryGetValue(nonGenericHandler, out genericHandler)) { _Plane.DrawingError -= genericHandler; _cleanupMap.Remove(nonGenericHandler); } } } } } 

为了补充上述内容,以下是非通用事件处理程序现在的样子:

 private void testControl1_DrawingError(object sender, EventArgs e) { var genericDrawingErrorEventArgs = e as Plane.DrawingErrorEventArgs; if (genericDrawingErrorEventArgs != null) { // TODO: } } 

请注意,此处的使用者必须知道e的类型才能执行转换。 在假设转换成功的情况下,使用as运算符将绕过祖先检查。

像这样的东西就像你要得到的一样接近。 是的,我们的大多数标准都很丑陋,但是如果您绝对需要在这些组件之上的设计时支持并且您无法更改Plane (这将更合适)那么这个,或者接近这个,是唯一可行的解​​决方法。

HTH