WPF Запустите анимацию из кода в MVVM

Я ищу способ запуска кода для анимации на элементе управления, содержащемся в ControlTemplate, В моем приложении у меня есть пользовательские шаблонные кнопки (играющие роль меню), созданные из ObservableCollection:

MainMenuViewModel:

/// <summary>
/// Menu items list
/// </summary>
private ObservableCollection<MenuItem> _items;

....

/// <summary>
/// Menu items list property
/// </summary>
public ObservableCollection<MenuItem> Items
{
    get { return _items; }
    set { _items = value; }
}

MainMenuView:

<UserControl x:Class="OfficeTourismeBrantome.Views.MainMenuView"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         mc:Ignorable="d" 
         d:DesignHeight="800" d:DesignWidth="300">
    <UserControl.Resources>
        <Style x:Key="MenuItemButtonStyle" TargetType="Button">
            <Setter Property="FontSize" Value="60" />
            <Setter Property="FontFamily" Value="Segoe" />
            <Setter Property="FontWeight" Value="UltraLight" />
            <Setter Property="Foreground" Value="#FFEBEDEA" />
            <!--<Setter Property="Height" Value="{Binding MenuLayout.MenuItemSize.Height}" />-->
            <Setter Property="HorizontalContentAlignment" Value="Right" />
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type Button}">
                        <ControlTemplate.Triggers>
                            <EventTrigger RoutedEvent="Button.Click">
                                <EventTrigger.Actions>
                                    <BeginStoryboard>
                                        <Storyboard Name="themeSelectionAnimation">
                                            <DoubleAnimation 
                                                Storyboard.TargetName="coloredRectangle"
                                                Storyboard.TargetProperty="Width"
                                                From="30.0" 
                                                To="250.0" 
                                                Duration="0:0:0.3" />
                                        </Storyboard>
                                    </BeginStoryboard>                                    
                                </EventTrigger.Actions>
                            </EventTrigger>
                        </ControlTemplate.Triggers>
                        <Canvas HorizontalAlignment="Stretch" ClipToBounds="False" >
                            <ContentPresenter Canvas.Left="{Binding MenuLayout.MenuItemLeftMargin}" HorizontalAlignment="Center"                                                  
                                            VerticalAlignment="Center" Canvas.ZIndex="1"/>
                            <TextBlock 
                                Text="{Binding SecondaryText}" 
                                Canvas.Top="50"
                                Canvas.Left="10"
                                FontSize="30"
                                FontWeight="ExtraLight"
                                FontStyle="Italic"
                                Canvas.ZIndex="1"
                                />
                            <Rectangle
                                Canvas.Top="30"
                                Canvas.Left="10"
                                Name="coloredRectangle"
                                Width="30"
                                Height="10"
                                Canvas.ZIndex="0"
                                Fill="{Binding Color}"/>
                        </Canvas>                        
                    </ControlTemplate>                    
                </Setter.Value>
            </Setter>             
        </Style>
        <Storyboard x:Key="themeUnselectionAnimation">
            <DoubleAnimation                 
                Storyboard.TargetProperty="Width"
                From="250.0" 
                To="30.0" 
                Duration="0:0:0.15" />
        </Storyboard>
    </UserControl.Resources>
    <ItemsControl Name="menuButtonContainer" ItemsSource="{Binding Items}" Margin="{Binding MenuLayout.MenuMargin}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel Orientation="Vertical" />
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>        
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Button 
                    Style="{StaticResource ResourceKey=MenuItemButtonStyle}" 
                    Margin="{Binding ElementName=menuButtonContainer, 
                                        Path=DataContext.MenuLayout.MenuItemMargin}"                    
                    Height="{Binding ElementName=menuButtonContainer, 
                                        Path=DataContext.MenuLayout.MenuItemSize.Height}"
                    Content="{Binding Text}"                    
                    Command="{Binding ElementName=menuButtonContainer, 
                                        Path=DataContext.ChangeThemeCommand}"
                    CommandParameter="{Binding Id}"
                    />
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</UserControl>

Как вы можете видеть из приведенного выше кода, я получил анимацию, автоматически запускаемую при нажатии кнопки. Я хочу воспроизвести его, когда нажата другая кнопка в коллекции (выбран другой пункт меню). Анимация для воспроизведения называется themeUnselectionAnimation.

Первый вопрос: есть ли способ сделать это только в XAML? Я не уверен, так как нужно нажать другую кнопку, чтобы вызвать ее.

Ниже то, что я думал:

  1. в Button (это мой пункт меню) командное действие, отправьте сообщение, чтобы позволить подписчикам, что пункт меню изменяется.
  2. Зарегистрируйтесь на это в MainMenuView кода позади.
  3. Запустите анимацию оттуда.

Моя проблема до сих пор состоит в том, чтобы установить целевой элемент управления для анимации. Для этого мне нужно найти Rectangle названный coloredRectangle в ControlTemplate, Как это сделать?

Здесь ниже мой код, соответствующий вышеуказанным шагам:

Шаг 1: отправить сообщение (я использую MVVM Light Framework)

