发生事件时在 DataTemplate 中运行 Storyboard

Posted

技术标签:

【中文标题】发生事件时在 DataTemplate 中运行 Storyboard【英文标题】:Run Storyboard inside DataTemplate when an event occurs 【发布时间】:2021-08-23 18:06:23 【问题描述】:

我有一个 ToggleButton 和一个 ItemsControl。 ItemsControl 的项目有一个DataTemplate。当 ToggleButton 的检查状态发生变化时,如何为 ItemsControl 的每个项目设置动画?

代码

在下面的代码中,您可以看到当用户单击 ToggleButton 时,V 形会旋转。我希望 ItemsControl 中的项目也可以设置动画。我添加了一个带有“ItemAnimation”键的情节提要。我认为当用户单击按钮时应该以某种方式触发它。

<Window x:Class="WinClient.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WinClient"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <DataTemplate x:Key="ShortcutButton">
            <DataTemplate.Resources>
                <Storyboard x:Key="ItemAnimation" AutoReverse="False">
                    <DoubleAnimation Storyboard.TargetName="Trans" Storyboard.TargetProperty="Y" Duration="0:0:0.25" From="-100" To="0" EasingFunction="StaticResource AnimationEase" />
                </Storyboard>
            </DataTemplate.Resources>
            <Border Width="100" Height="100" Background="White" CornerRadius="50" x:Name="ContainerBorder">
                <Border.RenderTransform>
                    <TranslateTransform x:Name="Trans"/>
                </Border.RenderTransform>
                <TextBlock Text="Binding Name" FontSize="60" HorizontalAlignment="Center" VerticalAlignment="Center"/>
            </Border>
        </DataTemplate>
    </Window.Resources>
    <StackPanel VerticalAlignment="Top" Margin="50">
        <Border Width="150" Height="150" CornerRadius="75" Background="White" BorderBrush="#FFC12121">
            <Grid>
                <Image Source="Resources/logo.png" />
                <ToggleButton Width="40" Height="40" VerticalAlignment="Bottom" IsChecked="True" Command="Binding ClickCommand">
                    <Image x:Name="chevron" Source="Resources/chevron-down.png" Width="32" Height="32">
                        <Image.RenderTransform>
                            <RotateTransform CenterX="16" CenterY="16"/>
                        </Image.RenderTransform>
                    </Image>
                    <ToggleButton.Triggers>
                        <EventTrigger RoutedEvent="ToggleButton.Unchecked">
                            <BeginStoryboard>
                                <Storyboard>
                                    <DoubleAnimation Storyboard.TargetName="chevron" Storyboard.TargetProperty="(UIElement.RenderTransform).(RotateTransform.Angle)" From="0" To="180" Duration="0:0:0.3">
                                        <DoubleAnimation.EasingFunction>
                                            <PowerEase EasingMode="EaseIn" />
                                        </DoubleAnimation.EasingFunction>
                                    </DoubleAnimation>
                                </Storyboard>
                            </BeginStoryboard>
                        </EventTrigger>
                        <EventTrigger RoutedEvent="ToggleButton.Checked">
                            <BeginStoryboard>
                                <Storyboard>
                                    <DoubleAnimation Storyboard.TargetName="chevron" Storyboard.TargetProperty="(UIElement.RenderTransform).(RotateTransform.Angle)" From="180" To="0" Duration="0:0:0.3">
                                        <DoubleAnimation.EasingFunction>
                                            <PowerEase EasingMode="EaseIn" />
                                        </DoubleAnimation.EasingFunction>
                                    </DoubleAnimation>
                                </Storyboard>
                            </BeginStoryboard>
                        </EventTrigger>
                    </ToggleButton.Triggers>
                </ToggleButton>
            </Grid>
        </Border>
        <ItemsControl ItemsSource="Binding Shortcuts"
                      RenderTransform="Binding Transform"
                      ItemTemplate="StaticResource ShortcutButton"/>
    </StackPanel>
</Window>

更新

我希望图标从徽标后面移动到它们的位置。这意味着第一个将移动 100 像素,第二个 200,第三个 300,依此类推。 这是我想要的图片:

【问题讨论】:

