简体   繁体   English

WPF从MVVM中的代码隐藏启动动画

[英]WPF Launch animation from code-behind in MVVM

I'm looking for the way to code-behind launch an animation on a control contained in an ControlTemplate . 我正在寻找在ControlTemplate包含的ControlTemplate上进行代码隐藏启动动画的方法。 In my app I have custom templated buttons (playing menu role) created from an ObservableCollection : 在我的应用程序中,我具有从ObservableCollection创建的自定义模板化按钮(播放菜单角色):

MainMenuViewModel : 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 : 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>

As you can see in the code above, I got an animation automatically triggered on button click. 如您在上面的代码中看到的那样,单击按钮时我自动触发了一个动画。 I want to play it reverse when another button in the collection is clicked (another menu entry is selected). 单击集合中的另一个按钮(选择另一个菜单项)时,我想将其反向播放。 The animation to play is the one called themeUnselectionAnimation. 要播放的动画是一种称为themeUnselectionAnimation的动画。

First question : is there a way to do it only in XAML? 第一个问题:仅在XAML中有方法吗? I'm not sure as another button need to be pressed to trigger it. 我不确定是否需要按下另一个按钮来触发它。

Below is what I thought : 以下是我的想法:

  1. In the Button (which is my menu item) command action, send a message to let subscribers that menu item is changing. 在“ Button (这是我的菜单项)命令操作中,发送一条消息以使订阅者该菜单项正在更改。
  2. Register to it in MainMenuView code-behind. 在后面的MainMenuView注册它。
  3. Launch animation from there. 从那里启动动画。

My problem so far is to set the target control for the animation. 到目前为止,我的问题是为动画设置目标控件。 To do that, I need to find the Rectangle named coloredRectangle in ControlTemplate . 为此,我需要在ControlTemplate找到名为coloredRectangleRectangle How to do that ? 怎么做 ?

Here below is my code corresponding to above steps : 以下是与上述步骤对应的代码:

Step 1 : send message (I'm using MVVM Light framework) 步骤1:发送消息(我正在使用MVVM Light框架)

/// <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;
    }                                    
}

Step 2 : Register to message 第2步:注册到邮件

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

Step 3 : reach Button from index/id and launch animation (not working) 步骤3:从索引/ ID到达Button并启动动画(不起作用)

/// <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();
}

Thank you very much for your answers ! 非常感谢您的回答!

Surely, you can just create another Style based on the first one that uses the other Storyboard instead... then you could just apply the reverse Style on whichever Button (s) that you want to start that Storyboard : 当然,您可以基于第一个使用另一个Storyboard Style来创建另一个Style ...然后,您可以将反向Style应用于要启动该Storyboard任何Button

<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>

I've got to be fully honest... I didn't totally understand your question, so if that didn't answer it, it seems as though you also want to know how to start a Storyboard from a view model. 我必须完全诚实...我不完全理解您的问题,因此,如果没有回答,您似乎也想知道如何从视图模型启动Storyboard In this case, you just need a bool property which will start the animation when set to true in the view model. 在这种情况下,您只需要一个bool属性,当在视图模型中将其设置为true ,该属性将启动动画。 You can do that using the DataTrigger.EnterActions : 您可以使用DataTrigger.EnterActions做到这DataTrigger.EnterActions

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

UPDATE >>> 更新>>>

Again... I still don't really know what you're after... I'd suggest working on your question asking skills before posting another one. 再说一次……我仍然不太清楚你在追求什么……我建议在发布另一个问题之前先研究您的提问技巧。 However, this much I could work out: 但是,我可以解决很多问题:

You're getting an TargetName cannot be used on Style Setter error and you want to target your coloredRectangle element. 您将收到无法在Style Setter错误上使用TargetName的信息,并且希望将自己的coloredRectangle元素作为目标。

The usual fix for this error is simply to move your Trigger to the element that you are trying to target. 通常,此错误的解决方法是将“ Trigger移动到您要定位的元素。 So try this instead: 因此,请尝试以下操作:

<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>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM