Error de baml de WPF: EventSetter en recurso estático se configura dos veces, la segunda vez es nulo

Si intento almacenar una colección de objetos SetterBase en xaml, que incluye y EventSetter, el cargador xaml arroja un error.

La causa raíz es que el cargador xaml intenta establecer PresentationFramework.dll! System.Windows.EventSetters.Event dos veces: la primera vez al valor correcto (ButtonBase.Click RoutedEvent) pero la segunda vez a nulo, y esto arroja una excepción. Mi devolución de llamada de propiedad adjunta no está involucrada.

¿Por qué intenta agregar el evento al EventSetter dos veces y por qué es nulo la segunda vez? Verifiqué que el ctor que se usa es el predeterminado, por lo que EventSeetter no está interactuando con la colección de ninguna manera inusual, así que no es así. La razón real es un error en wpf que supera el desafío de analizar la estructura de dos partes de un evento (Event y EventHandler).

Ver
<Window x:Class="Spec.Plain.MTCMinimal"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:ContentToggleButton;assembly=ContentToggleButton"
        Title="MTCMinimal" Height="300" Width="300">

<Window.Resources>

    <SetterBaseCollection x:Key="ButtonStyleSetters">
        <Setter Property="FrameworkElement.Height" Value="30"></Setter>
        <EventSetter Event="ButtonBase.Click" Handler="StyleClick" />
    </SetterBaseCollection>

</Window.Resources>

<Button Name="Button1"
        local:Behaviours.StyleSetters="{StaticResource ButtonStyleSetters}" />

El código detrás es solo InitializeComponent y un apéndice para el controlador de eventos. El error ocurre durante InitializeComponent.

Comportamiento
public static readonly DependencyProperty StyleSettersProperty =
    DependencyProperty.RegisterAttached(
        "StyleSetters", typeof(MyStyleSetters),
        typeof(Behaviours),
        new PropertyMetadata(default(MyStyleSetters),
            ButtonSettersChanged));

private static void ButtonSettersChanged (DependencyObject d,
    DependencyPropertyChangedEventArgs args)
{
    var fe = d as FrameworkElement;
    if (fe == null) return;
    var ui = d as UIElement;

    var newValue = args.NewValue as MyStyleSetters;
    if (newValue != null)
    {
        foreach (var member in newValue)
        {
            var setter = member as Setter;
            if(setter != null)
            {
                fe.SetValue(setter.Property, setter.Value);
                continue;
            }
            var eventSetter = member as EventSetter;
            if (eventSetter == null) continue;
            if (ui == null || eventSetter.Event == null) continue;
            ui.AddHandler(eventSetter.Event, eventSetter.Handler);
        }
    }
}

public static void SetStyleSetters(DependencyObject element,
    MyStyleSetters value)
{
    element.SetValue(StyleSettersProperty, value);
}

public static MyStyleSetters GetStyleSetters (
    DependencyObject element)
{
    return (MyStyleSetters)element
        .GetValue(StyleSettersProperty);
}
Error
System.Windows.Markup.XamlParseException occurred
  _HResult=-2146233087
  _message='Set property 'System.Windows.EventSetter.Event' threw an exception.' Line number '11' and line position '26'.
  HResult=-2146233087
  IsTransient=false
  Message='Set property 'System.Windows.EventSetter.Event' threw an exception.' Line number '11' and line position '26'.
  Source=PresentationFramework
  LineNumber=11
  LinePosition=26
  StackTrace:
       at System.Windows.Markup.XamlReader.RewrapException(Exception e, IXamlLineInfo lineInfo, Uri baseUri)
  InnerException: System.ArgumentNullException
       _HResult=-2147467261
       _message=Value cannot be null.
       HResult=-2147467261
       IsTransient=false
       Message=Value cannot be null.
Parameter name: value
       Source=PresentationFramework
       ParamName=value
       StackTrace:
            at System.Windows.EventSetter.set_Event(RoutedEvent value)
       InnerException
Depuración

Establecí un punto de interrupción de función en System.Windows.EventSetter.Event con una acción para registrar el valor pasado al setter ...

Luego ejecuto la aplicación y compruebo la ventana de salida y puedo ver que el setter fue golpeado dos veces, la primera vez con el valor correcto, la segunda vez con un valor nulo ...

El ejemplo de trabajo se puede encontrar en la solución en esteGITHub Repo en el proyecto llamado EventSetterNull-SO-41604891-2670182

baml