在问题中添加细节后,它变得更加清晰。让我们也澄清一下:在初始状态下,所有元素都隐藏在按钮下,并且位于另一个之上。当您单击时,它们中的每一个都会“移动”到一个单独的位置。如何计算这个位置?我们需要某种通过元素索引确定结束位置的转换器。但是如何确定索引?元素实现中是否有属性及其索引? 第二次点击时列表是否应该再次折叠?列表中的项目数不是恒定的吗?对最小、最大数量有限制吗? 根据您的 XAML,我根本看不到 ItemsControl 的任何样式。即使忽略动画,您将如何实现其元素之间的“混合”? @EldHasp 是的,您的假设是绝对正确的。位置将是每个项目的高度(比如 100)加上几个像素作为边距(比如 5)。所以第一项移动到按钮下方 5 像素,第二项移动到下方 110 像素,依此类推。项目的数量不是预先确定的,所以他们应该适应。我在想可能是 ViewModel 上的一个值,比如项目上的索引? @EldHasp 也是,关闭切换时项目应该折叠。与 Bootstrap 的折叠控件基本相同的功能,但动画不同。样式只是每个元素的一个圆圈。 【参考方案1】:

您提供了不完整的代码:没有 AnimationEase 资源,没有 DataContext (ViewModel) 代码。 因此,实现示例并不准确。 该示例使用备用属性 Tag。 将初始值写入其中,然后对其进行动画处理。 ItemsControl 项的转换绑定到此属性。 因此,它们也都是同步动画的。

<Window x:Class="WinClient.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WinClient" xmlns:specialized="clr-namespace:System.Collections.Specialized;assembly=System" xmlns:sys="clr-namespace:System;assembly=mscorlib"
        mc:Ignorable="d"
        Title="MainWindow" Height="650" Width="800">
    <Window.Resources>
        <specialized:StringCollection x:Key="source">
            <sys:String>First</sys:String>
            <sys:String>Second</sys:String>
        </specialized:StringCollection>
        <DataTemplate x:Key="ShortcutButton">
            <Border Width="100" Height="100" Background="White" CornerRadius="50" x:Name="ContainerBorder">
                <Border.RenderTransform>
                    <TranslateTransform Y="Binding Tag, ElementName=itemsControl" />
                </Border.RenderTransform>
                <TextBlock Text="Binding" FontSize="60" HorizontalAlignment="Center" VerticalAlignment="Center"/>
            </Border>
        </DataTemplate>
    </Window.Resources>
    <StackPanel VerticalAlignment="Top" Margin="50">
        <Border Width="150" Height="150" CornerRadius="75" Background="White" BorderBrush="#FFC12121">
            <Grid>
                <Image Source="Resources/logo.png"/>
                <ToggleButton Width="40" Height="40" VerticalAlignment="Bottom" IsChecked="True" >
                    <Image x:Name="chevron" Source="Resources/chevron-down.png" Width="32" Height="32">
                        <Image.RenderTransform>
                            <RotateTransform CenterX="16" CenterY="16"/>
                        </Image.RenderTransform>
                    </Image>
                    <ToggleButton.Triggers>
                        <EventTrigger RoutedEvent="ToggleButton.Unchecked">
                            <BeginStoryboard>
                                <Storyboard>
                                    <DoubleAnimation Storyboard.TargetName="chevron" Storyboard.TargetProperty="(UIElement.RenderTransform).(RotateTransform.Angle)" From="0" To="180" Duration="0:0:0.3">
                                        <DoubleAnimation.EasingFunction>
                                            <PowerEase EasingMode="EaseIn" />
                                        </DoubleAnimation.EasingFunction>
                                    </DoubleAnimation>
                                    <DoubleAnimation Storyboard.TargetName="itemsControl" Storyboard.TargetProperty="Tag" Duration="0:0:0.25" From="-100" To="0" />
                                </Storyboard>
                            </BeginStoryboard>
                        </EventTrigger>
                        <EventTrigger RoutedEvent="ToggleButton.Checked">
                            <BeginStoryboard>
                                <Storyboard>
                                    <DoubleAnimation Storyboard.TargetName="chevron" Storyboard.TargetProperty="(UIElement.RenderTransform).(RotateTransform.Angle)" From="180" To="0" Duration="0:0:0.3">
                                        <DoubleAnimation.EasingFunction>
                                            <PowerEase EasingMode="EaseIn" />
                                        </DoubleAnimation.EasingFunction>
                                    </DoubleAnimation>
                                </Storyboard>
                            </BeginStoryboard>
                        </EventTrigger>
                    </ToggleButton.Triggers>
                </ToggleButton>
            </Grid>
        </Border>
        <ItemsControl x:Name="itemsControl"
                      ItemsSource="DynamicResource source"
                      ItemTemplate="StaticResource ShortcutButton">
            <ItemsControl.Tag>
                <sys:Double>10</sys:Double>
            </ItemsControl.Tag>
        </ItemsControl>
    </StackPanel>
</Window>

添加以下说明:

问题是当我“取消选中”切换按钮时,圆圈不会回到顶部。另外,我希望图标从徽标后面移动到它们的位置。这意味着第一个将移动 100 像素,第二个 200,第三个 300 等等。

MultiConverter 用于乘法:

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;

namespace WinClient

    public class MultiplicationConverter : IMultiValueConverter
    
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        
            if (values == null || values.Length == 0)
                return DependencyProperty.UnsetValue;

            double product = 1;
            foreach (var value in values)
            
                if (double.TryParse(value.ToString(), out double number))
                    product *= number;
                if (product == 0)
                    break;
            
            return product;
        

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        
            throw new NotImplementedException();
        

        public static MultiplicationConverter Instance  get;  = new MultiplicationConverter();
    

    public class MultiplicationConverterExtension : MarkupExtension
    
        public override object ProvideValue(IServiceProvider serviceProvider)
            => MultiplicationConverter.Instance;
    


XAML 示例:

<Window x:Class="WinClient.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WinClient"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        mc:Ignorable="d"
        Title="MainWindow" Height="650" Width="800">
    <Window.Resources>
        <sys:String x:Key="source">12345</sys:String>
        <DataTemplate x:Key="ShortcutButton">
            <Border Width="100" Height="100" Background="White" CornerRadius="50" x:Name="ContainerBorder">
                <TextBlock Text="Binding" FontSize="60" HorizontalAlignment="Center" VerticalAlignment="Center"/>
            </Border>
        </DataTemplate>
        <sys:Double x:Key="zero">0</sys:Double>
        <ItemsPanelTemplate x:Key="canvasTemplate">
            <Canvas/>
        </ItemsPanelTemplate>
        <Style x:Key="itemContainerStyle" TargetType="ContentPresenter">
            <Setter Property="Canvas.Top">
                <Setter.Value>
                    <MultiBinding Converter="local:MultiplicationConverter">
                        <Binding Path="(ItemsControl.AlternationIndex)" RelativeSource="RelativeSource Self"/>
                        <Binding Path="Tag" ElementName="itemsControl"/>
                    </MultiBinding>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>
    <Grid VerticalAlignment="Top" Margin="50" HorizontalAlignment="Center">
        <Border x:Name="border" Width="150" Height="150" CornerRadius="75" Background="White" BorderBrush="#FFC12121"
                Panel.ZIndex="10">
            <Grid>
                <Image Source="Resources/logo.png"/>
                <ToggleButton Width="40" Height="40" VerticalAlignment="Bottom">
                    <Image x:Name="chevron" Source="Resources/chevron-down.png" Width="32" Height="32">
                        <Image.RenderTransform>
                            <RotateTransform CenterX="16" CenterY="16"/>
                        </Image.RenderTransform>
                    </Image>
                    <ToggleButton.Triggers>
                        <EventTrigger RoutedEvent="ToggleButton.Unchecked">
                            <BeginStoryboard>
                                <Storyboard>
                                    <DoubleAnimation Storyboard.TargetName="chevron" Storyboard.TargetProperty="(UIElement.RenderTransform).(RotateTransform.Angle)" From="0" To="180" Duration="0:0:0.3">
                                        <DoubleAnimation.EasingFunction>
                                            <PowerEase EasingMode="EaseIn" />
                                        </DoubleAnimation.EasingFunction>
                                    </DoubleAnimation>
                                    <DoubleAnimation Storyboard.TargetName="itemsControl" Storyboard.TargetProperty="Tag" Duration="0:0:2" To="0" From="100" />
                                    <DoubleAnimation Storyboard.TargetName="itemsControlXY" Storyboard.TargetProperty="Y" Duration="0:0:2" To="0" From="Binding ActualHeight, ElementName=border" />
                                </Storyboard>
                            </BeginStoryboard>
                        </EventTrigger>
                        <EventTrigger RoutedEvent="ToggleButton.Checked">
                            <BeginStoryboard>
                                <Storyboard>
                                    <DoubleAnimation Storyboard.TargetName="chevron" Storyboard.TargetProperty="(UIElement.RenderTransform).(RotateTransform.Angle)" From="180" To="0" Duration="0:0:0.3">
                                        <DoubleAnimation.EasingFunction>
                                            <PowerEase EasingMode="EaseIn" />
                                        </DoubleAnimation.EasingFunction>
                                    </DoubleAnimation>
                                    <DoubleAnimation Storyboard.TargetName="itemsControl" Storyboard.TargetProperty="Tag" Duration="0:0:2" From="0" To="100" />
                                    <DoubleAnimation Storyboard.TargetName="itemsControlXY" Storyboard.TargetProperty="Y" Duration="0:0:2" From="0" To="Binding ActualHeight, ElementName=border" />
                                </Storyboard>
                            </BeginStoryboard>
                        </EventTrigger>
                    </ToggleButton.Triggers>
                </ToggleButton>
            </Grid>
        </Border>
        <ItemsControl x:Name="itemsControl"
                      AlternationCount="x:Static sys:Int32.MaxValue"
                      ItemsSource="DynamicResource source"
                      ItemTemplate="DynamicResource ShortcutButton"
                      Tag="StaticResource zero"
                      ItemsPanel="DynamicResource canvasTemplate"
                      ItemContainerStyle="DynamicResource itemContainerStyle" RenderTransformOrigin="0.5,0.5">
            <ItemsControl.RenderTransform>
                <TranslateTransform x:Name="itemsControlXY" Y="0"/>
            </ItemsControl.RenderTransform>
        </ItemsControl>
    </Grid>
