Error de WPF baml: EventSetter en el recurso estático se establece dos veces, la segunda vez en nulo
visual-studio-2013 staticresource (0)
Si trato de 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 en el valor correcto (ButtonBase.Click RoutedEvent) pero la segunda vez en anular, y esto arroja una excepción. La devolución de mi propiedad adjunta no está involucrada.
¿Por qué intenta agregar el evento al EventSetter dos veces y por qué es nulo la segunda vez? Comprobé que el controlador que se utiliza es el predeterminado, por lo que EventSeetter no está interactuando con la colección de forma inusual, así que no es así. La razón real es un error en wpf que suaviza 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 stub para el manejador 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 verifico 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 valor nulo ...
El ejemplo de trabajo se puede encontrar en la solución en este GITHub Repo en el proyecto llamado EventSetterNull-SO-41604891-2670182
baml
Al establecer un BP en el miembro de índice de XamlNodeList pude capturar 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?
El baml nodeList se ve extraño, en primer lugar hay un miembro adicional del evento que comienza en idx [20] y este miembro es en realidad una System.NullReferenceException.
Esto se pasa al XamlObjectWriter que a su vez pasa a la propiedad EventSetter y esta es la causa del error.
El baml luego continúa como se esperaba, mostrando el miembro manejador y terminando apropiadamente 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 es un caso de borde evitable.
Work-around
En lugar de intentar establecer el evento en el estilo, use una propiedad adjunta en un objeto principal. Por ejemplo, ButtonBase.Click = "StyleClick" en un StackPanel entregará el comportamiento a todo lo que haga clic, que es lo que originalmente estaba tratando de hacer. Las colecciones de Property Setters aún pueden establecerse en un recurso estático y consumirse mediante comportamientos basados en propiedades adjuntas.
Investigaciones adicionales sobre la causa raíz
El problema es que una propiedad de evento tiene dos elementos: el evento y el controlador. Cuando el Baml2006Reader analiza un objeto en el baml, necesita permitir su estructura para asegurarse de que está en el estado correcto para interpretar fielmente los miembros del objeto. Para hacer esto, tiene una máquina de estados, impulsada desde un ciclo while en ReadObject , llamada Process_OneBamlRecord . Este método decodifica el siguiente xamlNodeType y llama al método apropiado para analizarlo y escribirlo como un objeto. Uno de estos métodos se llama Process_Property y tiene una lógica especial integrada en él para manejar el complejo de eventos en el baml.
El problema es que, si el evento se registra en baml como Process_PropertyWithConverter , este método no conoce los requisitos especiales de un evento y lo almacena todo. El controlador de eventos tiene un prefijo con una etiqueta de propiedad (lo más probable es que el analizador de eventos se haya recurrido y use la misma sintaxis para esta subestructura) y porque no ha habido ningún cambio de estado de EndMember, StartMember, la sub-propiedad del manejador es interpretada por ReadObject como una propiedad de evento. Y el objeto setter de eventos que se está creando arroja un error porque su propiedad Event ya está configurada.