определять анимации и триггеры как повторно используемый ресурс?

StackOverflow https://stackoverflow.com/questions/1654355

Вопрос

Есть ли способ определить анимацию где-нибудь в xaml (например.как ресурс) один раз, а затем повторно использовать его несколько раз?У меня есть много независимых кистей для разных datatemplates, которые независимо друг от друга должны запускать один и тот же вид анимации на основе datatrigger.Теперь, поскольку кажется, что анимация должна определять раскадровку.TargetName и Storyboard.TargetProperty.Это в значительной степени сводит на нет цель повторного использования.Я бы как-то хотел объявить "используйте эту анимацию из ресурса, но на этот раз примените ее к другому элементу".

Мне это кажется довольно простой, важной и насущной просьбой, и я удивлен, что ее не так просто выполнить.Я что-то здесь упускаю?

То же самое относится и к триггерам.Предположим, у меня есть много разных визуальных элементов, которые все представляют один и тот же тип состояния с использованием цветовой анимации.Например.становится зеленым, когда "активно", становится "красным", когда "ошибка" и т.д.Единственное различие между визуальными элементами заключается в их форме / визуальном дереве желаемое поведение анимации одинаково, у всех них где-то в их визуальном дереве есть элемент, который имеет свойство типа color .Я думаю, нетрудно представить, насколько утомительно переопределять одни и те же анимации и наборы данных снова и снова.Каждый разработчик ненавидит это.Я отчаянно ищу более простое решение, которое не требует никакого (или, по крайней мере, очень небольшого) кода на c #.

То, что я придумал до сих пор, это:

Определите анимации в ресурсе подобным образом (повторите это для всех существующих базовых состояний, таких как активация, активный, неактивный, ошибка).:

<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 мне приходится многократно копировать и вставлять для всех этих миллионов DataTriggers.

Было бы здорово определить все эти триггеры один раз и применить их к различным визуальным эффектам состояния.Как что-то подобное решается в WPF?Есть какие-нибудь чаевые?

Это было полезно?

Решение 2

Похоже, что для этой общей проблемы не существует какого-либо хорошего решения только на XAML.В итоге я написал свои собственные прикрепленные свойства, которые определяют все параметры анимации для данного элемента.Что- то вроде этого:

<DataTemplate>
   <!-- ...  -->
   <Rectangle Fill="Gray">
     <v:AnimationHelper.Animations>
        <v:StandardColorStateAnimation TargetColorProperty="(Rectangle.Fill).(SolidColorBrush.Color)" TargetStateProperty={Binding State} />
     </v:AnimationHelper.Animations>
   </Rectangle>
<DataTemplate>

Остальное (создание анимации и т.д.) Выполняется в codebehind.

Другие советы

Не могли бы вы попробовать что-то подобное?

  • Оберните все ваши текущие шаблоны элементов управления невидимым корневым элементом, напримерграница или StackPanel, ограничивающая рамка которой будет охватывать весь элемент управления.
  • Создайте стиль или шаблон элемента управления для этого невидимого поля, который содержит все ваши триггеры и анимации.
  • Попросите анимацию анимировать произвольное свойство цвета в невидимом поле.
  • В визуальных деревьях для всех ваших различных элементов управления привяжите все свойства, которые вы хотите анимировать, к свойству Color невидимого корневого элемента.

Я понимаю, что на момент публикации эта проблема немного устарела, но я нашел решение, которое требует очень мало кода.

Вы можете сделать Пользовательский элемент управления с пользовательскими свойствами(Прокрутите вниз примерно до рисунка 8), который содержит ваш прямоугольник, а также анимации и триггеры состояния.Этот пользовательский элемент управления определял бы общедоступное свойство, такое как status, которое вызывало бы изменение цвета при изменении.

Единственное, что требуется для разработки кода, - это создать ваши переменные в коде.

Пользовательский элемент управления позволяет использовать их повторно снова и снова, не переписывая раскадровки XAML или триггеры данных.

Самый "XAML способ" достижения этой цели, о котором я могу думать, состоит в том, чтобы создать выделенный MarkupExtension который извлек бы анимацию из словаря ресурсов и установил необходимые свойства - я предполагаю, что они ограничены подмножеством Storyboard.Target, Storyboard.TargetName и Storyboard.TargetProperty.Хотя для этого требуется некоторая доработка кода, более того, это одноразовое усилие, MarkupExtensions предназначены для использования с 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 реализация, но все еще возможна.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top