Thursday 22 October 2009

Getting the MethodInfo of a generic method using Lambda expressions

Getting hold of the MethodInfo of a generic method via Reflection (so you can invoke it dynamically, for example) can be a bit of a pain. So this afternoon I formulated a pain-killer, SymbolExtensions.GetMethodInfo. It’s not fussy: it works for non-generic methods too. You use it like this:

internal class SymbolExtensionsTests
{
    [Test]
    public void GetMethodInfo_should_return_method_info()
    {
        var methodInfo = SymbolExtensions.GetMethodInfo<TestClass>(c => c.AMethod());
        methodInfo.Name.ShouldEqual("AMethod");
    }

    [Test]
    public void GetMethodInfo_should_return_method_info_for_generic_method()
    {
        var methodInfo = SymbolExtensions.GetMethodInfo<TestClass>(c => c.AGenericMethod(default(int)));

        methodInfo.Name.ShouldEqual("AGenericMethod");
        methodInfo.GetParameters().First().ParameterType.ShouldEqual(typeof(int));
    }

    [Test]
    public void GetMethodInfo_should_return_method_info_for_static_method_on_static_class()
    {
        var methodInfo = SymbolExtensions.GetMethodInfo(() => StaticTestClass.StaticTestMethod());

        methodInfo.Name.ShouldEqual("StaticTestMethod");
        methodInfo.IsStatic.ShouldBeTrue();
    }
}

The active ingredient, as you can see, is Lambda expressions:

using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System;

public static class SymbolExtensions
{
    /// <summary>
    /// Given a lambda expression that calls a method, returns the method info.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="expression">The expression.</param>
    /// <returns></returns>
    public static MethodInfo GetMethodInfo(Expression<Action> expression)
    {
        return GetMethodInfo((LambdaExpression)expression);
    }

    /// <summary>
    /// Given a lambda expression that calls a method, returns the method info.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="expression">The expression.</param>
    /// <returns></returns>
    public static MethodInfo GetMethodInfo<T>(Expression<Action<T>> expression)
    {
        return GetMethodInfo((LambdaExpression)expression);
    }

    /// <summary>
    /// Given a lambda expression that calls a method, returns the method info.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="expression">The expression.</param>
    /// <returns></returns>
    public static MethodInfo GetMethodInfo<T, TResult>(Expression<Func<T, TResult>> expression)
    {
        return GetMethodInfo((LambdaExpression)expression);
    }

    /// <summary>
    /// Given a lambda expression that calls a method, returns the method info.
    /// </summary>
    /// <param name="expression">The expression.</param>
    /// <returns></returns>
    public static MethodInfo GetMethodInfo(LambdaExpression expression)
    {
        MethodCallExpression outermostExpression = expression.Body as MethodCallExpression;

        if (outermostExpression == null)
        {
            throw new ArgumentException("Invalid Expression. Expression should consist of a Method call only.");
        }

        return outermostExpression.Method;
    }
}

Thursday 15 October 2009

Flattery (almost) works

I checked my email using my smart-phone the other night, and found this:

Anonymous has left a new comment on your post "I'll be speaking at PDC 2008...":

Masters thesisI have been through the whole content of this blog which is very informative and knowledgeable stuff, So i would like to visit again.

How nice. I'm always chuffed when somebody takes the time to leave an appreciative comment on my blog. And this one stoked my ego. At a glance it seemed that Mr (or Ms) Anonymous was likening the quality and content of my work to a Masters thesis; though the wording of the message was a little strange ... perhaps English was not their native language?

Ever polite, the next time I went to my PC I fired up the browser and found the appropriate page on my blog with the intention of thanking Mr (or Ms) Anonymous for their kind words and inviting them to visit as often as they liked.

But the browser revealed what my phone's mail client had concealed. The words "Masters Thesis" were underlined in blue - a hyperlink to a site whose page rank I will not promote by repeating the link. Cue sound of hissing steam as ego is rapidly quenched.