</Window>

Ответ дополнен в связи с дополнительным вопросом:

我尝试像这样使用标签:Opacity="Binding Path=Tag, ElementName=itemsControl, Converter=StaticResource tagToOpacityConverter" 但问题是,我只获得了标签。这意味着我只得到 0 和 100 而不是两者之间的值。于是控件出现又消失

实现示例(对前面代码的改动):

    <Window.Resources>
        <sys:String x:Key="source">12345</sys:String>
        <sys:Double x:Key="percent">0.01</sys:Double>
        <DataTemplate x:Key="ShortcutButton">
            <Border Width="100" Height="100" Background="AliceBlue" CornerRadius="50" x:Name="ContainerBorder">
                <Border.Opacity>
                    <MultiBinding Converter="local:MultiplicationConverter">
                        <Binding Path="Tag" ElementName="itemsControl"/>
                        <Binding Source="StaticResource percent"/>
                    </MultiBinding>
                </Border.Opacity>
                <TextBlock Text="Binding" FontSize="60" HorizontalAlignment="Center" VerticalAlignment="Center"/>
            </Border>
        </DataTemplate>

【讨论】:

谢谢。我运行了这段代码。与 Funk 的答案相同的问题。问题是当我“取消选中”切换按钮时,圆圈不会回到顶部。另外,请检查更新的问题。谢谢。 阅读我的答案的补充 非常感谢。这正是我想要的。我会在 14 小时内奖励赏金。 一个问题。假设我在ShortcutButton DataTemplate 中添加了一个图像或一个文本块或一个边框。如果我想为它的不透明度设置动画,我应该怎么做?我尝试像这样使用TagOpacity="Binding Path=Tag, ElementName=itemsControl, Converter=StaticResource tagToOpacityConverter",但问题是,我只得到标签的开始和结束值。这意味着我只得到 0 和 100 而不是两者之间的值。所以控件出现又消失。 太棒了。谢谢。

以上是关于发生事件时在 DataTemplate 中运行 Storyboard的主要内容,如果未能解决你的问题,请参考以下文章

Xamarin Forms,在运行时在 CarouselView 中动态添加项目,奇怪的行为

WPF 菜单事件绑定 DataTemplate下button Command事件绑定 DataTemplate遍历实体数据

是否可以在 Silverlight DataTemplate 中绑定事件?

VueJS:在子组件中触发事件时在父组件中运行函数

如何使用 VBA 将事件添加到运行时在 Excel 中创建的控件

当表单中的任何文本框发生更改时运行函数?微软访问