How do I bind an Anonymous action as new event handler using reflection

自作多情 提交于 2020-01-04 13:38:02

问题


I am writing a number of unit tests for Windows Forms, and so far have been able to figure out how to set private controls' properties and invoke their methods, using Reflection. But I am stuck on how to associate an in-line lambda to attach itself to an event occurring on one of the controls, in this case the DataSourceChanged event of DataGridView.

public static void ObserveGrid(this Form form, string controlName, Action action)
{
    var controls = form.Controls.Find(controlName, true);
    if (controls.Any())
    {
        var control = controls[0] as DataGridView;
        if (control != null)
        {
            EventInfo ei = typeof(DataGridView).GetEvent("DataSourceChanged");
            if (ei != null)
            {
                ei.AddEventHandler(control, Delegate.CreateDelegate(ei.EventHandlerType, control, action.Method ));
            }
        }
    }
}

I was hoping to call it like this:

var monitor = new Mutex();
form.ObserveGrid("dataGridView1",
    () =>
    {
        Trace.WriteLine("Releasing mutex.");
        monitor.ReleaseMutex();
    });
var sw = new Stopwatch();
form.ClickButton("btnSearch", sw);
monitor.WaitOne();
sw.Stop();

During execution I get an error:

Cannot bind to the target method because its signature or security transparency is not compatible with that of the delegate type.

What am I doing wrong in this case?

UPDATE:

Using this great post, I have changed my Extensions class like so:

    public static void ObserveGrid(this Form form, string controlName, Action<object,object> action)
    {
        var controls = form.Controls.Find(controlName, true);
        if (controls.Any())
        {
            var control = controls[0] as DataGridView;
            if (control != null)
            {
                EventInfo ei = typeof(DataGridView).GetEvent("DataSourceChanged");
                if (ei != null)
                {
                    Delegate handler = ConvertDelegate(action, ei.EventHandlerType);
                    ei.AddEventHandler(control, handler);
                }
            }
        }
    }

    public static Delegate ConvertDelegate(Delegate originalDelegate, Type targetDelegateType)
    {
        return Delegate.CreateDelegate(
            targetDelegateType,
            originalDelegate.Target,
            originalDelegate.Method);
    }

However I get another error, this time about releasing the mutex from an non-synchronized thread:

Releasing mutex. System.Reflection.TargetInvocationException : Exception has been thrown by the target of an invocation. ----> System.ApplicationException : Object synchronization method was called from an unsynchronized block of code.

UPDATE 2

Swapping Mutex for SemaphoreSlim resolved the synchronization issue.


回答1:


Here is what I ended up doing:

First, the Extensions class:

public static class Extensions
{
    #region Public Methods and Operators

    public static Stopwatch ClickButton(this Form form, string controlName)
    {
        var controls = form.Controls.Find(controlName, true);
        if (controls.Any())
        {
            var control = controls[0] as Button;
            if (control != null)
            {
                MethodInfo mi = typeof(Button).GetMethod("OnClick", BindingFlags.NonPublic | BindingFlags.Instance);
                var stopWatch = Stopwatch.StartNew();
                mi.Invoke(
                    control,
                    new object[]
                    {
                        EventArgs.Empty
                    });
                return stopWatch;
            }
        }
        throw new ApplicationException("Control not found or of invalid Type");
    }

    public static Delegate ConvertDelegate(Delegate originalDelegate, Type targetDelegateType)
    {
        return Delegate.CreateDelegate(targetDelegateType, originalDelegate.Target, originalDelegate.Method);
    }

    public static object GetControlProperty<T>(this Form form, string controlName, string propertyName) where T : class
    {
        var controls = form.Controls.Find(controlName, true);
        if (controls.Any())
        {
            var control = controls[0];
            PropertyInfo pi = typeof(T).GetProperty(propertyName);
            return pi.GetValue(control, null);
        }
        throw new ApplicationException("Control not found or of invalid Type");
    }

