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

相当早以前,我注意到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> (可能会更合适),那么这是唯一可行的解​​决方法.

高温超导

上一篇:c#-Properties.Settings.Default.Save();不保存在用户计算机上


下一篇:c#-Windows窗体设置值不会在会话之间持续存在