Раскадровка не меняется при смене стиля в WPF

Я хочу запустить простую анимацию на изображении стрелки (png). Стрелка указывает вниз или вверх, и анимация должна быть волной, проходящей через стрелку в направлении, на которое она указывает.

Я использую элемент управления Image и назначаю ему один из двух стилей. Эти стили определяют изображение для использования и три двойных анимации в раскадровке. Предполагается, что анимация запускается безоговорочно, с момента создания изображения, навсегда. Одним из стилей является стрелка, указывающая вверх с волной, движущейся вверх (Trend_Rising), а другой стиль - это стрелка, указывающая вниз с волной, идущей вниз (Trend_Falling).

Ниже приведено изображение, а стиль находится в отдельном файле, на который ссылается пользовательский элемент управления, в который встроено изображение.

<Image x:Name="TrendImg" Style="{DynamicResource Trend_Falling}" />

Это содержимое файла стилей:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

     <Style x:Key="Trend_Base" TargetType="Image">

        <Setter Property="OpacityMask">
            <Setter.Value>
                <LinearGradientBrush StartPoint="0,1" EndPoint="0,0">
                    <GradientStop Color="Black" />
                    <GradientStop Color="Transparent" />
                    <GradientStop Color="Black" />
                </LinearGradientBrush>
            </Setter.Value>
        </Setter>

    </Style>

    <Style x:Key="Trend_Rising_Base" TargetType="Image" BasedOn="{StaticResource Trend_Base}">

        <Style.Triggers>
            <Trigger Property="IsVisible" Value="True">
                <Trigger.EnterActions>
                    <BeginStoryboard>
                        <Storyboard>
                            <DoubleAnimation Storyboard.TargetProperty="OpacityMask.GradientStops[0].Offset" From="-0.1" To="0.9" Duration="0:0:2" RepeatBehavior="Forever" />
                            <DoubleAnimation Storyboard.TargetProperty="OpacityMask.GradientStops[1].Offset" From="0.0"  To="1.0" Duration="0:0:2" RepeatBehavior="Forever" />
                            <DoubleAnimation Storyboard.TargetProperty="OpacityMask.GradientStops[2].Offset" From="0.1"  To="1.1" Duration="0:0:2" RepeatBehavior="Forever" />
                        </Storyboard>
                    </BeginStoryboard>
                </Trigger.EnterActions>
            </Trigger>
        </Style.Triggers>

    </Style>

    <Style x:Key="Trend_Falling_Base" TargetType="Image" BasedOn="{StaticResource Trend_Base}">

        <Style.Triggers>
            <Trigger Property="IsVisible" Value="True">
                <Trigger.EnterActions>
                    <BeginStoryboard>
                        <Storyboard>
                            <DoubleAnimation Storyboard.TargetProperty="OpacityMask.GradientStops[0].Offset" From="0.9" To="-0.1" Duration="0:0:2" RepeatBehavior="Forever" />
                            <DoubleAnimation Storyboard.TargetProperty="OpacityMask.GradientStops[1].Offset" From="1.0" To="0.0"  Duration="0:0:2" RepeatBehavior="Forever" />
                            <DoubleAnimation Storyboard.TargetProperty="OpacityMask.GradientStops[2].Offset" From="1.1" To="0.1"  Duration="0:0:2" RepeatBehavior="Forever" />
                        </Storyboard>
                    </BeginStoryboard>
                </Trigger.EnterActions>
            </Trigger>
        </Style.Triggers>

    </Style>

    <Style x:Key="Trend_Rising" TargetType="Image" BasedOn="{StaticResource Trend_Rising_Base}">
        <Setter Property="Height" Value="16" />
        <Setter Property="Source" Value="trend_rising.png" />
    </Style>

    <Style x:Key="Trend_Falling" TargetType="Image" BasedOn="{StaticResource Trend_Falling_Base}">
        <Setter Property="Height" Value="16" />
        <Setter Property="Source" Value="trend_falling.png" />
    </Style>

</ResourceDictionary>

Дело в том, что когда я меняю стиль программно, анимация не меняется. Например, если я запускаю приложение (для изображения назначен стиль Trend_Falling), будет отображаться стрелка вниз с волновой анимацией, движущейся вниз, как и должно быть. Но когда я меняю стиль на Trend_Rising во время выполнения, изображение стрелки меняется, как и должно, но анимация остается прежней.

TrendImg.SetResourceReference(Control.StyleProperty, "Trend_Rising")

Что я делаю неправильно? Буду благодарен за любую помощь. Спасибо!

-- РЕДАКТИРОВАТЬ --

Я создал класс ImageWithAnim, который является потомком Image, и добавил к нему логическое свойство зависимости Animate. Затем я добавил триггер к этому свойству вместо IsVisible. True запускает раскадровку, а false должен ее остановить, но это не так... Когда я устанавливаю Animate в false, возникает исключение, говорящее о том, что имя RisingStoryboard не может быть разрешено в пространстве имен System.Windows.Style. Я нашел несколько сообщений в Stackru, согласно которым этот пример должен работать (среди тех, кто утверждал, что это не будет:-)).

Итак... теперь я понятия не имею, как сделать это правильно. Буду благодарен за любую помощь. Спасибо!

Это измененный xaml:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:Test="clr-namespace:StyleChangeTest">

    <Style x:Key="Trend_Base" TargetType="Test:ImageWithAnim">

        <Setter Property="OpacityMask">
            <Setter.Value>
                <LinearGradientBrush StartPoint="0,1" EndPoint="0,0">
                    <GradientStop Color="Black" />
                    <GradientStop Color="Transparent" />
                    <GradientStop Color="Black" />
                </LinearGradientBrush>
            </Setter.Value>
        </Setter>

    </Style>

    <Style x:Key="Trend_Rising_Base" TargetType="Test:ImageWithAnim" BasedOn="{StaticResource Trend_Base}">

        <Style.Triggers>
            <Trigger Property="Animate" Value="True">
                <Trigger.EnterActions>
                    <BeginStoryboard x:Name="RisingStoryboard">
                        <Storyboard>
                            <DoubleAnimation Storyboard.TargetProperty="OpacityMask.GradientStops[0].Offset" From="-0.1" To="0.9" Duration="0:0:2" RepeatBehavior="Forever" />
                            <DoubleAnimation Storyboard.TargetProperty="OpacityMask.GradientStops[1].Offset" From="0.0"  To="1.0" Duration="0:0:2" RepeatBehavior="Forever" />
                            <DoubleAnimation Storyboard.TargetProperty="OpacityMask.GradientStops[2].Offset" From="0.1"  To="1.1" Duration="0:0:2" RepeatBehavior="Forever" />
                        </Storyboard>
                    </BeginStoryboard>
                </Trigger.EnterActions>
            </Trigger>

            <Trigger Property="Animate" Value="False">
                <Trigger.EnterActions>
                    <StopStoryboard BeginStoryboardName="RisingStoryboard" />
                </Trigger.EnterActions>
            </Trigger>
        </Style.Triggers>

    </Style>

    <Style x:Key="Trend_Falling_Base" TargetType="Test:ImageWithAnim" BasedOn="{StaticResource Trend_Base}">

        <Style.Triggers>
            <Trigger Property="Animate" Value="True">
                <Trigger.EnterActions>
                    <BeginStoryboard x:Name="FallingStoryboard">
                        <Storyboard>
                            <DoubleAnimation Storyboard.TargetProperty="OpacityMask.GradientStops[0].Offset" From="0.9" To="-0.1" Duration="0:0:2" RepeatBehavior="Forever" />
                            <DoubleAnimation Storyboard.TargetProperty="OpacityMask.GradientStops[1].Offset" From="1.0" To="0.0"  Duration="0:0:2" RepeatBehavior="Forever" />
                            <DoubleAnimation Storyboard.TargetProperty="OpacityMask.GradientStops[2].Offset" From="1.1" To="0.1"  Duration="0:0:2" RepeatBehavior="Forever" />
                        </Storyboard>
                    </BeginStoryboard>
                </Trigger.EnterActions>
            </Trigger>

            <Trigger Property="Animate" Value="False">
                <Trigger.EnterActions>
                    <StopStoryboard BeginStoryboardName="FallingStoryboard" />
                </Trigger.EnterActions>
            </Trigger>                
        </Style.Triggers>

    </Style>

    <Style x:Key="Trend_Rising" TargetType="Test:ImageWithAnim" BasedOn="{StaticResource Trend_Rising_Base}">
        <Setter Property="Height" Value="16" />
        <Setter Property="Source" Value="trend_rising.png" />
    </Style>

    <Style x:Key="Trend_Falling" TargetType="Test:ImageWithAnim" BasedOn="{StaticResource Trend_Falling_Base}">
        <Setter Property="Height" Value="16" />
        <Setter Property="Source" Value="trend_falling.png" />
    </Style>

</ResourceDictionary>

А вот класс ImageWithAnim:

