Вопрос

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.

Это было полезно?

Решение

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.

Другие советы

Try the following definition below:

public static class Xtd
{
    public static void AddEventEasy(this object component, EventInfo eventInfo, Delegate eventAction)
    {
        Delegate ConvertDelegate(Delegate originalDelegate, Type targetDelegateType)
        {
            return Delegate.CreateDelegate(
                targetDelegateType,
                originalDelegate.Target,
                originalDelegate.Method);
        }

        eventInfo.AddEventHandler(component, ConvertDelegate(eventAction, eventInfo.EventHandlerType));
    }
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top