【问题标题】:WPF Launch animation from code-behind in MVVMWPF 从 MVVM 中的代码隐藏启动动画
【发布时间】:2015-05-13 08:03:15
【问题描述】:

我正在寻找在 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; }
}

主菜单视图:

<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. MainMenuViewcode-behind 中注册。
  3. 从那里启动动画。

到目前为止,我的问题是为动画设置目标控件。为此,我需要在ControlTemplate 中找到名为coloredRectangleRectangle。该怎么做?

下面是我对应上述步骤的代码:

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

第 2 步:注册消息

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

第 3 步:从 index/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();
}

非常感谢您的回答!

【问题讨论】:

  • 绝对应该有一种比您正在尝试的更好(更直接)的方法。例如:在项目视图模型中有一个类似“IsSelected”的属性——并使用DataTrigger 来触发 XAML 中的动画。另一个例子:使用重新模板化的ListBox 而不是 ItemsControl(因为您的控件显然需要项目选择的概念)。
  • 谢谢 McGarnagle,Trigger 似乎是我最好的尝试。正如您所说,我将使用重新模板化的 ListBox。感谢您的建议!

标签: c# wpf mvvm binding wpf-animation


【解决方案1】:

当然,您可以根据第一个使用另一个StoryboardStyle 创建另一个Style...然后您可以将反向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 cannot be used on Style Setter 错误,并且您想要定位您的 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>

【讨论】:

  • 嗨,Sheridan,非常感谢您的回答。我是 WPF 新手,确实我的帖子可能有点长,但您的第二个建议非常接近我试图实现的目标。我现在需要的唯一精度是知道如何在 DataTemplate 中定位我的 colouredRectangle。当我尝试使用 TargetName 属性时,出现 MC4011 错误(TargetName 不能在样式设置器上使用)。有什么建议吗?谢谢!
  • 我将它放在 ControlTemplate.Triggers 中,并将它绑定到我的 VM 属性中。我定义了 TargetName 和 TargetProperty:`
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-03-25
  • 2013-09-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-22
  • 1970-01-01
相关资源
最近更新 更多