简体   繁体   English

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

[英]Workaround for generic event handler in Windows Forms

Quite some time ago, I noticed that the Windows Forms editor of Visual Studio does not support events which contain generic type parameters. 很久以前,我注意到Visual Studio的Windows窗体编辑器不支持包含泛型类型参数的事件。 For example, an event like 例如,像这样的事件

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

where 哪里

public class ListEventArgs<T> : EventArgs { List<T> args; }

does not even show up in the event list in the property manager of Visual Studio. 甚至没有出现在Visual Studio的属性管理器的事件列表中。 Now, this is a somewhat artificial example that could easily be modified to work in Visual Studio by rewriting the classes and their events. 现在,这是一个有点人为的例子,可以通过重写类及其事件轻松修改以在Visual Studio中工作。 However, I am currently working on a project where I cannot change some classes for compatibility reasons. 但是,我目前正在开发一个项目,由于兼容性原因我无法更改某些类。 The only thing I can do is to change the events of my user control. 我唯一能做的就是改变用户控件的事件。 The events of this control currently look like this: 此控件的事件当前如下所示:

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

Note that the underlying Plane class (represented by the _Plane instance which is a protected field) cannot be changed. 请注意,无法更改基础Plane类(由_Plane实例表示,它是受保护的字段)。 Its DrawingError event and its EventArgs type are declared in the Plane class like this: 它的DrawingError事件及其EventArgs类型在Plane类中声明如下:

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

Of course, the Windows Forms editor of Visual Studio does not show any of the events of my user control. 当然,Visual Studio的Windows窗体编辑器不会显示我的用户控件的任何事件。 I have been looking for a number of workarounds to get them shown again, but have not been able to find a workaround that actually works. 我一直在寻找一些解决方法来让它们再次显示,但是却找不到真正有效的解决方法。 Here are some things that I tried: 以下是我尝试的一些事情:

  1. Created a MyPlane class which inherits from Plane and used that instead: public event EventHandler<MyPlane.DrawingErrorEventArgs> DrawingError ... . 创建了一个继承自Plane的MyPlane类,并使用它代替: public event EventHandler<MyPlane.DrawingErrorEventArgs> DrawingError ... For reasons unknown to me, the events still don't show up in the editor. 由于我不知道的原因,事件仍未显示在编辑器中。 Perhaps this is due to the parameters of the event, some of which still are generic. 也许这是由于事件的参数,其中一些仍然是通用的。 Find a minimal working example below. 找到下面的最小工作示例。
  2. Created a helper class which defines implicit conversion operators between EventHandler<Plane<GDISurface>.DrawingErrorEventArgs> and EventHandler<GDIPlane.DrawingErrorEventArgs> where GDIPlane is just a dummy class which inherits from Plane<GDISurface> . 创建了一个辅助类,它定义了EventHandler<Plane<GDISurface>.DrawingErrorEventArgs>EventHandler<GDIPlane.DrawingErrorEventArgs>之间的隐式转换运算符,其中GDIPlane只是一个继承自Plane<GDISurface>的虚拟类。 This does work to some extent, but duplicates event calls since the conversion creates new event handlers which are passed down to _Plane which cannot be removed/unregistered properly. 这在某种程度上有效,但重复事件调用,因为转换会创建新的事件处理程序,这些处理程序将传递给_Plane,无法正确删除/取消注册。
  3. Tried to inherit from EventHandler<Plane<GDISurface>.DrawingErrorEventArgs> , which obviously does not work since EventHandler<T> is sealed. 试图从EventHandler<Plane<GDISurface>.DrawingErrorEventArgs>继承,这显然不起作用,因为EventHandler<T>被密封。

Are there any other ways to make my events visible again in the Windows Forms editor? 有没有其他方法可以在Windows窗体编辑器中再次显示我的事件?

Best regards Andreas 最好的问候Andreas

EDIT: Minimal working example for 1: 编辑:1的最小工作示例:

public interface ISurface { }

public class GDISurface : ISurface { }

