Определить анимацию и триггеры как ресурс многократного использования?
Есть ли способ определить анимацию где-нибудь в xaml (например, как ресурс) один раз, а затем повторно использовать ее несколько раз? У меня есть много независимых кистей через различные datatemplates, которые независимо должны запускать ту же самую анимацию, основанную на datatrigger. Теперь, поскольку кажется, что анимация должна определять Storyboard.TargetName и Storyboard.TargetProperty. Это в значительной степени побеждает цель повторного использования. Я хотел бы как-то объявить "использовать эту анимационную форму для ресурса, но на этот раз применить ее к другому элементу".
Мне кажется, что это довольно простая, важная и важная просьба, и я удивлен тем, что ее не так прямо, чтобы успеть. Я что-то пропустил?
То же самое относится и к триггерам. Предположим, у меня есть много разных визуальных элементов, которые все представляют один и тот же тип состояния, используя цветные анимации. Например. исчезают до зеленого, когда "активный" исчезает до "красного", когда "ошибка" и т.д. Единственное различие между визуальными эффектами - это их форма/визуальное дерево, то желаемое поведение анимации одинаково, все они имеют элемент где-то в своем визуальном дереве, свойство типа color. Я думаю, что нетрудно представить, насколько утомительно это переопределять одни и те же анимации и набор данных снова и снова. Каждый разработчик ненавидит это. Я отчаянно ищут более легкое решение, для которого не требуется (или, по крайней мере, очень мало) кода С#.
До сих пор я пришел к следующему:
Определите анимации в ресурсе lik this this (повторите это для всех основных состояний, которые есть, например, активируются, активны, неактивны, ошибка):
<ColorAnimationUsingKeyFrames x:Key="deactivatingColorAnimation"
Storyboard.TargetProperty="Material.(MaterialGroup.Children)[0].Brush.(SolidColorBrush.Color)"
FillBehavior="HoldEnd" RepeatBehavior="Forever" AutoReverse="True">
<ColorAnimationUsingKeyFrames.KeyFrames>
<LinearColorKeyFrame KeyTime="00:00:00" Value="Gray"/>
<LinearColorKeyFrame KeyTime="00:00:0.25" Value="Gray"/>
<LinearColorKeyFrame KeyTime="00:00:0.5" Value="Gray" />
<LinearColorKeyFrame KeyTime="00:00:0.75" Value="Gray" />
</ColorAnimationUsingKeyFrames.KeyFrames>
</ColorAnimationUsingKeyFrames>
Использовать его в раскадровке в триггерах (повторите эти дваллионы раз для каждого состояния X, каждый из которых отличается от stateviusal, всегда придумывает новое имя для раскадровки):
<DataTrigger Binding="{Binding SubstrateHolder.State}" Value="Deactivating">
<DataTrigger.EnterActions>
<BeginStoryboard x:Name="someStateVisualDeactivatingStoryboard">
<Storyboard Storyboard.TargetName="someStateVisual">
<StaticResource ResourceKey="deactivatingColorAnimation" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<RemoveStoryboard BeginStoryboardName="someStateVisualDeactivatingStoryboard" />
</DataTrigger.ExitActions>
</DataTrigger>
Вы можете легко представить себе, сколько раздувать XAML, которое я должен многократно копировать и вставлять для всех этих zillion DataTriggers.
Было бы здорово определить все эти триггеры один раз и применить его к различным визуальным состояниям. Как что-то подобное в WPF? Любой совет?
Ответы
Ответ 1
Кажется, что нет никакого хорошего решения XAML для этой общей идеи. Я закончил писать свои собственные свойства, которые определяют все поведение анимации для данного элемента. что-то вроде этого:
<DataTemplate>
<!-- ... -->
<Rectangle Fill="Gray">
<v:AnimationHelper.Animations>
<v:StandardColorStateAnimation TargetColorProperty="(Rectangle.Fill).(SolidColorBrush.Color)" TraggetSateProperty={Binding State} />
</v:AnimationHelper.Animations>
</Rectangle>
<DataTemplate>
Остальное (создание анимации и т.д.) выполняется в коде.
Ответ 2
Не могли бы вы попробовать что-то вроде этого?
- Оберните все текущие шаблоны управления невидимым корневым элементом, например. Border или StackPanel, чей ограничивающий прямоугольник будет охватывать весь элемент управления.
- Создайте шаблон стиля или управления для этого невидимого блока, который содержит все триггеры и анимации.
- Анимируйте анимацию произвольного свойства Color в невидимом поле.
- В визуальных деревьях для всех ваших различных элементов управления свяжите все свойства, которые вы хотите оживить, к свойству "Цвет" в невидимом корневом элементе.
Ответ 3
Я понимаю, что эта проблема немного мертва во время этой публикации, но я нашел решение, для которого требуется очень мало кода.
Вы можете сделать UserControl с настраиваемыми свойствами (прокрутите вниз до примерно 8), который содержит ваш прямоугольник, а также анимацию и состояние триггеры. Этот пользовательский элемент управления будет определять публичное свойство, такое как статус, который приведет к изменению цвета при изменении.
Единственный необходимый код - это создать переменные в коде.
Пользовательский элемент управления может повторно использоваться повторно, не переписывая раскадровки XAML или триггеры данных.
Ответ 4
Самый "способ XAML" для достижения этой цели я могу представить, это создать выделенный MarkupExtension
, который вытащил бы анимацию из словаря ресурсов и установил необходимые свойства. Я предполагаю, что они ограничены подмножеством Storyboard.Target
, Storyboard.TargetName
и Storyboard.TargetProperty
. Хотя для этого требуется некоторый код, это одноразовые усилия, более того, MarkupExtension
предназначены для использования с XAML. Вот простейшая версия:
[MarkupExtensionReturnType(typeof(Timeline))]
public class AnimationResourceExtension : StaticResourceExtension
{
//This property is for convienience so that we
//don't have to remember to set x:Shared="False"
//on all animation resources, but have an option
//to avoid redundant cloning if it is
public bool IsShared { get; set; } = true;
public DependencyObject Target { get; set; }
public string TargetName { get; set; }
public PropertyPath TargetProperty { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (base.ProvideValue(serviceProvider) is Timeline animation)
{
//If the animation is shared we shall clone it
//Checking if it is frozen is optional and we can
//either clone it or throw an exception
//(or simply proceed knowing one will be thrown anyway)
if (IsShared || animation.IsFrozen)
animation = animation.Clone();
Storyboard.SetTarget(animation, Target);
Storyboard.SetTargetName(animation, TargetName);
Storyboard.SetTargetProperty(animation, TargetProperty);
return animation;
}
else
throw new XamlException("The referenced resource is not an animation");
}
}
Использование очень просто:
<FrameworkElement.Resources>
<DoubleAnimation x:Key="MyAnimation" From="0" To="1" Duration="0:0:1" />
</FrameworkElement.Resources>
(...)
<Storyboard>
<utils:AnimationResource ResourceKey="MyAnimation" TargetName="SomeElement" TargetProperty="Opacity" />
</Storyboard>
Быть таким простым, каким может быть это решение, имеет свои ограничения - он не поддерживает ни расширения Binding
, ни DynamicResource
для вышеупомянутых свойств. Это, однако, достижимо, но требует дополнительных усилий. Поддержка Binding
должна быть довольно простой - вопрос правильного использования XamlSetMarkupExtensionAttribute
(плюс некоторый шаблонный код). Поддержка DynamicResource
была бы немного сложнее, и в дополнение к использованию XamlSetMarkupExtensionAttribute
потребовалось бы обернуть IServiceProvider
для возврата адекватного IProvideValueTarget
, но все еще возможно.