VisualStateManager не работает как объявлено
следующий вопрос мучает меня уже несколько дней, но я только что смог дистиллировать его до самой простой формы. Рассмотрим следующий код:
<Window x:Class="VSMTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style TargetType="CheckBox">
<Setter Property="Margin" Value="3"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="CheckBox">
<Grid x:Name="Root">
<Grid.Background>
<SolidColorBrush x:Name="brush" Color="White"/>
</Grid.Background>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="CheckStates">
<VisualStateGroup.Transitions>
<VisualTransition To="Checked" GeneratedDuration="00:00:03">
<Storyboard Name="CheckingStoryboard">
<ColorAnimationUsingKeyFrames Storyboard.TargetName="brush" Storyboard.TargetProperty="Color">
<DiscreteColorKeyFrame KeyTime="0" Value="LightGreen"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualTransition>
<VisualTransition To="Unchecked" GeneratedDuration="00:00:03">
<Storyboard Name="UncheckingStoryboard">
<ColorAnimationUsingKeyFrames Storyboard.TargetName="brush" Storyboard.TargetProperty="Color">
<DiscreteColorKeyFrame KeyTime="0" Value="LightSalmon"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualTransition>
</VisualStateGroup.Transitions>
<VisualState Name="Checked">
<Storyboard Name="CheckedStoryboard" Duration="0">
<ColorAnimationUsingKeyFrames Storyboard.TargetName="brush" Storyboard.TargetProperty="Color">
<DiscreteColorKeyFrame KeyTime="0" Value="Green"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="Unchecked">
<Storyboard Name="UncheckedStoryboard" Duration="0">
<ColorAnimationUsingKeyFrames Storyboard.TargetName="brush" Storyboard.TargetProperty="Color">
<DiscreteColorKeyFrame KeyTime="0" Value="Red"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<ContentPresenter/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<StackPanel>
<CheckBox x:Name="cb1">Check Box 1</CheckBox>
<CheckBox x:Name="cb2">Check Box 2</CheckBox>
<CheckBox x:Name="cb3">Check Box 3</CheckBox>
</StackPanel>
</Window>
Это просто re-шаблоны CheckBox
управление так, чтобы его фон зависел от его состояния:
- Проверил = Зеленый
- Unchecked = Красный
- проверка (переход) = светло-зеленый
- снятие флажка (переход) = свет Красный
Итак, когда вы проверяете один из флажков, вы ожидаете, что он станет светло-зеленым на короткий период, а затем станет зеленым. Аналогично, при снятии флажка вы ожидаете, что он станет светло-красным на короткий период, а затем станет красным.
и он обычно делает именно это. но не всегда.
играйте с программой достаточно долго (я могу получить ее примерно за 30 секунд), и вы обнаружите, что анимация перехода иногда превосходит это в визуальном состоянии. То есть флажок будет по-прежнему отображаться светло-зеленым при выборе или светло-красным при невыборе. Вот скриншот, иллюстрирующий, что я имею в виду, сделанный хорошо после 3 секунд перехода настроен на:
когда это происходит, это не потому что элемент управления не удалось успешно перейти в целевое состояние. Оно должно быть в правильном состоянии. Я проверил это, проверив следующее: отладчик (для конкретного случая, задокументированного приведенным выше скриншотом):
var vsgs = VisualStateManager.GetVisualStateGroups(VisualTreeHelper.GetChild(this.cb2, 0) as FrameworkElement);
var vsg = vsgs[0];
// this is correctly reported as "Unselected"
var currentState = vsg.CurrentState.Name;
если я включаю трассировку для анимации, я получаю следующий вывод, когда переход завершается успешно:
System.Windows.Media.Animation Start: 1 : Storyboard has begun; Storyboard='System.Windows.Media.Animation.Storyboard'; Storyboard.HashCode='44177654'; Storyboard.Type='System.Windows.Media.Animation.Storyboard'; StoryboardName='UncheckedStoryboard'; TargetElement='System.Windows.Controls.Grid'; TargetElement.HashCode='41837403'; TargetElement.Type='System.Windows.Controls.Grid'; NameScope='<null>'
System.Windows.Media.Animation Stop: 1 :
System.Windows.Media.Animation Start: 1 : Storyboard has begun; Storyboard='System.Windows.Media.Animation.Storyboard'; Storyboard.HashCode='6148812'; Storyboard.Type='System.Windows.Media.Animation.Storyboard'; StoryboardName='UncheckedStoryboard'; TargetElement='System.Windows.Controls.Grid'; TargetElement.HashCode='8261103'; TargetElement.Type='System.Windows.Controls.Grid'; NameScope='<null>'
System.Windows.Media.Animation Stop: 1 :
System.Windows.Media.Animation Start: 1 : Storyboard has begun; Storyboard='System.Windows.Media.Animation.Storyboard'; Storyboard.HashCode='36205315'; Storyboard.Type='System.Windows.Media.Animation.Storyboard'; StoryboardName='UncheckedStoryboard'; TargetElement='System.Windows.Controls.Grid'; TargetElement.HashCode='18626439'; TargetElement.Type='System.Windows.Controls.Grid'; NameScope='<null>'
System.Windows.Media.Animation Stop: 1 :
System.Windows.Media.Animation Start: 3 : Storyboard has been removed; Storyboard='System.Windows.Media.Animation.Storyboard'; Storyboard.HashCode='44177654'; Storyboard.Type='System.Windows.Media.Animation.Storyboard'; StoryboardName='UncheckedStoryboard'; TargetElement='System.Windows.Controls.Grid'; TargetElement.HashCode='41837403'; TargetElement.Type='System.Windows.Controls.Grid'
System.Windows.Media.Animation Stop: 3 :
System.Windows.Media.Animation Start: 1 : Storyboard has begun; Storyboard='System.Windows.Media.Animation.Storyboard'; Storyboard.HashCode='36893403'; Storyboard.Type='System.Windows.Media.Animation.Storyboard'; StoryboardName='CheckingStoryboard'; TargetElement='System.Windows.Controls.Grid'; TargetElement.HashCode='41837403'; TargetElement.Type='System.Windows.Controls.Grid'; NameScope='<null>'
System.Windows.Media.Animation Stop: 1 :
System.Windows.Media.Animation Start: 1 : Storyboard has begun; Storyboard='System.Windows.Media.Animation.Storyboard'; Storyboard.HashCode='49590434'; Storyboard.Type='System.Windows.Media.Animation.Storyboard'; StoryboardName='<null>'; TargetElement='System.Windows.Controls.Grid'; TargetElement.HashCode='41837403'; TargetElement.Type='System.Windows.Controls.Grid'; NameScope='<null>'
System.Windows.Media.Animation Stop: 1 :
System.Windows.Media.Animation Start: 3 : Storyboard has been removed; Storyboard='System.Windows.Media.Animation.Storyboard'; Storyboard.HashCode='36893403'; Storyboard.Type='System.Windows.Media.Animation.Storyboard'; StoryboardName='CheckingStoryboard'; TargetElement='System.Windows.Controls.Grid'; TargetElement.HashCode='41837403'; TargetElement.Type='System.Windows.Controls.Grid'
System.Windows.Media.Animation Stop: 3 :
System.Windows.Media.Animation Start: 3 : Storyboard has been removed; Storyboard='System.Windows.Media.Animation.Storyboard'; Storyboard.HashCode='49590434'; Storyboard.Type='System.Windows.Media.Animation.Storyboard'; StoryboardName='<null>'; TargetElement='System.Windows.Controls.Grid'; TargetElement.HashCode='41837403'; TargetElement.Type='System.Windows.Controls.Grid'
System.Windows.Media.Animation Stop: 3 :
System.Windows.Media.Animation Start: 1 : Storyboard has begun; Storyboard='System.Windows.Media.Animation.Storyboard'; Storyboard.HashCode='16977025'; Storyboard.Type='System.Windows.Media.Animation.Storyboard'; StoryboardName='CheckedStoryboard'; TargetElement='System.Windows.Controls.Grid'; TargetElement.HashCode='41837403'; TargetElement.Type='System.Windows.Controls.Grid'; NameScope='<null>'
System.Windows.Media.Animation Stop: 1 :
System.Windows.Media.Animation Start: 3 : Storyboard has been removed; Storyboard='System.Windows.Media.Animation.Storyboard'; Storyboard.HashCode='16977025'; Storyboard.Type='System.Windows.Media.Animation.Storyboard'; StoryboardName='CheckedStoryboard'; TargetElement='System.Windows.Controls.Grid'; TargetElement.HashCode='41837403'; TargetElement.Type='System.Windows.Controls.Grid'
System.Windows.Media.Animation Stop: 3 :
System.Windows.Media.Animation Start: 1 : Storyboard has begun; Storyboard='System.Windows.Media.Animation.Storyboard'; Storyboard.HashCode='16977025'; Storyboard.Type='System.Windows.Media.Animation.Storyboard'; StoryboardName='CheckedStoryboard'; TargetElement='System.Windows.Controls.Grid'; TargetElement.HashCode='41837403'; TargetElement.Type='System.Windows.Controls.Grid'; NameScope='<null>'
System.Windows.Media.Animation Stop: 1 :
и я получаю следующий вывод, когда переход не завершается успешно:
System.Windows.Media.Animation Start: 1 : Storyboard has begun; Storyboard='System.Windows.Media.Animation.Storyboard'; Storyboard.HashCode='44177654'; Storyboard.Type='System.Windows.Media.Animation.Storyboard'; StoryboardName='UncheckedStoryboard'; TargetElement='System.Windows.Controls.Grid'; TargetElement.HashCode='41837403'; TargetElement.Type='System.Windows.Controls.Grid'; NameScope='<null>'
System.Windows.Media.Animation Stop: 1 :
System.Windows.Media.Animation Start: 1 : Storyboard has begun; Storyboard='System.Windows.Media.Animation.Storyboard'; Storyboard.HashCode='6148812'; Storyboard.Type='System.Windows.Media.Animation.Storyboard'; StoryboardName='UncheckedStoryboard'; TargetElement='System.Windows.Controls.Grid'; TargetElement.HashCode='8261103'; TargetElement.Type='System.Windows.Controls.Grid'; NameScope='<null>'
System.Windows.Media.Animation Stop: 1 :
System.Windows.Media.Animation Start: 1 : Storyboard has begun; Storyboard='System.Windows.Media.Animation.Storyboard'; Storyboard.HashCode='36205315'; Storyboard.Type='System.Windows.Media.Animation.Storyboard'; StoryboardName='UncheckedStoryboard'; TargetElement='System.Windows.Controls.Grid'; TargetElement.HashCode='18626439'; TargetElement.Type='System.Windows.Controls.Grid'; NameScope='<null>'
System.Windows.Media.Animation Stop: 1 :
System.Windows.Media.Animation Start: 3 : Storyboard has been removed; Storyboard='System.Windows.Media.Animation.Storyboard'; Storyboard.HashCode='44177654'; Storyboard.Type='System.Windows.Media.Animation.Storyboard'; StoryboardName='UncheckedStoryboard'; TargetElement='System.Windows.Controls.Grid'; TargetElement.HashCode='41837403'; TargetElement.Type='System.Windows.Controls.Grid'
System.Windows.Media.Animation Stop: 3 :
System.Windows.Media.Animation Start: 1 : Storyboard has begun; Storyboard='System.Windows.Media.Animation.Storyboard'; Storyboard.HashCode='36893403'; Storyboard.Type='System.Windows.Media.Animation.Storyboard'; StoryboardName='CheckingStoryboard'; TargetElement='System.Windows.Controls.Grid'; TargetElement.HashCode='41837403'; TargetElement.Type='System.Windows.Controls.Grid'; NameScope='<null>'
System.Windows.Media.Animation Stop: 1 :
System.Windows.Media.Animation Start: 1 : Storyboard has begun; Storyboard='System.Windows.Media.Animation.Storyboard'; Storyboard.HashCode='49590434'; Storyboard.Type='System.Windows.Media.Animation.Storyboard'; StoryboardName='<null>'; TargetElement='System.Windows.Controls.Grid'; TargetElement.HashCode='41837403'; TargetElement.Type='System.Windows.Controls.Grid'; NameScope='<null>'
System.Windows.Media.Animation Stop: 1 :
первые 12 строк точно такие же, как при успешном переходе, но последние 10 строк полностью отсутствуют!
Я прочитал вся документация VSM, которую я мог найти, и не смог придумать объяснение этому неустойчивому поведению.
должен ли я предположить, что это ошибка в VSM? Есть ли какое-либо известное объяснение или обходной путь для этой проблемы?
4 ответов
я смог определить и исправить проблему следующим образом:
во-первых, я понизил свой проект repro до .NET 3.5 и схватил исходный код инструментария WPF из страницы CodePlex. Я добавил проект инструментария WPF в свое решение и добавил ссылку на него из проекта Repro.
затем я запустил приложение и убедился, что все еще могу воспроизвести проблему. Конечно, это было легко сделать.
затем я взломал VisualStateManager.cs файл и начал добавлять некоторые диагностики в ключевых местах, которые сказали бы мне, какой код работает, а что нет. Добавив эти диагностики и сравнив выходные данные от хорошего перехода к плохому переходу, я быстро смог определить, что следующий код не был запущен, когда проблема проявилась:
// Hook up generated Storyboard's Completed event handler
dynamicTransition.Completed += delegate
{
if (transition.Storyboard == null ||
transition.ExplicitStoryboardCompleted)
{
if (ShouldRunStateStoryboard(control, element, state, group))
{
group.StartNewThenStopOld(element, state.Storyboard);
}
group.RaiseCurrentStateChanged(element, lastState, state,
control);
}
transition.DynamicStoryboardCompleted = true;
};
таким образом, природа ошибки сместилась от проблемы в VSM к проблеме в Storyboard.Completed
событие не всегда поднятый. Это проблема, с которой я сталкивался раньше, и, похоже, является источником большого беспокойства для любого разработчика WPF, делающего что-то даже немного необычное, когда дело доходит до анимации.
на протяжении всего этого процесса я размещал свои выводы на WPF ученики google group, и именно в этот момент, что Паван Podila ответил этим камнем:
Кент
у меня были проблемы в прошлом для раскадровки не стреляют завершенное событие. Что я понял это если вы замените раскадровку сразу, без сперва останавливать его, вы можете увидеть некоторые из Завершенное событие. В моем случае я был применение новых раскадровок к тому же FrameworkElement, без остановки раньше раскадровка, и это было у меня есть вопросы. Не уверен, если ваш случай похож, но я подумал, что поделись этим лакомым кусочком.
Паван
вооружившись этим проницательность, я изменил эту строку в VisualStateManager.cs:
group.StartNewThenStopOld(element, transition.Storyboard, dynamicTransition);
для этого:
var masterStoryboard = new Storyboard();
if (transition.Storyboard != null)
{
masterStoryboard.Children.Add(transition.Storyboard);
}
masterStoryboard.Children.Add(dynamicTransition);
group.StartNewThenStopOld(element, masterStoryboard);
и-о чудо - мой упрек, который раньше терпел неудачу с перерывами, теперь работал каждый раз!
Итак, действительно, это работает вокруг ошибки или странного поведения в подсистеме анимации WPF.
похоже, что настройка Duration="0"
на проверенных и непроверенных раскадровках был виновник. Удаление его устраняет проблему. Я не уверен, что понимаю, почему, если раскадровка каким-то образом не связана с соответствующим переходом.
тем не менее, я думаю, что нашел для вас более чистое решение. Если вы измените ControlTemplate на это, он выполнит то же самое без переходов...
<ControlTemplate TargetType="CheckBox">
<Grid x:Name="Root">
<Grid.Background>
<SolidColorBrush x:Name="brush" Color="White"/>
</Grid.Background>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="CheckStates">
<VisualState Name="Checked">
<Storyboard x:Name="CheckedStoryboard">
<ColorAnimationUsingKeyFrames Storyboard.TargetName="brush" Storyboard.TargetProperty="Color">
<DiscreteColorKeyFrame KeyTime="0" Value="LightGreen"/>
<DiscreteColorKeyFrame KeyTime="00:00:03" Value="Green"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="Unchecked">
<Storyboard x:Name="UncheckedStoryboard">
<ColorAnimationUsingKeyFrames Storyboard.TargetName="brush" Storyboard.TargetProperty="Color">
<DiscreteColorKeyFrame KeyTime="0" Value="LightSalmon"/>
<DiscreteColorKeyFrame KeyTime="00:00:03" Value="Red"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<ContentPresenter/>
</Grid>
</ControlTemplate>
Не знаю, связано ли это вообще с вашей проблемой, но я также наткнулся на проблемы с AnimationClock.Завершено не надежно срабатывание при замене запущенной анимации на другую. Я решил, что это вопрос сбора мусора и ссылок/укоренения. Когда AnimationClock все еще работает, но больше не ссылается как-то, это может быть мусор, собранный в любой момент времени. Если конец достигнут до того, как произойдет сбор мусора, завершенный запускается, иначе нет. Что приводит к очень непредсказуемому поведению.
мой обходной путь состоит в том, чтобы сначала добавить мои часы в некоторую коллекцию (чтобы заставить ее укорениться и, таким образом, предотвратить сборку мусора) и удалить ее из коллекции по завершении, затем завершенный получает уволен 100% времени, и нет утечек памяти.
просто мои два цента...
эта проблема подняла свою уродливую голову для меня недавно в WPF 4.5. В моем случае, похоже, что мой переход получал мусор, собранный во время активности, поэтому иногда он никогда не запускал завершенное событие и никогда не сбрасывал свою анимацию. Поскольку мое проверенное VisualState в основном вызывало все те же свойства снова, чтобы "исправить" их в конечных точках перехода, казалось, что это состояние частично сработало, но я не верю, что это когда-либо было.
решение: я оставил Свойство GeneratedDuration в моих VisualTransitions (мои переходы выполнялись медленнее, чем должны были быть, поэтому я оставил его, чтобы попытаться ускорить его.). Я думаю, что это свойство работает, чтобы" закрепить " переход за данное время. Когда я добавил свойство обратно в переходы, он исправил мою проблему, и мои анимации будут работать надежно.