WPF baml bug: EventSetter в статическом ресурсе устанавливается дважды, второй раз - в null

Если я пытаюсь сохранить коллекцию объектов SetterBase в xaml, в том числе и EventSetter, загрузчик xaml выдает ошибку.

Основная причина в том, что загрузчик xaml пытается установить PresentationFramework.dll! System.Windows.EventSetters.Event дважды: первый раз на правильное значение (ButtonBase.Click RoutedEvent), но второй раз на ноль, и это вызывает исключение. Моя обратная связь с прикрепленным свойством не участвует.

Почему он пытается добавить событие в EventSetter дважды и почему во второй раз он нулевой? Я проверил, используется ли ctor по умолчанию, поэтому EventSeetter не взаимодействует с коллекцией каким-либо необычным образом, так что это не так. Фактическая причина - ошибка в wpf, которая устраняет проблему разбора двухкомпонентной структуры события (Event и EventHandler).

Посмотреть
<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}" />

Код позади - только InitializeComponent и заглушка для обработчика событий. Ошибка происходит во время InitializeComponent.

Поведение
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);
}
ошибка
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
отладка

Я установил точку останова функции в System.Windows.EventSetter.Event с помощью действия, чтобы записать значение, переданное установщику ...

Затем я запускаю приложение, проверяю окно вывода и вижу, что установщик получил два удара: первый раз с правильным значением, второй раз со значением null ...

Рабочий пример можно найти в решении в этомGITHub Repo в проекте под названием EventSetterNull-SO-41604891-2670182

BAML

Установив BP в члене Index в XamlNodeList, я мог бы поймать символы xaml, связанные с объектом 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: "
Баг?

Baml nodeList выглядит странно, во-первых, есть дополнительный член Event, начинающийся с idx [20], и этот член на самом деле является System.NullReferenceException.
Это передается XamlObjectWriter, который, в свою очередь, передается свойству EventSetter, и это является причиной ошибки.
Затем baml продолжает работу, как и ожидалось, показывая элемент-обработчик и правильно завершая элементы и объекты.

Заключение

Проблема в преобразовании из xaml в baml, поэтому я бы сказал, что это ошибка. Хотя это крайний случай, которого можно избежать.

Работа вокруг

Вместо того чтобы пытаться установить событие в стиле, используйте вложенное свойство в родительском объекте. Например, ButtonBase.Click = "StyleClick" в StackPanel доставит поведение всем щелчкам, что я и пытался сделать изначально. Коллекции сеттеров свойств по-прежнему можно задавать в статическом ресурсе и использовать в привязанном поведении на основе свойств.

Дальнейшие исследования первопричины

Проблема в том, что свойство события имеет два элемента: событие и обработчик. КогдаBaml2006Reader анализирует объект в baml, он должен учитывать его структуру, чтобы убедиться, что он находится в правильном состоянии для точной интерпретации членов объекта. Для этого у него есть конечный автомат, управляемый из цикла while вReadObject, называетсяProcess_OneBamlRecord, Этот метод декодирует следующий xamlNodeType и вызывает соответствующий метод, чтобы проанализировать его и записать как объект. Один из этих методов называетсяProcess_Property и он имеет специальную логику, встроенную в него для обработки комплекса событий в baml.

Проблема в том, что, если событие записано в BAML какProcess_PropertyWithConverterэтот метод не знает особых требований к событию и запутывает все. Обработчик события имеет префикс с тегом свойства (наиболее вероятно, что анализатор событий предназначался для повторения и использования одного и того же синтаксиса для этой подструктуры), и поскольку не было никакого изменения состояния EndMember, StartMember, подсвойство обработчика интерпретируется как ReadObject как свойство события. И созданный объект установки событий выдает ошибку, потому что его свойство Event уже установлено.

Ответы на вопрос(0)

Ваш ответ на вопрос