Объект StoryBoard становится доступным только для чтения, если он задан стилем.

У меня есть присоединенное поведение, которое имеет одно присоединенное свойство типа StoryBoard. Я хочу установить это свойство для каждого элемента в ListView. XAML выглядит примерно так:

<Grid>
   <Grid.Resources>
      <Storyboard x:Key="TheAnimation" x:Shared="False">
         <DoubleAnimation From="0.0" To="1.0" Duration="0:0:0.20"
             Storyboard.TargetProperty="Opacity" />
      </Storyboard>
   </Grid.Resources>

   <ListView>
      <ListView.Resources>
         <Style TargetType="{x:Type ListViewItem}">
            <Setter Property="local:MyBehavior.Animation"
               Value="{StaticResource TheAnimation}" />
         </Style>
      </ListView.Resources>
   </ListView>
</Grid>

Все идет нормально. Затем код в «MyBehavior» пытается сделать это:

private static void AnimationChanged(DependencyObject d,
   DependencyPropertyChangedEventArgs e)
{
   var listViewItem = d as ListViewItem;
   if (d == null)
      return;

   var sb = e.NewValue as Storyboard;
   if (sb == null)
      return;

   Storyboard.SetTarget(sb, listViewItem);
   sb.Begin();
}

Но при вызове StoryBoard.SetTarget() выдается InvalidOperationException: «Невозможно установить свойство для объекта« System.Windows.Media.Animation.Storyboard », поскольку он находится в состоянии только для чтения». Если я проверю Storyboard в отладчике, я увижу, что оба его свойства IsSealed и IsFrozen установлены на true.

Напротив, если я устанавливаю MyBehavior.Animation непосредственно на ListView, чтобы мне не нужно было использовать Style, StoryBoard прибывает незапечатанным, и я могу установить цель и успешно запустить ее. Но это не то, чего я хочу.

Почему мой StoryBoard запечатан, и могу ли я что-нибудь сделать, чтобы предотвратить это?

Обновление: я могу решить свою проблему, добавив это сразу после нулевой проверки:

if(sb.IsSealed)
   sb = sb.Clone();

Но мне все равно интересно, что происходит. Видимо что-то где-то (Style? Setter?) замораживает/запечатывает объект в Setter.Value.


person dlf    schedule 18.07.2015    source источник
comment
Это имеет смысл, когда вы стоите в стороне и думаете об этом (я думаю). Узел xaml Style предположительно преобразуется в один объект Style. Этот единственный объект вместе с Value объектами в его Setters потенциально может использоваться совместно несколькими объектами. Поэтому, если один из них каким-то образом перехватит объект Value (в моем случае это PropertyChangedCalledback) и изменит его, это непреднамеренно повлияет на все другие объекты, которые разделяют этот стиль. Разработчики фреймворка предвидели это и запечатали Value, чтобы этого не произошло. Это мое предположение, по крайней мере.   -  person dlf    schedule 18.07.2015
comment
Этот ход мыслей привел меня к этому связанному вопросу   -  person dlf    schedule 18.07.2015
comment
Также относится   -  person dlf    schedule 18.07.2015
comment
непреднамеренно повлияет на все другие объекты, разделяющие этот стиль - именно это я и указал в своем ответе.   -  person Peter Duniho    schedule 18.07.2015


Ответы (2)


Я провел некоторое исследование по этому поводу и думаю, что понял большую часть того, что происходит. Короткий ответ заключается в том, что такое поведение предусмотрено дизайном. Стили и шаблоны страница для .net 4.0 прямо говорит, что «после применения стиля он запечатывается и не может быть изменен». Комментарии с Style.IsSealed подтверждают это. Но это сам Style; Я имею дело с объектом, содержащимся в Style Setter Value. Итак, Style.Seal запечатывает все свои Setters , и Setter.Seal запечатывает его Value. С этой информацией в руках (в голове) ничто из того, что здесь произошло, не особенно шокирует. Но до сих пор нет объяснения, почему вся эта герметизация делается в первую очередь. Есть претензии здесь и здесь, что это связано с безопасностью потоков. Это кажется разумным, но я бы предположил, что если все объекты, потребляющие конкретный Style, совместно используют один объект Style (и я не знаю, так это или нет), запечатывание может быть выполнено по той простой причине, что вы не хочу, чтобы один потребитель модифицировал Style и случайно изменил всех остальных.

Все это, похоже, означает, что общего решения проблемы нет, и ее нужно будет решать в каждом конкретном случае. В моем случае решением было просто клонировать Storyboard, а затем работать с этим клоном.

person dlf    schedule 18.07.2015
comment
Как я уже упоминал, вероятной причиной является обеспечение того, чтобы объекты, предназначенные для совместного использования, не изменялись (т. е. затрагивали многие зависимые объекты). Это может быть проблема с производительностью (слишком много изменений распространяется через систему привязки) или просто проблема с удобством использования API. Это не проблема потоков, поскольку объекты WPF привязаны к определенному потоку и в любом случае не могут использоваться в нескольких потоках. - person Peter Duniho; 18.07.2015

Я далек от эксперта в WPF, поэтому я не могу объяснить более тонкие детали того, почему Microsoft сделала именно такой выбор. Но, насколько я понимаю, основная проблема заключается в том, что объект, объявленный как ресурс, скорее всего, будет использоваться совместно с несколькими другими объектами. Таким образом, вы не можете изменить его.

Если вы все еще хотите пойти по маршруту ресурсов, возможно вы можете обрабатывать ресурс как {DynamicResource...} вместо {StaticResource...}, и это может позволить вам изменить объект, который использовался для какого-то другого объекта. Как я уже сказал, я не эксперт в WPF, и я признаю, что все еще немного туманен в отношении различий между DynamicResource и StaticResource, но я смутно припоминаю, что он относится к этому сценарию. :)

person Peter Duniho    schedule 18.07.2015
comment
«DynamicResource» был одной из вещей, которые я пробовал, но это не помогло. Но посмотрите мой комментарий с исходным вопросом; Я могу понять, что происходит. - person dlf; 18.07.2015