今天在公司遇到一個神奇的情況:我們的HIE在ListBox的DataTemplate中定義了一個Storyboard,而負責撰寫程式的同仁在試著播放該動畫的時候,試了好幾種方式,就是沒辦法順利的針對某一個ListBox中的Item播放動畫....
今天在公司遇到一個神奇的情況:我們的HIE在ListBox的DataTemplate中定義了一個Storyboard,而負責撰寫程式的同仁在試著播放該動畫的時候,試了好幾種方式,就是沒辦法順利的針對某一個ListBox中的Item播放動畫,於是來跟我討論。雖然我個人的經驗,處理這種類似的需求,還是以Trigger來實作會比較方便,但是身為工程師的我,不認輸不投降的個性又開始在我的心中大喊「一定要試出來!!!!」。經過幾個小時的瘋狂測試,總算被我試出一個方式了!!~這就列出來和大家分享,也做為自己的筆記。
先來直接看看透過Blend建立出來的Xaml檔(如果在ListBox的ItemTemplate編輯模式中建立了Storyboard,該Storyboard就會像以下Xaml一樣,被放在DataTemplate.Resources標籤裡):
<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檔:
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)的方式一文調整瀏覽器設定)
最後,一樣附上專案的原始檔,請自行服用: