Freigeben über


Expanding WPF for UI Debugging

The Optimum User Experience

Since the very earliest days of WPF/XAML development, there has always been a need to inspect the visual tree. Early Pre-Alpha builds of Blend had a debug command which would dump the entire visual tree to a text file so developers could look through it for anomalies. We had internal tools for inspecting visual trees and the properties of those elements and debugging capabilities of this type have been a frequent ask for years. For Visual Studio 2015, we wanted to take a look at the needs that developers, both internal and external, faced and look at the tools we’ve made to create the optimal runtime inspection tools that we could. Information on how to use the UI debugging tools can be found here, but if you’re interested in how we made it happen, please read on.

After examining our internal solutions and the common issues we’ve heard and put together the following list of items we wanted to do:

  • The ability to inspect an existing installed app or an ‘in-development’ app at debug time.

  • A representation of the visual tree that is always up to date.

  • The ability to select elements by selecting them in the running app.

  • The ability to locate and select any element in the representation of the visual tree.

  • A command to locate a given element in the source XAML.

  • A complete list of all properties on any selected element, displayed based on where the property is set (locally, in a Style, etc…).

  • The ability to change any property setter at any scope.

  • A command to jump to wherever a property setter is defined if it comes from XAML.

  • A persistence option to push runtime debugging changes into the XAML.

We created these requirements based on the needs of XAML developers, regardless of their platform or target device of choice. Naturally, the experience would tweak slightly based on these factors. The way a user selects an element for investigation on the desktop is going to be different from selection on a Windows Phone.

What do we need and what is missing?

The first system we need is one that allows us to talk to different apps on different platforms. We started by creating the XamlDiagnosticTap.dll, which is a generic solution that the debugger extension in Visual Studio knows how to communicate with. This assembly is loaded by one of a set of platform specific assemblies. For instance, in a WPF application, WPFXamlDiagnosticTap.dll is injected into the running app and it will pull in the generic XamlDiagnosticTap.dll. It’s obvious that we’ll see other flavors of the platform level tap, like DesktopXamlDiagnosticTap.dll or WPXamlDiagnosticTap.dll.

 

When we compared our requirement list to the capabilities of WPF, we found that we really only had two shortcomings. WPF didn’t provide a system for listening to changes in the visual tree, and it only stored source information privately in BAML files.

The VisualTreeChanged event

It turns out that all Visual Tree manipulations channel through four or five specific methods. These methods had all the information that we wanted, including the handles to the elements being added or removed. Adding the events here was surprisingly simple. The final implementation fires the events whenever any element is added or removed from any other element.

The API ends up looking like this:

public enum VisualTreeChangeType { Add, Remove }

public static event EventHandler<VisualTreeChangeEventArgs> System.Windows.Diagnostics.VisualDiagnostics.VisualTreeChanged

public class VisualTreeChangeEventArgs : EventArgs

{

  public DependencyObject Parent { get; }

  public DependencyObject Child { get; }

  public int ChildIndex { get; }

  public VisualTreeChangeType ChangeType { get; }

}

It should be noted that this API is designed as a debugger API. Specifically, when you attempt to register for notifications on the VisualTreeChanged event, the request to register is ignored if the debugger is not currently attached. It’s also worth mentioning that at the time of this writing, this API is still in development, and it’s subject to change.

The GetXamlSourceInfo method

The source code information was a bit trickier. For elements originating from a BAML file, the elements already knew their source information. They knew the source XAML file that declared the elements as well as the Line/Column information to locate the declarations. Unfortunately, the information was private to the element and couldn’t be extracted by the diagnostic tap. Then, unfortunately, elements originating from a XAML file didn’t have their source information at all.

Exposing the already existing file information for BAML elements was easy, but we started some interesting discussions around the XAML elements. BAML is an optional step, and many people don’t even use it. Additionally, while folks are still working on their app, it’s uncommon for people to already be creating BAML files, so in our investigations, most people worked from XAML files until later in the development process. Now, consider what it means to add the source information. For every element, we need to add the file information along with the line/column information. While this isn’t a large amount per element, when you add it all together, it’s a sufficiently significant amount of memory that may obscure other memory issues. We eventually decided that the source information for XAML originating elements would only be available in Debug builds of the user’s apps, and we’d require a launch-time switch to enable it, even for Debug builds.

For DependencyObject elements, it’s trivial to add an additional attached DependencyProperty, and in that property, store the file, line and row information. However, non-DependencyObject elements, like Styles, don’t afford us this luxury. For these, we used the handy ConditionalWeakTable object from the System.Runtime.CompilerServices namespace. Simply put, the ConditionalWeakTable is similar to a dictionary, but with the noted exception that it doesn’t hold references to the objects inside it. This means that if we create a reference to Object A and all other references to A cease to exist, the ConditionalWeakTable deletes the reference to A. This prevents the ConditionalWeakTable from keeping objects alive once the system has released all other references to them.

