Background
Visual studio has a really cool feature in that allows you to log a message to the output window when a breakpoint is hit. Even more cool, you can reference live values by surrounding them with curly brackets. But wait: there's more; you can even call a method, with arbitrary signature (passing arguments and returning a value). Whatever value is returned is written in the output window. Awsome.
In VS 2013, this is the "When Hit" feature
and in VS 2015 (and 2017 RC), it's called "Action".
Motivation
I am trying to understand what's going on in the WPF source code, so I set breakpoints with an "Action" to output trace info. But, because WPF is not constraining the types of the values I'm interested in (they are passed as object
and the type inferred according to context), the values are more often than not logged as an error because the breakpoint Action is not aware of this context and therefore not aware of the type to expect.
My plan to deal with this is to call a custom logger from the breakpoint Action and pass arguments sufficient for the logger can resolve the type of the value.
The custom logger is a member of my MainWindow partial class.
Proof of concept
I verified that this works by setting a breakpoint action in my MainWindow partial class, it references the logger member and passes arguments no problem. This is fine for breakpoints within my app but I want to use it on breakpoints in other assemblies.
The problem
As stated above, this works fine when the breakpoint is in my own assembly but fails for breakpoints set in third party assemblies (like the wpf source code). Obviously the object needs to be qualified so that the debugger can resolve it.
How do I qualify it, how do I reference the instance of the window and therefore the member I'm looking for?
I guess I could make it a static singleton but I might need more than one logger.
I figured out that I can reference my logger. For example, if my main window partial class looks like this...
namespace EventSetterNull_SO_41604891_2670182
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
...then I can reference it like this...
((EventSetterNull_SO_41604891_2670182.MainWindow)System.Windows.Application.Current.MainWindow)
So, my breakpoint is like...
Then, I can set breakpoints in, for example, System.Windows.Markup.WpfXamlLoader.TransformNodes, pass the xamlReader to my logger, and log out the node structure that the parser generates from the baml.
xaml
<Window x:Class="EventSetterNull_SO_41604891_2670182.BuildInXaml"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:EventSetterNull_SO_41604891_2670182"
mc:Ignorable="d"
Title="BuildInXaml" Height="350" Width="525">
<Window.Resources>
<SetterBaseCollection x:Key="ButtonStyleSetters">
<Setter Property="FrameworkElement.Height" Value="30"></Setter>
</SetterBaseCollection>
</Window.Resources>
<Button Name="Button1"
local:Behaviours.StyleSetters="{StaticResource ButtonStyleSetters}" />
Output window
"Line: 6 NamespaceDeclaration http://schemas.microsoft.com/winfx/2006/xaml/presentation"
"Line: 6 NamespaceDeclaration http://schemas.microsoft.com/winfx/2006/xaml"
"Line: 6 NamespaceDeclaration http://schemas.microsoft.com/expression/blend/2008"
"Line: 6 NamespaceDeclaration http://schemas.openxmlformats.org/markup-compatibility/2006"
"Line: 6 NamespaceDeclaration clr-namespace:EventSetterNull_SO_41604891_2670182;assembly=EventSetterNull-SO-41604891-2670182"
"Line: 6 StartObject BuildInXaml"
"Line: 6 StartMember Title"
"Line: 6 Value BuildInXaml"
"Line: 6 EndMember "
"Line: 8 StartMember Height"
"Line: 8 Value System.Windows.Baml2006.TypeConverterMarkupExtension"
"Line: 8 EndMember "
"Line: 8 StartMember Width"
"Line: 8 Value System.Windows.Baml2006.TypeConverterMarkupExtension"
"Line: 8 EndMember "
"Line: 8 StartMember Resources"
"Line: 9 GetObject System.Windows.Baml2006.Baml2006SchemaContext"
"Line: 9 StartMember DeferrableContent"
"Line: 9 Value System.IO.MemoryStream"
"Line: 9 EndMember "
"Line: 9 EndObject "
"Line: 15 EndMember "
"Line: 17 StartMember Content"
"Line: 17 StartObject Button"
"Line: 17 StartMember ConnectionId"
"Line: 17 Value 1"
"Line: 17 EndMember "
"Line: 0 StartMember Name"
"Line: 0 Value Button1"
"Line: 0 EndMember "
"Line: 17 StartMember StyleSetters"
"Line: 17 Value System.Windows.StaticResourceExtension"
"Line: 17 EndMember "
"Line: 10 StartObject SetterBaseCollection"
"Line: 10 StartMember _Items"
"Line: 11 StartObject Setter"
"Line: 11 StartMember Property"
"Line: 11 Value Height"
"Line: 11 EndMember "
"Line: 11 StartMember Value"
"Line: 11 Value 30"
"Line: 11 EndMember "
"Line: 11 EndObject "
"Line: 11 EndMember "
"Line: 11 EndObject "
"Line: 18 EndObject "
"Line: 18 EndMember "
"Line: 18 EndObject "
So, I can see exactly how wpf sees the xaml, which is handy if it screws up...
xaml
<Window x:Class="EventSetterNull_SO_41604891_2670182.BuildInXaml"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:EventSetterNull_SO_41604891_2670182"
mc:Ignorable="d"
Title="BuildInXaml" Height="350" Width="525">
<Window.Resources>
<SetterBaseCollection x:Key="ButtonStyleSetters">
<Setter Property="FrameworkElement.Height" Value="30"></Setter>
<EventSetter Event="ButtonBase.Click" Handler="StyleClick"
HandledEventsToo="False" />
</SetterBaseCollection>
</Window.Resources>
<Button Name="Button1"
local:Behaviours.StyleSetters="{StaticResource ButtonStyleSetters}" />
Output window
"Line: 6 NamespaceDeclaration http://schemas.microsoft.com/winfx/2006/xaml/presentation"
"Line: 6 NamespaceDeclaration http://schemas.microsoft.com/winfx/2006/xaml"
"Line: 6 NamespaceDeclaration http://schemas.microsoft.com/expression/blend/2008"
"Line: 6 NamespaceDeclaration http://schemas.openxmlformats.org/markup-compatibility/2006"
"Line: 6 NamespaceDeclaration clr-namespace:EventSetterNull_SO_41604891_2670182;assembly=EventSetterNull-SO-41604891-2670182"
"Line: 6 StartObject BuildInXaml"
"Line: 6 StartMember Title"
"Line: 6 Value BuildInXaml"
"Line: 6 EndMember "
"Line: 8 StartMember Height"
"Line: 8 Value System.Windows.Baml2006.TypeConverterMarkupExtension"
"Line: 8 EndMember "
"Line: 8 StartMember Width"
"Line: 8 Value System.Windows.Baml2006.TypeConverterMarkupExtension"
"Line: 8 EndMember "
"Line: 8 StartMember Resources"
"Line: 9 GetObject System.Windows.Baml2006.Baml2006SchemaContext"
"Line: 9 StartMember DeferrableContent"
"Line: 9 Value System.IO.MemoryStream"
"Line: 9 EndMember "
"Line: 9 EndObject "
"Line: 15 EndMember "
"Line: 17 StartMember Content"
"Line: 17 StartObject Button"
"Line: 17 StartMember ConnectionId"
"Line: 17 Value 1"
"Line: 17 EndMember "
"Line: 0 StartMember Name"
"Line: 0 Value Button1"
"Line: 0 EndMember "
"Line: 17 StartMember StyleSetters"
"Line: 17 Value System.Windows.StaticResourceExtension"
"Line: 17 EndMember "
"Line: 10 StartObject SetterBaseCollection"
"Line: 10 StartMember _Items"
"Line: 11 StartObject Setter"
"Line: 11 StartMember Property"
"Line: 11 Value Height"
"Line: 11 EndMember "
"Line: 11 StartMember Value"
"Line: 11 Value 30"
"Line: 11 EndMember "
"Line: 11 EndObject "
"Line: 12 StartObject EventSetter"
"Line: 12 StartMember Event"
"Line: 12 Value System.Windows.Baml2006.TypeConverterMarkupExtension"
"Line: 12 EndMember "
"Line: 12 StartMember Event"
Exception thrown: 'System.Xaml.XamlDuplicateMemberException' in System.Xaml.dll
As you can see, wpf is getting confused and finding a second Event member in the EventSetter object, that's not there and throwing an error.
This is a bug in wpf.