WPF - Cách Làm Hoạt Họa Trong WPF (Storyboard/Animation in WPF)


Như các bạn đã biết, WPF có khá nhiều điểm mạnh và mới so với Winform trước kia, trong đó có Binding, 3D, XAML... và hôm nay chúng ta tìm hiểu về Storyboard - Nó được sử dụng để tạo ra các kịch bản hoạt họa/hoạt hình cho giao diện WPF. Để một đối tượng hình học(Sharp) có thể di chuyển trong một Panel thường chúng ta phải viết khá nhiều dòng code-behind nhưng với WPF chúng ta không nhất thiết phải làm như thế; sẽ có công cụ cho chúng ta tạo kịch bản.





I> Storyboard là gì


Trước tiên để hình dung được công dụng của Storyboard tôi sẽ có ví dụ sau đây. Tôi sẽ tạo một cửa sổ có chứa một hình Chữ nhật(với Radius khá lớn để biến thành hình tròn - chỉ để đẹp thôi), hình chữ nhật này sẽ thay đổi kích thước và màu nền khi sự kiện Rectangle.MouseEnter sảy ra - Chú ý rằng Storyboard hỗ trợ cho tất cả các loại Trigger trong code XAML, tham khảo thêm bản sau:














































Storyboard is begun using…Per-instanceStyleControl templateData templateExample
BeginStoryboard
and an EventTrigger
YesYesYesYesHow to
BeginStoryboard
and a property Trigger
NoYesYesYesHow to
BeginStoryboard
and a DataTrigger
NoYesYesYesHow to
Begin
method
YesNoNoNoHow to

Có thể thực hiện ý tưởng trên bằng hai cách tương đương:



=> Code XAML



[code language="xml"]
<Window x:Class="Storyboard.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Rectangle Name="MyRectangle"
Width="200"
Height="200" RadiusY="100" RadiusX="100">
<Rectangle.Fill>
<SolidColorBrush x:Name="MySolidColorBrush" Color="Blue" />
</Rectangle.Fill>
<Rectangle.Triggers>
<EventTrigger RoutedEvent="Rectangle.MouseEnter">
<BeginStoryboard>
<Storyboard AutoReverse="True">
<DoubleAnimation
Storyboard.TargetName="MyRectangle"
Storyboard.TargetProperty="Width"
From="200" To="300" Duration="0:0:1" />

<ColorAnimation
Storyboard.TargetName="MySolidColorBrush"
Storyboard.TargetProperty="Color"
From="Blue" To="Red" Duration="0:0:1" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Rectangle.Triggers>
</Rectangle>
</Grid>
</Window>
[/code]

=> Code Behind



[code language="csharp"]
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using Storyboarddf = System.Windows.Media.Animation;
using System.Windows.Data;
using System.Windows.Shapes;
using System.Windows.Input;
using System.Windows.Media.Animation;

namespace Storyboard
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();

StoryboardsExample();
}

public void StoryboardsExample()
{
StackPanel myStackPanel = new StackPanel();
myStackPanel.Margin = new Thickness(20);

Rectangle myRectangle = new Rectangle();
myRectangle.Name = "MyRectangle";

// Create a name scope for the page.
NameScope.SetNameScope(this, new NameScope());

this.RegisterName(myRectangle.Name, myRectangle);
myRectangle.Width = 200;
myRectangle.Height = 200;
myRectangle.RadiusX = 100;
myRectangle.RadiusY = 100;
SolidColorBrush mySolidColorBrush = new SolidColorBrush(Colors.Blue);
this.RegisterName("MySolidColorBrush", mySolidColorBrush);
myRectangle.Fill = mySolidColorBrush;

DoubleAnimation myDoubleAnimation = new DoubleAnimation();
myDoubleAnimation.From = 200;
myDoubleAnimation.To = 300;
myDoubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(1));
Storyboarddf.Storyboard.SetTargetName(myDoubleAnimation, myRectangle.Name);
Storyboarddf.Storyboard.SetTargetProperty(myDoubleAnimation,
new PropertyPath(Rectangle.WidthProperty));

ColorAnimation myColorAnimation = new ColorAnimation();
myColorAnimation.From = Colors.Blue;
myColorAnimation.To = Colors.Red;
myColorAnimation.Duration = new Duration(TimeSpan.FromSeconds(1));
Storyboarddf.Storyboard.SetTargetName(myColorAnimation, "MySolidColorBrush");
Storyboarddf.Storyboard.SetTargetProperty(myColorAnimation,
new PropertyPath(SolidColorBrush.ColorProperty));
Storyboarddf.Storyboard myStoryboard = new Storyboarddf.Storyboard();
myStoryboard.AutoReverse = true;
myStoryboard.Children.Add(myDoubleAnimation);
myStoryboard.Children.Add(myColorAnimation);

myRectangle.MouseEnter += delegate(object sender, MouseEventArgs e)
{
Begin(this);
};

myStackPanel.Children.Add(myRectangle);
this.Content = myStackPanel;
}
}
}
[/code]

