ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

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

2019-11-20 19:09:13  阅读:297  来源: 互联网

标签:visual-studio-2013 event-handling generics c winforms


相当早以前,我注意到Visual Studio的Windows窗体编辑器不支持包含通用类型参数的事件.例如,类似

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

哪里

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

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

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

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

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

当然,Visual Studio的Windows窗体编辑器不会显示我的用户控件的任何事件.我一直在寻找多种解决方法来再次显示它们,但一直无法找到一种有效的解决方法.这是我尝试过的一些方法:

>创建一个从Plane继承的MyPlane类,并改用它:公共事件EventHandler< MyPlane.DrawingErrorEventArgs> DrawingError ….由于我不知道的原因,事件仍然没有显示在编辑器中.也许这是由于事件的参数所致,其中某些参数仍然是通用的.在下面找到一个最小的工作示例.
>创建了一个帮助程序类,该类定义了EventHandler< Plane< GDISurface> .DrawingErrorEventArgs>之间的隐式转换运算符.和EventHandler< GDIPlane.DrawingErrorEventArgs>其中GDIPlane只是从Plane< GDISurface>继承的伪类.这样做确实可以工作,但是由于转换会创建新的事件处理程序,因此会重复事件调用,这些事件处理程序将传递给_Plane,无法正确删除/取消注册.
>试图继承自EventHandler< Plane< GDISurface> .DrawingErrorEventArgs&gt ;,因为EventHandler< T>被密封.

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

最好的祝福
安德烈亚斯

编辑: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; } }
}

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

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

解决方法:

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

通常,如果事件上需要通用类型参数,并且需要对它们的设计时支持(这是不同的实现关注点),则可以将它们包装在特定于表示的外观中(例如,“额外的代码层”促进设计时需求”.)

就个人而言,我会减少您现在使用的泛型类型,这似乎有点过分,如果您不了解泛型类型的协方差/相反,可能会在某些时候使您陷入困境,例如现在.

但是,要变通解决此问题:

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

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

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

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

不幸的是,这也有一个协方差问题,要解决这个问题,您实际上需要更多类似这样的东西:

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

即便如此,这些通用版本仍无法解决Visual Studio的限制,这只是您已经向我们展示的内容的更合适的替代形式.

更新:按照要求,从最基本的意义上讲,这是“专用门面”的外观.请注意,在这种情况下,用户控件用作外观层,因为事件处理程序将用户委托公开给基础对象模型.从用户控件(从消费者/设计人员的角度来看)无法直接访问基础对象模型.

请注意,除非您在应用程序的整个生命周期内都弃用了这些用户控件,否则不需要对事件处理程序进行引用跟踪(这样做仅是为了确保根据提供的委托正确地删除委托,该委托包装在闭包/委托中,因为您会在下面看到).

同样值得注意的是,除了验证设计器放到表单上时设计器在属性网格中是否显示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);
                }
            }
        }
    }
}

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

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

请注意,这里的使用者必须具有类型的知识才能执行转换.在转换应该成功的假设下,使用as运算符将绕过祖先检查.

像这样的事情与您将要获得的接近.是的,按照我们的大多数标准来看,这是很丑陋的,但是,如果您绝对“需要”这些组件之上的设计时支持,并且您无法更改Plane< T> (可能会更合适),那么这是唯一可行的解​​决方法.

高温超导

标签:visual-studio-2013,event-handling,generics,c,winforms
来源: https://codeday.me/bug/20191120/2046138.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有