    public static void ObserveControlEvents<T>(this Form form, string controlName, string eventName, Action<object, object> action) where T : class
    {
        var controls = form.Controls.Find(controlName, true);
        if (controls.Any())
        {
            var control = controls[0] as T;
            if (control != null)
            {
                EventInfo ei = typeof(T).GetEvent(eventName);
                if (ei != null)
                {
                    Delegate handler = ConvertDelegate(action, ei.EventHandlerType);
                    ei.AddEventHandler(control, handler);
                }
            }
        }
    }

    public static void ObserveGrid(this Form form, string controlName, string eventName, Action<object, object> action)
    {
        var controls = form.Controls.Find(controlName, true);
        if (controls.Any())
        {
            var control = controls[0] as DataGridView;
            if (control != null)
            {
                EventInfo ei = typeof(DataGridView).GetEvent(eventName);
                if (ei != null)
                {
                    Delegate handler = ConvertDelegate(action, ei.EventHandlerType);
                    ei.AddEventHandler(control, handler);
                }
            }
        }
    }

    public static void SetControlProperty<T>(this Form form, string controlName, string propertyName, object value) where T : class
    {
        var controls = form.Controls.Find(controlName, true);
        if (controls.Any())
        {
            var control = controls[0];
            PropertyInfo pi = typeof(T).GetProperty(propertyName);
            pi.SetValue(control, value, null);
        }
    }

    public static void SetFormProperty(this Form form, string controlName, object value)
    {
        var controls = form.Controls.Find(controlName, true);
        if (controls.Any())
        {
            var control = controls[0];
            PropertyInfo pi = typeof(Control).GetProperty("Text");
            pi.SetValue(control, value, null);
        }
    }

    #endregion
}

Now, in my unit tests I can create the form, set values, trigger and observe the events:

[TestFixture]
public class FormsTests
{
    #region Public Methods and Operators

    [TestCase(null, null, null, 1000)]
    [TestCase("Kim", null, null, 500)]
    [TestCase("Kim", null, "Akers", 250)]
    [TestCase("Kim", "B", "Abercrombie", 100)]
    public void InsuredSearcherResponseTimeWithReflectionTest(string firstName, string middleName, string lastName, long milliseconds)
    {
        var monitor = new SemaphoreSlim(1);
        monitor.Wait();
        var form = new Insured();
        form.SetControlProperty<TextBox>("tbFirstName", "Text", firstName);
        form.SetControlProperty<TextBox>("tbMiddleName", "Text", middleName);
        form.SetControlProperty<TextBox>("tbLastName", "Text", lastName);
        form.ObserveControlEvents<DataGridView>(
            "dataGridView1",
            "DataSourceChanged",
            (sender, args) =>
            {
                Trace.WriteLine("Occured in delegate");
                monitor.Release();
            });
        Trace.WriteLine("Executing");
        var sw = form.ClickButton("btnSearch");
        monitor.Wait();
        sw.Stop();
        Trace.WriteLine(String.Format("Row count was {0} took {1}ms to process", form.GetControlProperty<DataGridView>("dataGridView1", "RowCount"), sw.ElapsedMilliseconds));
        Assert.IsTrue(sw.ElapsedMilliseconds < milliseconds);
    }

    [TestFixtureSetUp]
    public void TestFixtureSetup()
    {
        var monitor = new SemaphoreSlim(1);
        monitor.Wait();
        var form = new Insured();
        form.ObserveControlEvents<DataGridView>(
            "dataGridView1",
            "DataSourceChanged",
            (sender, args) =>
            {
                Trace.WriteLine("Occured in delegate");
                monitor.Release();
            });
        form.ClickButton("btnSearch");
        monitor.Wait();
    }
}

I hope this helps someone as well.



来源:https://stackoverflow.com/questions/23658733/how-do-i-bind-an-anonymous-action-as-new-event-handler-using-reflection

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!