In effect, an element gets created, we store its source information in the ConditionalWeakTable. If the app deletes the element and abandons all of its references to it, the ConditionalWeakTable automatically removes the source information from itself and the object is ready for Garbage Collection. More information on the ConditionalWeakTable can be found on MSDN.

The API for getting XamlSourceInfo is quite straightforward:

  public static XamlSourceInfo System.Windows.Diagnostics.VisualDiagnostics.GetXamlSourceInfo(object obj)

  public class XamlSourceInfo

  {

    public Uri SourceUri { get; }

    public int LineNumber;

    public int LinePosition;

  }

However, in order to use to use the method, two things need to be true first. As discussed above, the SourceInfo is only available for Debug builds of apps. Second, the environment variable ENABLE_XAML_DIAGNOSTICS_SOURCE_INFO will be checked when the app is started. If the variable is set and set to any value except 0 or false, WPF will store the source information for all elements. When launching your app from inside Visual Studio, the debugger extension will automatically set this in the process it will use to launch your app. However, if you’re trying to get source information on another machine, you can manually set the environment variable before launching the Debug build of the app, and you will be able to attach and get source information.

Additionally, like the VisualTreeChanged event, the GetXamlSourceInfo API is subject to change. MSDN will have the latest up to date information when the API is finalized.

Conclusion

The architecture for these tools had to be created in a platform agnostic way, in order to have a consistent experience across platforms. Then, we needed to create a platform dependent wrapper around our generic architecture to handle the various differences between the platforms. Finally, when we looked at the user experience we wanted to deliver, we had to choose between cutting back on the experience or extending the platform. When it came down to it, we simply weren’t satisfied with the experience we’d be delivering to our WPF developers without the platform extensions. The decision to deliver the top-notch experience that we wanted was an easy one to make.

Dante Gagne is a Program Manager with 12 years of experience with XAML. He started as one of the original testers on Expression Blend and brings his passion to XAML developers in Visual Studio. His focus is on the design experience and productivity.

Comments

  • Anonymous
    February 24, 2015
    it's alive

  • Anonymous
    February 24, 2015
    This is looking very promising. A complex portion of our UI is created in code at runtime instead of through XAML. Will there be an API exposed where my debug build could annotate my generated objects with FILE and LINE info as well?

  • Anonymous
    February 24, 2015
    @jschroedl - For now, no, we won't be exposing an API for annotating your generated objects. It is, however, something we want to investigate in the future.

  • Anonymous
    February 24, 2015
    Good choice to force-disable these APIs without a debugger. It's really annoying when third party libraries ignore the big picture and register for such events because "it's easy".

  • Anonymous
    February 25, 2015
    I'm glad this feature is coming to Visual Studio. Snoop, WPF Inspector and XAML Spy are all nice tools, but nothing replaces VS integration :)

  • Anonymous
    February 27, 2015
    Exciting... Keep investing in WPF - It's an awesome tool. I assume performance improvements in the rendering is coming along soon too? :)

  • Anonymous
    March 03, 2015
    I was wondering, is there a reason that there is no ParentChanged event for Xaml objects which have parents? any chance of inserting that one?

  • Anonymous
    March 04, 2015
    @Eyal Perry I'd like to know what you're looking for. If you're asking about a situation where the parent of a given element changes, that would actually cause the VisualTreeChanged event to fire twice, once when the given element is disconnected from its original parent and once when it gets added to the new parent. If you're asking about a generic event to indicate when a property is changed on the parent, that should be available via PropertyChanged events. Can you explain a specific scenario that you'd like a solution for? Thanks! Dante

  • Anonymous
    March 04, 2015
    This is great.  Here's hoping this isn't just a token gesture and that WPF will finally start to get developed again.  My concern is that we're so close to the release of VS2015 that I'd be surprised if any additional WPF improvements are done before it's released, and although this is a worthwhile development requiring platform changes it is, at the end of the day, a debugging nicety and not something an end-user will benefit from as far as a WPF app goes. When I start seeing significant performance and stability improvements, cleaner/simpler XAML, better MVVM & data-binding support, and user controls at least on a par with what WinForms had 10(!!) years ago, then I'll start believing Microsoft is doing something more than just giving the brass on the Titanic one last polish.

  • Anonymous
    March 25, 2015
    Hello, guys. I wonder, can you improve DropShadowEffect? It has huge impact for scrolling, etc. Thanks.

  • Anonymous
    March 25, 2015
    I very much appreciate you guys picking up the pace on WPF improvement. Keep it coming! A couple month ago, I commented on your roadmap post that I'd wish you'd publish WPF as a nuget package to be able to do quicker and bolder changes. I'm very happy to learn that this is excactly what happend :) I'm looking forward to the future. Now I feel more comfortable to continue recommending WPF as one possibility for new business applications.

  • Anonymous
    April 01, 2015
    Will you at least fix airspace issues in upcoming release?

  • Anonymous
    April 20, 2015
    > A persistence option to push runtime debugging changes into the XAML. Does mean something like Edit & Continue for XAML? Because that's what I am really missing. It's quite annoying having to restart the app for minor layout changes...

  • Anonymous
    April 22, 2015
    The comment has been removed