[WPF][C#] 什麼!?動畫也能這樣玩? - 透過C#播放定義在DataTemplate中的動畫

  • 9551
  • 0
  • C#
  • 2013-07-15

今天在公司遇到一個神奇的情況:我們的HIE在ListBox的DataTemplate中定義了一個Storyboard,而負責撰寫程式的同仁在試著播放該動畫的時候,試了好幾種方式,就是沒辦法順利的針對某一個ListBox中的Item播放動畫....

 

今天在公司遇到一個神奇的情況:我們的HIE在ListBox的DataTemplate中定義了一個Storyboard,而負責撰寫程式的同仁在試著播放該動畫的時候,試了好幾種方式,就是沒辦法順利的針對某一個ListBox中的Item播放動畫,於是來跟我討論。雖然我個人的經驗,處理這種類似的需求,還是以Trigger來實作會比較方便,但是身為工程師的我,不認輸不投降的個性又開始在我的心中大喊「一定要試出來!!!!」。經過幾個小時的瘋狂測試,總算被我試出一個方式了!!~這就列出來和大家分享,也做為自己的筆記。

 

先來直接看看透過Blend建立出來的Xaml檔(如果在ListBox的ItemTemplate編輯模式中建立了Storyboard,該Storyboard就會像以下Xaml一樣,被放在DataTemplate.Resources標籤裡):

Page1.xaml
<Page x:Class="Wpf_StoryboardInDataTemplate.Page1"
        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"
        ShowsNavigationUI="False" d:DesignHeight="600" d:DesignWidth="800" Title="Page1">
    <Page.Resources>
        <DataTemplate x:Key="ItemTemplate">
            <DataTemplate.Resources>
                <Storyboard x:Key="Bright">
                    <DoubleAnimationUsingKeyFrames
                            Storyboard.TargetProperty="(Panel.Background).(GradientBrush.GradientStops)[0].(GradientStop.Offset)"
                            Storyboard.TargetName="border">
                        <EasingDoubleKeyFrame KeyTime="0" Value="1" />
                        <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="1" />
                        <EasingDoubleKeyFrame KeyTime="0:0:1" Value="1" />
                    </DoubleAnimationUsingKeyFrames>
                    <DoubleAnimationUsingKeyFrames
                            Storyboard.TargetProperty="(Panel.Background).(GradientBrush.GradientStops)[1].(GradientStop.Offset)"
                            Storyboard.TargetName="border">
                        <EasingDoubleKeyFrame KeyTime="0" Value="0" />
                        <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="0" />
                        <EasingDoubleKeyFrame KeyTime="0:0:1" Value="0" />
                    </DoubleAnimationUsingKeyFrames>
                    <ColorAnimationUsingKeyFrames
                            Storyboard.TargetProperty="(Panel.Background).(GradientBrush.GradientStops)[0].(GradientStop.Color)"
                            Storyboard.TargetName="border">
                        <EasingColorKeyFrame KeyTime="0" Value="Transparent" />
                        <EasingColorKeyFrame KeyTime="0:0:0.5" Value="Transparent" />
                        <EasingColorKeyFrame KeyTime="0:0:1" Value="Transparent" />
                    </ColorAnimationUsingKeyFrames>
                    <ColorAnimationUsingKeyFrames
                            Storyboard.TargetProperty="(Panel.Background).(GradientBrush.GradientStops)[1].(GradientStop.Color)"
                            Storyboard.TargetName="border">
                        <EasingColorKeyFrame KeyTime="0" Value="Transparent" />
                        <EasingColorKeyFrame KeyTime="0:0:0.5" Value="Yellow" />
                        <EasingColorKeyFrame KeyTime="0:0:1" Value="Transparent" />
                    </ColorAnimationUsingKeyFrames>
                    <ColorAnimationUsingKeyFrames
                            Storyboard.TargetProperty="(TextElement.Foreground).(SolidColorBrush.Color)"
                            Storyboard.TargetName="textBlock">
                        <EasingColorKeyFrame KeyTime="0:0:0.5" Value="Red" />
                        <EasingColorKeyFrame KeyTime="0:0:1" Value="Black" />
                    </ColorAnimationUsingKeyFrames>
                    <DoubleAnimationUsingKeyFrames
                            Storyboard.TargetProperty="(Panel.Background).(GradientBrush.GradientStops)[0].(GradientStop.Offset)"
                            Storyboard.TargetName="border">
                        <EasingDoubleKeyFrame KeyTime="0:0:1" Value="0.891" />
                    </DoubleAnimationUsingKeyFrames>
                </Storyboard>
            </DataTemplate.Resources>
            <!-- 一定要給Template的Root一個名字,以用來找到該控制項元件 -->
            <Border x:Name="border">
                <Border.Background>
                    <RadialGradientBrush RadiusY="4" RadiusX=".5">
                        <GradientStop Color="#00FFFF00" Offset="0" />
                        <GradientStop Color="#00FFFF00" Offset="1" />
                    </RadialGradientBrush>
                </Border.Background>
                <StackPanel Height="Auto" Width="Auto">
                    <TextBlock x:Name="textBlock" Text="{Binding Property1}" />
                    <Image Source="{Binding Property2}" HorizontalAlignment="Left" Height="64"
                            Width="64" />
                </StackPanel>
            </Border>
        </DataTemplate>
    </Page.Resources>
    <Border Background="White" BorderBrush="#FF323232" BorderThickness="3" CornerRadius="10"
            DataContext="{Binding Source={StaticResource SampleDataSource}}">
        <Grid Margin="10">
            <Grid.RowDefinitions>
                <RowDefinition Height="40" />
                <RowDefinition Height="0.854*" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <TextBlock TextWrapping="Wrap" Text="Strory In DataTemplate 範例"
                    HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="#FF646464"
                    FontSize="21.333" FontWeight="Bold" />
            <Rectangle Fill="#FF646464" Height="3" VerticalAlignment="Bottom" Margin="0" />
            <ListBox x:Name="lstSampleData" Grid.Row="1"
                    ItemTemplate="{DynamicResource ItemTemplate}" ItemsSource="{Binding Collection}"
                    Margin="0,5,0,0" />
            <Button x:Name="btnBeginStoryboard" Content="播放動畫" HorizontalAlignment="Center"
                    VerticalAlignment="Center" Grid.Row="2" Padding="10,5" FontSize="13.333"
                    Click="btnBeginStoryboard_Click" Margin="0,5,0,0" />
        </Grid>
    </Border>
</Page>

Xaml的部份很合理,也很正常,沒什麼問題,而且這樣寫的話,名為Bright的Storyboard照理說應該可以供所有ListBox中的項目使用,但是真的要透過C#去針對ListBox中的某個項目播放動畫的時候,該怎麼做呢?

透過ItemTemplate的Resource取出Storyboard物件很簡單,但是,要怎麼去設定Storyboard的Target為我們想要的那個項目呢?因為有DataBinding的關係,如果直接去取得ListBox的SelectedItem,只會得到資料的Instance,而不是用來顯示的Control,所以是沒辦法拿來當Target使用的。

所以~我們就得繞個路,設法取得ListBox中透過DataTemplate生成的控制項,才能正常的撥放動畫;這時候,VisualTreeHelper就又派上用場了!!

讓我們來看看CodeBehind的cs檔:

Page1.xaml.cs
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;

namespace Wpf_StoryboardInDataTemplate
{

	public partial class Page1 : Page
	{
		//預先準備好一個用來放置Template中元件的List來存放抓出來的物件
		List<Border> borders;

		public Page1()
		{
			InitializeComponent();

			this.Loaded += new RoutedEventHandler( Page1_Loaded );
		}

		void Page1_Loaded( object sender , RoutedEventArgs e )
		{
			//透過FindVisualChildren抓出在lstSampleData中型別為Border且名稱為border的控制項
			borders = FindVisualChildren<Border>( lstSampleData , "border" );
		}

		private void btnBeginStoryboard_Click( object sender , RoutedEventArgs e )
		{
			//由lstSampleData的ItemTemplate中的Resources取出名為Bright的Storyboard
			Storyboard bright = lstSampleData.ItemTemplate.Resources[ "Bright" ] as Storyboard;

			//透過lstSampleData的SelectedIndex,從borders中取得被選取到的物件,並把它設為Storyboard的目標來播放。
			bright.Begin( borders.ElementAt( lstSampleData.SelectedIndex ) );
		}

		private List<T> FindVisualChildren<T>( DependencyObject depObj , string name = "" ) where T : DependencyObject
		{
			List<T> list = new List<T>();
			if( depObj != null )
			{
				for( int i = 0 ; i < VisualTreeHelper.GetChildrenCount( depObj ) ; i++ )
				{
					DependencyObject child = VisualTreeHelper.GetChild( depObj , i );
					if( child != null && child is T )
					{
						if( string.IsNullOrEmpty( name ) == true || ( child as FrameworkElement ).Name == name )
						{
							list.Add( ( T ) child );
						}
					}

					List<T> childItems = FindVisualChildren<T>( child , name );

					if( childItems != null && childItems.Count() > 0 )
					{
						foreach( var item in childItems )
						{
							list.Add( item );
						}
					}
				}
			}
			return list;
		}

	}
}

這樣做之後,我們就可以開心的抓取DataTemplate中的動畫來玩啦!!

來享受一下最後的成果吧!!

(*若無法正常瀏覽本文WPF版範例,煩請參考[Windows7]使用IE9、FireFox與Chrome瀏覽WPF Browser Application(.XBAP)的方式一文調整瀏覽器設定)

 

最後,一樣附上專案的原始檔,請自行服用: