Snoop Now Works in WPF Interop Scenarios, Part II

First off, in my previous post, I made the mistake of assuming (given the new look and new functionality) that Pete Blois had bumped the version of Snoop up to 2.0. This does not seem to be the case. So, oops! My apologies!

Now, onto the meat of post: I have found a couple more places where you need to insulate yourself from not having a current Application … and I was able to fix the keyboard issue!

Let’s tackle the keyboard issue first as that is the big one. In the Inspect method of the SnoopUI class, you need to call the static method ElementHost.EnableModelessKeyboardInterop and you need to do that when in an interop scenario, that is, when the current Application is null.

The MSDN documentation for this method states that this method allows a System.Windows.Window to receive keyboard messages correctly when opened modelessly from Windows Forms (our interop scenario). Hmm. Sounds exactly like what I was looking for!

public void Inspect(object target)
{
    this.rootObject = target;
    this.Load(target);
    this.CurrentSelection = this.root;

    this.OnPropertyChanged("Root");

    if (Application.Current != null)
    {
        this.Owner = Application.Current.MainWindow;
    }
    else
    {
        ElementHost.EnableModelessKeyboardInterop(this);
    }
    this.Show();
}


This also requires you to add a reference to WindowsFormsIntegration.dll and to add a using statement:

using System.Windows.Forms.Integration;

With the above changes, you should be able to use the keyboard once again to change property values in the property grid that comes up when you Snoop something. Whew! That copy/paste trick was getting old, really fast.

Now, onto the additional places where you need to protect yourself from not having an Application instance. First, the Previewer was broken … and you need to modify the HandleMagnify method of the Previewer class:

private void HandleMagnify(object sender, ExecutedRoutedEventArgs e)
{
    Zoomer zoomer = new Zoomer();
    zoomer.Target = this.Target;
    if (Application.Current != null)
        zoomer.Owner = Application.Current.MainWindow;
    zoomer.Show();
    e.Handled = true;
}


Second, you need to modify the ProcessFilter method of the SnoopUI class so that it uses the Dispatcher for the current thread versus grabbing it off the non-existent Application instance:

private void ProcessFilter()
{
    this.filtered.Clear();

    // Blech.
    if (this.filter == "Clear Filter")
    {
        Dispatcher dispatcher = null;
        if (Application.Current == null)
            dispatcher = Dispatcher.CurrentDispatcher;
        else
            dispatcher = Application.Current.Dispatcher;

        dispatcher.BeginInvoke
        (
            DispatcherPriority.Loaded,
            new DispatcherOperationCallback
            (
                delegate(object arg)
                {
                    this.Filter = string.Empty;
                    return null;
                }
            ),
            null
        );
        return;
    }
    if (this.filter == "Visuals with binding errors")
        this.FilterBindings(this.root);
    else if (this.filter.Length == 0)
        this.filtered.Add(this.root);
    else
        this.FilterTree(this.root, this.filter.ToLower());
}

By the way, I would love to hear (by leaving a comment on the blog here), if this helps you at all. I was truly missing the ability to Snoop our WPF interop application myself.

Enjoy!

p.s.

I would love to make my source code modifications to Snoop readily available to the general public, but I have been unable to contact Pete Blois and obtain his permission to do so. Sorry!

(The source code and binaries for Snoop are now (see posts: 1, 2) up on CodePlex. Please go download it from there as that code will contain the latest and greatest bits.)