Erro baml do WPF: EventSetter no recurso estático sendo definido duas vezes, segunda vez como nulo
Se eu tentar armazenar uma coleção de objetos SetterBase no xaml, que inclui EventSetter, o carregador xaml gera um erro.
A causa principal é que o carregador xaml tenta definir o PresentationFramework.dll! System.Windows.EventSetters.Event duas vezes: na primeira vez com o valor correto (ButtonBase.Click RoutedEvent), mas na segunda vez como nulo, e isso gera uma exceção. Meu retorno de chamada de propriedade anexado não está envolvido.
Por que ele tenta adicionar o evento ao EventSetter duas vezes e por que é nulo na segunda vez? Eu verifiquei que o ctor que está sendo usado é o padrão, então EventSeetter não está interagindo com a coleção de nenhuma maneira incomum, então não é isso. O motivo real é um bug no wpf que desafia o desafio de analisar a estrutura de duas partes de um evento (Event e EventHandler).
Visão<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}" />
O código por trás é apenas InitializeComponent e um esboço para o manipulador de eventos. O erro ocorre durante InitializeComponent.
Comportamentopublic 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);
}
ErroSystem.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
DepuraçãoDefino um ponto de interrupção da função em System.Windows.EventSetter.Event com uma ação para registrar o valor passado ao setter ...
Então eu executo o aplicativo e verifico a janela de saída e posso ver que o setter foi atingido duas vezes, primeira vez com o valor correto, segunda vez com valor nulo ...
O exemplo de trabalho pode ser encontrado na solução nesteRepositório GITHub no projeto chamado EventSetterNull-SO-41604891-2670182
bamlAo definir um BP no membro Index de XamlNodeList, eu poderia pegar os símbolos xaml associados ao objeto xaml SetterBaseCollection ...
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: "
O inseto?O nodeList do baml parece estranho, antes de tudo, há um membro extra do Event começando em idx [20] e esse membro é realmente um System.NullReferenceException.
Isso está sendo passado para o XamlObjectWriter, que por sua vez é passado para a propriedade EventSetter e essa é a causa do erro.
O baml continua como esperado, mostrando o membro manipulador e finalizando adequadamente os membros e objetos.
O problema está na conversão de xaml para baml, então eu diria que é um bug. Embora seja um caso de borda evitável.
GambiarraEm vez de tentar definir o evento no estilo, use uma propriedade anexada em um objeto pai. Por exemplo, ButtonBase.Click = "StyleClick" em um StackPanel entregará o comportamento a tudo o que é clicável, o que eu estava originalmente tentando fazer. As coleções de Setters de propriedades ainda podem ser definidas em um recurso estático e consumidas por comportamentos baseados em propriedades anexados.
Mais pesquisas sobre a causa raizO problema é que uma propriedade de evento possui dois elementos: o evento e o manipulador. Quando oBaml2006Reader analisa um objeto no baml, ele precisa permitir sua estrutura para garantir que esteja no estado correto para interpretar fielmente os membros do objeto. Para fazer isso, ele possui uma máquina de estado, acionada a partir de um loop whileReadObject, chamadoProcess_OneBamlRecord. Esse método decodifica o próximo xamlNodeType e chama o método apropriado para analisá-lo e gravá-lo como um objeto. Um desses métodos é chamadoProcess_Property e possui uma lógica especial embutida para lidar com o complexo de eventos no baml.
O problema é que, se o evento for registrado no baml como umProcess_PropertyWithConverter, esse método não está ciente dos requisitos especiais de um evento e enche tudo. O manipulador de eventos é prefixado com uma tag de propriedade (provavelmente o analisador de eventos deveria recursar e usar a mesma sintaxe para essa subestrutura) e, como não houve alteração de estado EndMember, StartMember, a subpropriedade do manipulador é interpretada por ReadObject como uma propriedade Event. E o objeto do configurador de eventos que está sendo criado gera um erro porque sua propriedade Event já está configurada.