Public Class ImageWithAnim
    Inherits Image

    Private Shared _animate As DependencyProperty = DependencyProperty.Register("Animate",
                                                                                GetType(Boolean),
                                                                                GetType(ImageWithAnim),
                                                                                New PropertyMetadata(defaultValue:=False))

    Public Shared ReadOnly Property AnimateProperty As DependencyProperty
        Get
            Return _animate
        End Get
    End Property

    Public Property Animate() As Boolean
        Get
            Return CBool(GetValue(_animate))
        End Get
        Set(value As Boolean)
            SetValue(_animate, value)
        End Set
    End Property

End Class

1 ответ

Решение

По стечению обстоятельств я только что нашел ответ на свой вопрос. Имена раскадровок не могут быть решены, потому что они были определены в базовых стилях. Когда я определил их непосредственно в двух интересующих меня стилях, я мог запускать и останавливать их, используя свойство Animate ImageWithAnim, как и ожидалось, без каких-либо исключений.

Итак, при изменении стиля изображения, я должен сделать следующее:

TrendImg.Animate = False
TrendImg.SetResourceReference(ImageWithAnim.StyleProperty, "Trend_Falling")
TrendImg.Animate = True

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

И вот изменился стиль:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:Test="clr-namespace:StyleChangeTest">

    <Style x:Key="Trend_Base" TargetType="Test:ImageWithAnim">

        <Setter Property="OpacityMask">
            <Setter.Value>
                <LinearGradientBrush StartPoint="0,1" EndPoint="0,0">
                    <GradientStop Color="Black" />
                    <GradientStop Color="Transparent" />
                    <GradientStop Color="Black" />
                </LinearGradientBrush>
            </Setter.Value>
        </Setter>

    </Style>

    <Style x:Key="Trend_Rising" TargetType="Test:ImageWithAnim" BasedOn="{StaticResource Trend_Base}">
        <Setter Property="Height" Value="16" />
        <Setter Property="Source" Value="trend_rising.png" />

        <Style.Triggers>
            <Trigger Property="Animate" Value="True">
                <Trigger.EnterActions>
                    <BeginStoryboard x:Name="RisingStoryboard">
                        <Storyboard>
                            <DoubleAnimation Storyboard.TargetProperty="OpacityMask.GradientStops[0].Offset" From="-0.1" To="0.9" Duration="0:0:2" RepeatBehavior="Forever" />
                            <DoubleAnimation Storyboard.TargetProperty="OpacityMask.GradientStops[1].Offset" From="0.0"  To="1.0" Duration="0:0:2" RepeatBehavior="Forever" />
                            <DoubleAnimation Storyboard.TargetProperty="OpacityMask.GradientStops[2].Offset" From="0.1"  To="1.1" Duration="0:0:2" RepeatBehavior="Forever" />
                        </Storyboard>
                    </BeginStoryboard>
                </Trigger.EnterActions>

                <Trigger.ExitActions>
                    <StopStoryboard BeginStoryboardName="RisingStoryboard" />
                </Trigger.ExitActions>
            </Trigger>
        </Style.Triggers>
    </Style>

    <Style x:Key="Trend_Falling" TargetType="Test:ImageWithAnim" BasedOn="{StaticResource Trend_Base}">
        <Setter Property="Height" Value="16" />
        <Setter Property="Source" Value="trend_falling.png" />

        <Style.Triggers>
            <Trigger Property="Animate" Value="True">
                <Trigger.EnterActions>
                    <BeginStoryboard x:Name="FallingStoryboard">
                        <Storyboard>
                            <DoubleAnimation Storyboard.TargetProperty="OpacityMask.GradientStops[0].Offset" From="0.9" To="-0.1" Duration="0:0:2" RepeatBehavior="Forever" />
                            <DoubleAnimation Storyboard.TargetProperty="OpacityMask.GradientStops[1].Offset" From="1.0" To="0.0"  Duration="0:0:2" RepeatBehavior="Forever" />
                            <DoubleAnimation Storyboard.TargetProperty="OpacityMask.GradientStops[2].Offset" From="1.1" To="0.1"  Duration="0:0:2" RepeatBehavior="Forever" />
                        </Storyboard>
                    </BeginStoryboard>
                </Trigger.EnterActions>

                <Trigger.ExitActions>
                    <StopStoryboard BeginStoryboardName="FallingStoryboard" />
                </Trigger.ExitActions>
            </Trigger>
        </Style.Triggers>
    </Style>

</ResourceDictionary>

И объяснить, почему я поместил раскадровки в базовые стили в начале. Дело в том, что в моем исходном приложении у меня есть несколько разных изображений стрелок, и я просто не хотел повторять определения раскадровки. Но похоже, что я должен.

Другие вопросы по тегам