Out of curiosity at the wares being peddled by these flattersome spammers, I followed the link, and found offers of written-to-order essays or Masters Thesis - guaranteed native English, and plagiarism free. The kind-hearted scholars at the site will also undertake to complete assignments and exams for online courses and can guarantee to ace them all - for a small consideration of $2000 plus $300 per exam.

Needless to say, I put my blog's trash-can button to good use.

Saturday 10 October 2009

Practical LINQ #4: Finding a descendant in a tree

Trees are everywhere. I don’t mean the green, woody variety. I mean the peculiar ones invented by programmers that have their root somewhere in the clouds and branch downwards. As inevitably as apples fall from trees on the heads of meditating physicists, so coders find themselves writing code to traverse tree structures.

In an earlier post we explored the tree created by the UI Automation API representing all the widgets on the Windows desktop. One very common tree-traversal operation is finding a particular child several nodes down from a root starting point. I gave the example of locating the Paint.Net drawing canvas within the main window of the application:

// find the Paint.Net drawing Canvas
 var canvas = mainWindow.FindDescendentByIdPath(new[] {
     "appWorkspace",
       "workspacePanel",
         "DocumentView",
           "panel",
             "surfaceBox" });

What we have here is an extension method, FindDescendentByIdPath, that starts from a parent element and works its way down through the tree, at each level picking out the child with the given Automation Id. In my last post, I skipped over my implementation of this method, but it deserves a closer look because of the way it uses Functional techniques and LINQ to traverse the hierarchy.

So here it is:

public static AutomationElement FindDescendentByIdPath(this AutomationElement element, IEnumerable<string> idPath)
{
    var conditionPath = CreateConditionPathForPropertyValues(AutomationElement.AutomationIdProperty, idPath.Cast<object>());

    return FindDescendentByConditionPath(element, conditionPath);
}

public static IEnumerable<Condition> CreateConditionPathForPropertyValues(AutomationProperty property, IEnumerable<object> values)
{
    var conditions = values.Select(value => new PropertyCondition(property, value));

    return conditions.Cast<Condition>();
}

public static AutomationElement FindDescendentByConditionPath(this AutomationElement element, IEnumerable<Condition> conditionPath)
{
    if (!conditionPath.Any())
    {
        return element;
    }

    var result = conditionPath.Aggregate(
        element,
        (parentElement, nextCondition) => parentElement == null
                                              ? null
                                              : parentElement.FindChildByCondition(nextCondition));

    return result;
}

The first thing we do is convert the sequence of Automation Id strings into a sequence of Conditions (actually PropertyConditions) that the UI Automation API can use to pick out children of parent elements. This is handled for us by the CreateConditionPathForPropertyValues method in line 8.

The actual method of interest is FindDescendentByConditionPath, starting in line 15. Here we put the Enumerable.Aggregate method to a slightly unconventional use. Considered in the abstract, the Aggregate method takes elements from a sequence one by one and performs an function (of your choice) to combine the element with the previous result; the very first element is combined with a seed value.

In this case the seed value is the parent element at the top of tree, the sequence of elements is the list of conditions that we use to pick out the child at each level of the tree. And we provide a lambda function that, each time it is called, takes the element it found in the previous iteration, together with the next condition from the list and uses an extension method that I demonstrated in the earlier blog post, FindChildByCondition, to find the appropriate child.

I’ve found this method a great help when monkeying around in UI Automation trees. If you think you might, look for it in the the source code from last time.

Friday 2 October 2009

Work around for WPF Bug: Drag and Drop does not work when executing a WPF application in a non-default AppDomain

Today's task was implementing Drag/Drop to move items into folders. My estimate of yesterday was 6 hours to get the job done. That should have been plenty: then I discovered the bug.

We're creating an Excel Addin using Addin-Express (which is very like VSTO). All the UI for our add-in is built with WPF (naturally). When Addin Express hosts Add-ins for Excel, it isolates them each in their own App-Domain. Which is why today I came to add myself to the list of people who have reproduced this issue on the Connect website: "Drag and Drop does not work when executing a WPF application in a non-default AppDomain"