Storyboard được sử dụng nhiều trong việc tạo ra các giao diện thay đổi trạng thái theo cách người dùng tương tác cũng như thực hiện các chuyển động với kịch bản không quá phức tạp và không cần nhiều sự linh hoạt; Storyboard có hai property ta cần quan tâm nhất đó là AutoReverse=["True"] và RepeatBehavior=["Forever"]




  • AutoReverse: Sau khi thực hiện xong kịch bản, đối tượng sẽ trở lại trạng thái cũng như vị trí ban đầu - thực hiện ngược kịch bản trước

  • RepeatBehavior: Sau khi thực hiện xong kịch bản, đối tượng tiếp tục thực hiện lại nó cho đến khi được gọi Pause hoặc Stop trong code-Behind


II> Tạo kịch bản Storyboard với công cụ


Ở một bài trước tôi có giới thiệu về về một công cụ giúp thiết kế giao diện cho WPF(Expression Studio), hôm nay chúng ta sẽ sử dụng nó. Trước tiên chúng ta cần tải về và cài đặt công cụ này tại đây, có nhiều tùy chọn trong bộ công cụ nhưng chúng ta chỉ cần Blend là đủ/cũng có thể cài full nếu cần thiết.


Phạm Tuân WPF




[code language="csharp"]
TJ2R3-WHW22-B848T-B78YJ-HHJWJ

6WDDQ-K7D4F-GQGF4-2VYBJ-8K6MB

6WDDQ-K7D4F-GQGF4-2VYBJ-8K6MB

6WDDQ-K7D4F-GQGF4-2VYBJ-8K6MB

YV688-DW39R-JPKH2-6DG4R-HM9JD
[/code]

Sau khi cài đặt xong chúng ta khởi chạy ứng dụng và tạo mới một Project WPF- trình tự như hình sau




[caption id="" align="aligncenter" width="350"] Step 01 - New project[/caption]

[caption id="" align="aligncenter" width="550"] Step 02 - Chọn WPF App[/caption]

[caption id="" align="aligncenter" width="783"] Step 03 - Sau khi tạo xong project, ta thêm CheckBox vào UI[/caption]

[caption id="" align="aligncenter" width="733"] Step 04 - Thêm 1 Eclip và 1 Rectangle[/caption]

[caption id="" align="aligncenter" width="314"] Step 05 - Tiến đăng ký Event-Trigger cho sự kiện IsChecked của CheckBox[/caption]

[caption id="" align="aligncenter" width="440"] Step 06 - Tạo kịch bản (Storyboard)[/caption]

Tại giao diện biên tập Storyboard chúng ta có biểu tượng Recording phía trên-phải sáng lên và thanh TimeLine, thanh Timeline lưu lại trạng thái của từng đối tượng cho các móc thời gian trên thước đo. Khi một thuộc tính nào đó của đối tượng thay đổi tại một cột thời gian kế tiếp thì hệ thống sẽ tự in dấu và lưu giữ bước biên tập hiện tại - thể hiện bằng dấu chấm trắng trên cột Time. Công việc của chúng ta là kéo cột Time màu vàng qua lần lược các cột timer, sau đó thực hiện thay đổi thuộc tính cho đối tượng đích và lập lại đến khi hết kịch bản.




[caption id="" align="aligncenter" width="544"] Step 07 - Giao diện Storyboard như trên[/caption]

[caption id="" align="aligncenter" width="689"] Step 08 - Xây dựn kịch bản[/caption]

Để khi kết thúc kịch bản, các đối tượng trở lại vị trí cũ ta Set property AutoReverse="True"




[caption id="" align="aligncenter" width="449"] Step 09 - Tắt chế độ biên tập và nhấn F5 để chạy ứng dụng[/caption]

Vậy là chúng ta có một kịch bản hoàn chỉnh như sau: Quả bóng tròn lăng sang phải khi CheckBox "Run" được Checked, khi quả bóng lăng đụng khối vuông, khối vuông biến thành tròn và bị văng đi một đoạn. Sau đó ta thấy kịch bản bị tua ngược lại vì thuộc tính AutoReverse="True".


Để Start một Storyboard đã được định nghĩa trong [Resource] ta có hai cách đó là [1]Start trong Triger hoặc [2]Start trong Code-behind, Cách một được dùng như tôi hướng dẫn ở trên, cách hai ta làm như sau


Định ra một Storyboard




[code language="xml"]
<Window x:Class="StackOverflow.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:StackOverflow"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Storyboard x:Key="PlayAnimation" Storyboard.TargetProperty="(Canvas.Left)">
<DoubleAnimation From="0" To="100" Duration="0:0:1"/>
</Storyboard>
</Window.Resources>

<Canvas>
<Button x:Name="btn">Test</Button>
</Canvas>
</Window>
[/code]

Tìm và khởi động storyboard trên như sau đây




[code language="csharp"]
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(MainWindow_Loaded);
}

void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
Storyboard sb = this.FindResource("PlayAnimation") as Storyboard;
//Không cần dòng dưới nếu trong XAML đã thể hiện điều này - Target
Storyboard.SetTarget(sb, this.btn);
// End Find

//Start
sb.Begin();
}
}
[/code]

Chúc các bạn vui vẻ!
Phạm Tuân


Chúc các bạn thành công!
PHẠM TUÂN