Al establecer un BP en el miembro Index de XamlNodeList, pude captar los símbolos xaml asociados con el objeto SetterBaseCollection xaml ...

XamlNode [0] "None: LineInfo: System.Xaml.LineInfo"
XamlNode [1] "StartObject: SetterBaseCollection"
XamlNode [2] "StartMember: _Items"
XamlNode [3] "None: LineInfo: System.Xaml.LineInfo"
XamlNode [4] "StartObject: Setter"
XamlNode [5] "StartMember: Property"
XamlNode [6] "Value: Height"
XamlNode [7] "EndMember: "
XamlNode [8] "StartMember: Value"
XamlNode [9] "Value: 30"
XamlNode [10] "EndMember: "
XamlNode [11] "None: LineInfo: System.Xaml.LineInfo"
XamlNode [12] "EndObject: "
XamlNode [13] "None: LineInfo: System.Xaml.LineInfo"
XamlNode [14] "None: LineInfo: System.Xaml.LineInfo"
XamlNode [15] "StartObject: EventSetter"
XamlNode [16] "StartMember: Event"
XamlNode [17] "Value: System.Windows.Baml2006.TypeConverterMarkupExtension"
XamlNode [18] "EndMember: "
               -->EventSetter value: {System.Windows.RoutedEvent}
XamlNode [19] "None: LineInfo: System.Xaml.LineInfo"
XamlNode [20] "StartMember: Event"
XamlNode [21] {System.NullReferenceException: Object reference not set to an instance of an object.
   at System.Xaml.XamlNode.ToString() in ...\AppData\Local\JetBrains\Shared\v06\DecompilerCache\...\XamlNode.cs:line 159
   at <>x.<>m0(XamlNode& <>4__this)}
XamlNode [22] "EndMember: "
               -->EventSetter value: null
               !!!Then the null reference error throws
XamlNode [23] = "StartMember: Handler"
XamlNode [24] = "Value: StyleClick"
XamlNode [25] = "EndMember: "
XamlNode [26] = "None: LineInfo: System.Xaml.LineInfo"
XamlNode [27] = "EndObject: "
XamlNode [28] = "EndMember: "
XamlNode [29] = "EndObject: "
XamlNode [30] = "None: "
                 The remaining of the 41 nodes are all "None: "
¿El bicho?

La lista de nodos de baml parece extraña, en primer lugar, hay un miembro de evento adicional que comienza en idx [20] y este miembro es en realidad una excepción System.NullReferenceException.
Esto se pasa al XamlObjectWriter que a su vez se pasa a la propiedad EventSetter y esta es la causa del error.
El baml continúa como se esperaba, mostrando el miembro manejador y terminando adecuadamente los miembros y objetos.

Conclusión

El problema está en la conversión de xaml a baml, así que diría que es un error. Aunque sea un caso de borde evitable.

Work-around

En lugar de intentar establecer el evento en el estilo, use una propiedad adjunta en un objeto primario. Por ejemplo, ButtonBase.Click = "StyleClick" en un StackPanel entregará el comportamiento a todo clic que es lo que originalmente estaba tratando de hacer. Las colecciones de establecedores de propiedades aún pueden establecerse en un recurso estático y consumirse mediante comportamientos adjuntos basados en propiedades.

Más investigación sobre la causa raíz

El problema es que una propiedad de evento tiene dos elementos: el evento y el controlador. Cuando elBaml2006Reader analiza un objeto en el baml, necesita permitir que su estructura se asegure de que está en el estado correcto para interpretar fielmente los miembros del objeto. Para hacer esto, tiene una máquina de estado, impulsada desde un bucle whileReadObject, llamadoProcess_OneBamlRecord. Este método decodifica el próximo xamlNodeType y llama al método apropiado para analizarlo y escribirlo como un objeto. Uno de estos métodos se llamaProceso_Propiedad y tiene una lógica especial integrada para manejar el complejo de eventos en el baml.

El problema es que, si el evento se registra en el baml como unProcess_PropertyWithConverter, este método no conoce los requisitos especiales para un evento y lo rellena todo. El controlador de eventos tiene como prefijo una etiqueta de propiedad (lo más probable es que el analizador de eventos se repita y use la misma sintaxis para esta subestructura) y debido a que no ha habido un cambio de estado EndMember, StartMember, la subpropiedad del controlador es interpretada por ReadObject como una propiedad de evento. Y el objeto de establecimiento de eventos que se está creando arroja un error porque su propiedad Evento ya está establecida.

Respuestas a la pregunta(0)

Su respuesta a la pregunta