Googling around the problem, I found that Tao Wen had also encountered the problem, and had managed to find something of a work-around.

Having Reflectored around the WPF source-code a little, I can see that when WPF is hosted in a non-default App Domain, the check that the HwndSource class (which manages the Win32 Window object on behalf of the WPF Window class) does to ensure the code has the UnmanagedCode permission fails, so Windows do not register themselves as OLE Drop Targets (see the last few lines of the HwndSource.Initialize method)

Tao Wen's solution is to use reflection to directly call the internal DragDrop.RegisterDropTarget method. What his solution doesn't take into account is that, when the Window is closed it must unregister itself as a drop target.

Fortunately, by incrementing the _registeredDropTargetCount field in the HwndSource class we can ensure that HwndSource will itself call DropDrop.RevokeDropTarget when it's disposed (see about half-way down the HwndSource.Dispose method).

For anybody else who is blocked by the same issue, I've created a little class that will implement the hack on a Window class. Be warned: it uses evil reflection code to access private members of WPF classes, and it might break at any time. Use it at your own risk.

/// <summary>
/// Contains a helper method to enable a window as a drop target.
/// </summary>
public static class DropTargetEnabler
{
    /// <summary>
    /// Enables the window as drop target. Should only be used to work around the bug that
    /// occurs when WPF is hosted in a non-default App Domain
    /// </summary>
    /// <param name="window">The window to enable.</param>
    /// <remarks>
    /// This is a hack, so should be used with caution. It might stop working in future versions of .Net.
    /// The original wpf bug report is here: http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=422485
    /// </remarks>
    // This code was inspired by a post made by Tao Wen on the Add-in Express forum:
    // http://www.add-in-express.com/forum/read.php?FID=5&TID=2436
    public static void EnableWindowAsDropTarget(Window window)
    {
        window.Loaded += HandleWindowLoaded;
    }

    private static void HandleWindowLoaded(object sender, RoutedEventArgs e)
    {
        // See the HwndSource.Initialize method to see how it sets up a window as a Drop Target
        // also see the HwndSource.Dispose for how the window is removed as a drop target
        var window = sender as Window;

        IntPtr windowHandle = new WindowInteropHelper(window).Handle;

        // invoking RegisterDropTarget calls Ole32 RegisterDragDrop
        InvokeRegisterDropTarget(windowHandle);

        // to make sure RevokeDragDrop gets called we have to increment the private field
        // _registeredDropTargetCount on the HwndSource instance that the Window is attached to
        EnsureRevokeDropTargetGetsCalled(windowHandle);
    }

    private static void InvokeRegisterDropTarget(IntPtr windowHandle)
    {
        var registerDropTargetMethod = typeof(System.Windows.DragDrop)
            .GetMethod("RegisterDropTarget", BindingFlags.Static | BindingFlags.NonPublic);
        if (registerDropTargetMethod == null)
        {
            throw new InvalidOperationException("The EnableWindowAsDropTarget Hack no longer works!");
        }

        registerDropTargetMethod
            .Invoke(null, new object[] { windowHandle });
    }

    private static void EnsureRevokeDropTargetGetsCalled(IntPtr windowHandle)
    {
        var hwndSource = HwndSource.FromHwnd(windowHandle);
        var fieldInfo = typeof(HwndSource).GetField("_registeredDropTargetCount", BindingFlags.NonPublic | BindingFlags.Instance);
        if (fieldInfo == null)
        {
            throw new InvalidOperationException("The EnableWindowAsDropTarget hack no longer works!");
        }

        var currentValue = (int) fieldInfo.GetValue(hwndSource);
        fieldInfo.SetValue(hwndSource, currentValue + 1);
    }
}

Use it like this:

public partial class MyWindow
 {
        public MyWindow()
        {
            InitializeComponent();
            DropTargetEnabler.EnableWindowAsDropTarget(this);
        }
}