/// <summary>
/// Delegates that handles theme change process and tasks
/// </summary>
/// <param name="themeId">the new active theme</param>
private void ChangeTheme(int themeId)
{
    // Set current active theme as inactive, if one is selected.
    // Exception use because of Single implementation that throw an InvalidOperationException if not item is found
    try
    {
        MenuItem currentTheme = Items.Single(x => x.IsActive == true);

        // Check if this is current theme. If it is, we do nothing.
        if(currentTheme.Id == themeId)
            return;

        // If current theme is set and new theme id is not the same, disable the old one
        currentTheme.IsActive = false;
        // Set new theme as active
        Items.Single(x => x.Id == themeId).IsActive = true;

        // Finally, launch unselection animation

        // Send message and register to it in view code behind
        // Create inner message
        ThemeChangeNotification innerMessage = new ThemeChangeNotification();
        innerMessage.NewThemeId = themeId;
        innerMessage.OldThemeId = currentTheme.Id;

        NotificationMessage<ThemeChangeNotification> message =
            new NotificationMessage<ThemeChangeNotification>(innerMessage, "");
        // Send message
        Messenger.Default.Send(message);                
    }
    catch (InvalidOperationException exception)
    {
        // Set first theme selection as active
        Items.Single(x => x.Id == themeId).IsActive = true;
    }                                    
}

Шаг 2: Зарегистрируйтесь в сообщении

Messenger.Default.Register<NotificationMessage<ThemeChangeNotification>>(this, ChangeThemeAnimation);

Шаг 3: добраться до кнопки из индекса / идентификатора и запустить анимацию (не работает)

/// <summary>
/// Theme change message delegate
/// </summary>
/// <param name="e">The ThemeChangeNotification message</param>
private void ChangeThemeAnimation(NotificationMessage<ThemeChangeNotification> message)
{
    var buttonTheme = menuButtonContainer.ItemContainerGenerator.ContainerFromIndex(message.Content.OldThemeId) as FrameworkElement;
    var rectangle = buttonTheme.FindName("coloredRectangle") as Rectangle;
    Storyboard sb = this.FindResource("themeUnselectionAnimation") as Storyboard;
    Storyboard.SetTarget(sb, rectangle);
    sb.Begin();
}

Большое спасибо за ваши ответы!

1 ответ

Решение

Конечно, вы можете просто создать другой Style на основе первого, который использует другой Storyboard вместо этого... тогда вы можете просто применить обратное Style на что угодно Button (s) что вы хотите начать это Storyboard:

<Style x:Key="ReverseMenuItemButtonStyle" TargetType="Button">
    <Setter Property="FontSize" Value="60" />
    <Setter Property="FontFamily" Value="Segoe" />
    <Setter Property="FontWeight" Value="UltraLight" />
    <Setter Property="Foreground" Value="#FFEBEDEA" />
    <Setter Property="HorizontalContentAlignment" Value="Right" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type Button}">
                <ControlTemplate.Triggers>
                    <EventTrigger RoutedEvent="Button.Click">
                        <EventTrigger.Actions>
                            <BeginStoryboard>
                                <Storyboard Name="themeUnselectionAnimation">
                                    <DoubleAnimation 
                                        Storyboard.TargetName="coloredRectangle"
                                        Storyboard.TargetProperty="Width"
                                        From="30.0" 
                                        To="250.0" 
                                        Duration="0:0:0.3" />
                                </Storyboard>
                            </BeginStoryboard>                                    
                        </EventTrigger.Actions>
                    </EventTrigger>
                </ControlTemplate.Triggers>
                ...
            </ControlTemplate>                    
        </Setter.Value>
    </Setter>             
</Style>

Я должен быть полностью честным... Я не совсем понял ваш вопрос, поэтому, если он не ответил на него, кажется, что вы также хотите знать, как начать Storyboard с точки зрения модели. В этом случае вам просто нужно bool свойство, которое запускает анимацию, когда установлено true в представлении модели. Вы можете сделать это, используя DataTrigger.EnterActions:

<Style>
    <Style.Triggers>
        <DataTrigger Binding="{Binding SomeBooleanPropertyInViewModel}" Value="True">
            <DataTrigger.EnterActions>
                <BeginStoryboard>
                    <Storyboard ... />
                </BeginStoryboard>
            </DataTrigger.EnterActions>
        </DataTrigger>
    </Style.Triggers>
</Style>

ОБНОВЛЕНИЕ >>>

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

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

Обычное исправление этой ошибки - просто переместить Trigger к элементу, на который вы пытаетесь нацелиться. Так что попробуйте это вместо:

<Rectangle Canvas.Top="30" Canvas.Left="10" Name="coloredRectangle" ... >
    <Rectangle.Style>
        <Style>
            <Style.Triggers>
                <DataTrigger Binding="{Binding SomeBooleanProperty}" Value="True">
                    <DataTrigger.EnterActions>
                        <BeginStoryboard>
                            <Storyboard ... />
                        </BeginStoryboard>
                    </DataTrigger.EnterActions>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Rectangle.Style>
</Rectangle>
Другие вопросы по тегам