public class Plane<T> where T : ISurface
{
    public event EventHandler<DrawingErrorEventArgs> DrawingError = null;
    public class DrawingErrorEventArgs : EventArgs { T stuff; }
}

public class TestControl : UserControl
{
    public class GDIPlane : Plane<GDISurface>  { }
    GDIPlane _Plane = null;
    public event EventHandler<GDIPlane.DrawingErrorEventArgs> DrawingError { add { _Plane.DrawingError += value; } remove { _Plane.DrawingError -= value; } }
}

DrawingError does not show up in the list of events in the property manager when clicking on a TestControl instance. 单击TestControl实例时,DrawingError不会显示在属性管理器的事件列表中。

EDIT2: This is the original problem (without any workarounds) where the DrawingError event does of TestControl does not show up either: EDIT2:这是TestControl的DrawingError事件没有出现的原始问题(没有任何变通方法):

public interface ISurface { }

public class GDISurface : ISurface { }

public class Plane<T> where T : ISurface
{
    public event EventHandler<DrawingErrorEventArgs> DrawingError = null;
    public class DrawingErrorEventArgs : EventArgs { T stuff; }
}

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

This is behavior specific to Visual Studio, and the cause is rooted in the fact that EventHandler<> does not specify covariance on its 'TEventArgs' (it would impose seemingly silly restrictions) and the tools do not perform enough introspection of your code to suss out an appropriate type (even though you've left a trail of type data in constructing the control.) Thus, it seems as though VS does not support generic event properties. 这是Visual Studio特有的行为,原因在于EventHandler <>没有在其'TEventArgs'上指定协方差(它会施加看似愚蠢的限制),并且工具没有对代码执行足够的内省以至于suss输出一个合适的类型(即使你在构造控件时留下了类型数据的踪迹。)因此,似乎VS不支持通用事件属性。 You may consider filing a feature request on Microsoft Connect , I wouldn't suggest filing it as a bug as they may label it "by design" and close it. 您可以考虑在Microsoft Connect上提交功能请求,我不建议将其作为错误提交,因为他们可能会“按设计”标记并关闭它。

As a general rule, if you need generic type parameters on your events and you need design time support for them (which are different implementation concerns), you're looking at wrapping them in a presentation-specific facade (eg "extra layer of code to facilitate design-time needs".) 作为一般规则,如果您需要在事件上使用泛型类型参数,并且需要对它们进行设计时支持(这是不同的实现问题),那么您需要将它们包装在特定于表示的外观中(例如“额外的代码层”)以方便设计时需求“。)

Personally, I would reduce the generic typing you have in play now, it seems a bit excessive and if you don't understand covariance/contravariance in generic types it might put you in a tight spot at some point, such as now. 就个人而言,我会减少你现在玩的通用打字,看起来有点过分,如果你不理解通用类型中的协方差/逆变,它可能会让你在某个时刻处于紧张状态,比如现在。

However, to work around your problem: 但是,要解决您的问题:

Consider using a custom event args class which could transport data in a non-generic property, and also use a non-generic EventHandler event/property. 考虑使用可以在非泛型属性中传输数据的自定义事件args类,还可以使用非泛型的EventHandler事件/属性。 Understanding the 'type' of the event is then shifted away from generic type parameters and made the responsibility of your non-generic event args instead. 然后,理解事件的“类型”将从泛型类型参数转移出来,而非负责使用非泛型事件args。 If the 'class' of the event args is insufficient, you can add a property to convey the event type (or data type) so that receiving code can properly interpret it (assuming, of course, that it does not already know by some other means.): 如果事件args的“类”不足,您可以添加一个属性来传达事件类型(或数据类型),以便接收代码可以正确解释它(当然,假设其他人不知道它手段。):

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

This is most often only used to ferry data through an event chain, and it is usually implemented as follows: 这通常仅用于通过事件链传送数据,通常按如下方式实现:

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

Unfortunately, this also has a covariance problem, to resolve it you would actually want something more like this: 不幸的是,这也有一个协方差问题,要解决它你真的想要更像这样的东西:

public interface IDataArgs<out T>
{
    T Data { get; }
}

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

Even so, these generic versions still don't work around Visual Studio's limitations , this is merely more proper alternative forms of what you already have shown us. 即便如此, 这些通用版本仍无法解决Visual Studio的局限性 ,这只是您已经向我们展示的更合适的替代形式。

UPDATE: As requested, here is what a "purpose built facade" might look like in the most basic sense. 更新:根据要求,这是一个“目的建立的外观”在最基本的意义上可能是什么样子 Note that the usercontrol functions as a facade layer in this case as the eventhandler it exposes delegates to the underlying object model. 请注意,在这种情况下,usercontrol充当外观层,因为它将委托暴露给底层对象模型。 There is no direct access to underlying object model from the user control (from consumer/designer perspective.) 用户控件无法直接访问底层对象模型(从消费者/设计者的角度来看)。

Please note the reference tracking for event handlers is not necessary unless you dispose of these user controls throughout the lifetime of the app (it is only done to ensure proper delegate removal based on the delegate provided, which is wrapped in a closure/delegate, as you see below.) 请注意,除非您在应用程序的整个生命周期中处理这些用户控件,否则不必对事件处理程序进行引用跟踪(这样做只是为了确保根据提供的委托删除正确的委托,该委托包含在一个闭包/委托中,如你看下面。)

Also worth noting I did not test-run this code beyond verifying that the designer shows DrawingError in the property grid when dropped onto a form. 另外值得注意的是,我没有测试运行此代码,除了验证设计器在放到表单上时在属性网格中显示DrawingError

namespace SampleCase3
{
    public interface ISurface { }

    public class GDISurface : ISurface { }

    public class Plane<T> where T : ISurface
    {
        public event EventHandler<DrawingErrorEventArgs> DrawingError;
        public class DrawingErrorEventArgs : EventArgs { T stuff; }
    }

    public class TestControl : UserControl
    {
        private Plane<GDISurface> _Plane = new Plane<GDISurface>(); // 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<EventHandler, EventHandler<Plane<GDISurface>.DrawingErrorEventArgs>> _cleanupMap = new Dictionary<EventHandler, EventHandler<Plane<GDISurface>.DrawingErrorEventArgs>>();

        public event EventHandler DrawingError
        {
            add
            {
                var nonGenericHandler = value;
                var genericHandler = (EventHandler<Plane<GDISurface>.DrawingErrorEventArgs>)delegate(object sender, Plane<GDISurface>.DrawingErrorEventArgs e)
                {
                    nonGenericHandler(sender, e);
                };
                _Plane.DrawingError += genericHandler;
                _cleanupMap[nonGenericHandler] = genericHandler;
            }
            remove
            {
                var nonGenericHandler = value;
                var genericHandler = default(EventHandler<Plane<GDISurface>.DrawingErrorEventArgs>);
                if (_cleanupMap.TryGetValue(nonGenericHandler, out genericHandler))
                {
                    _Plane.DrawingError -= genericHandler;
                    _cleanupMap.Remove(nonGenericHandler);
                }
            }
        }
    }
}

To complement the above, here is what a non-generic event handler would now look like: 为了补充上述内容,以下是非通用事件处理程序现在的样子:

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

Note that the consumer here has to have knowledge of the type for e to perform conversion. 请注意,此处的使用者必须知道e执行转换的类型。 The use of the as operator will bypass ancestry checks under the assumption that the conversion should succeed. 在假设转换成功的情况下,使用as运算符将绕过祖先检查。

Something like this is as close as you're going to get. 像这样的东西就像你要得到的一样接近。 Yes it is ugly by most of our standards, but if you absolutely 'need' design-time support on top of these components and you cannot change Plane<T> (which would be more appropriate) then this, or something close to this, is the only viable workaround. 是的,我们的大多数标准都很丑陋,但如果您绝对需要在这些组件之上的设计时支持而且您无法更改Plane<T> (这将更合适)那么这个,或者接近这个,是唯一可行的解​​决方法。

HTH HTH

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM