如今的軟件市場,競爭已經進入白熱化階段,功能強、運算快、界面友好、Bug少、價格低都已經成為了必備條件。這還不算完,隨著計算機的多媒體功能越來越強,軟件的界面是否色彩亮麗、是否能通過動畫、3D等效果是否吸引用戶的眼球也已經成為衡量軟件的標准。
軟件項目成功的三個要素是:資源、成本、時間。無論是為了在競爭中保持不敗還是為了激發起用戶對軟件的興趣,提高軟件界面的美化程度、恰當的將動畫和3D等效果引入應用程序都是一個必然趨勢。然而使用傳統的桌面應用程序開發工具和框架(如Winform、MFC、VB、Delphi等)進行開發時,為了使軟件界面變漂亮、加入動畫或者3D效果,邊際成本會非常的高。體現在:
資源消耗增大:需要招聘懂得動畫和3D編程的程序員,還需要更多的設計師、工薪和溝通成本隨著上升。
開發時間增加:界面美化、動畫和3D開發遠遠比業務邏輯開發困難、耗時。
成本增加:隨著資源消耗的增加和開發周期的拉長,成本必然增加。
之所以會出現這種情況,根本原因在於傳統開發工具和框架並沒有原生的支持美化用戶界面、向應用程序中添加動畫和3D效果等功能。舉個簡單的例子,當用戶提出需要把TextBox的外觀改成圓角時,Winform和Delphi程序員只能通過派生新類並在底層做修改的方法來實現。類似的用戶需求還有好多不得不實現,否則客戶會懷疑我們的開發能力;即使實現了也沒有什麼額外的經濟效益,因為這些東西在客戶的眼裡都是很簡單的東西。
WPF的推出可謂是對症下藥、專門解決上述問題。體現在:
XAML語言針對的是界面美化的問題,可以讓設計師直接加入開發團隊、降低溝通成本。
XAML的圖形繪制功能非常強大,可以輕易繪出復雜的圖標、圖畫。
WPF支持濾鏡功能,可以像PhotoShop一樣為對象添加各種效果。
WPF原生支持動畫開發,無論是設計師還是程序員,都能夠使用XAML或C#輕松開發制作絢麗的動畫效果。
WPF原生支持3D效果,甚至可以將其它3D建模工具創建的模型導進來、為我所用。
Blend作為專門的設計工具讓WPF如虎添翼,即能夠幫助不了解編程的設計師快速上手,又能夠幫助資深開發者快速建立圖形或者動畫的原型。
1.1 WPF繪圖
與傳統的.net開發使用GDI+進行繪圖不同,WPF擁有自己的一套繪圖API。使用這套API不但可以輕松繪制出精美的圖形,還可以為各種圖形添加類似與PhotoShop的“濾鏡效果”及“變形效果”。本節我們就一起研究WPF圖形API繪圖,效果和變形等功能。
先觀察下面一組圖片:
顯然,這組圖片是矢量圖(Vector Image),無論怎樣放大縮小都不會出現鋸齒。你可能會想:“這是組PNG格式的圖片嗎?”答案是“NO”。這組圖是用XAML語言繪制的!XAML繪圖本身就是矢量的,而且支持各式各樣的填充和效果,甚至還可以添加濾鏡,這些功能絲毫不亞於Photoshop。以前,使用PhotoShop制作出來的圖形需要程序員使用.net的繪圖接口進行二次轉換才能應用到程序裡,現在好了,直接把XAML代碼拿來用就可以了。
繪圖並不是VisualStudio的強項,這些漂亮的XAML矢量圖是怎麼畫出來的呢?答案是借助Microsoft Expression Studio中的Blend和Design兩個工具。Blend我們已經介紹過了,用它可以直接繪制XAML圖形;Design可以像PhotoShop或者FireWorks那樣繪制圖形,再由設計者決定導出xaml格式還是png格式。雖然“唯代碼派”的程序員們在Visualstudio裡一行一行寫代碼也能把復雜的圖形以非可視化的形式創建出來,但在Blend和Design中畫出原型再在Visual Studio裡面進行細節的修飾才是提高效率之道。
積沙成塔,集腋成裘,別看前面那些圖片很復雜,但都是由幾個有限的基本圖形組成的。WPF的基本圖形包括以下幾個(它們都是Shap類的派生類):
Line:直線段,可以設置其筆觸(Stroke)。
Rectangle:矩形,既有筆觸,又有填充(Fill)。
Ellipse:橢圓,長寬相等的橢圓即為正圓,既有筆觸又有填充。
Polygon:多邊形,由多條直線線段圍成的閉合區域,既有筆觸又有填充。
PolyLine:折線(不閉合),由多條首尾相接的直線組成。
Path:路徑(閉合區域),基本圖形中功能最強的一個,可由若干直線,圓弧,被塞爾曲線組成。
1 直線
直線是最簡單的圖形。使用X1,Y1兩個屬性值可以設置它的起點坐標,X2,Y2兩個屬性值可以設置它的終點坐標。控制終點/起點做標就可以實現平行,交錯等效果。Stroke(筆觸)屬性的數據類型是Brush(畫刷),凡是Brush的派生類均可以用於給這個屬性賦值。因為WPF提供多種漸變色畫刷,所以畫直線也可以畫出漸變效果。同時,Line的一些屬性還可以幫助我們畫出虛線以及控制線段終點的形狀。下面的例子綜合了這些屬性:
[html] view plain copy
- <Window x:Class="WpfApplication1.Window45"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="Window45" Height="293" Width="437">
- <Grid>
- <Line X1="10" Y1="20" X2="260" Y2="20" Stroke="Red" StrokeThickness="10"></Line>
- <Line X1="10" Y1="40" X2="260" Y2="40" Stroke="Orange" StrokeThickness="6"></Line>
- <Line X1="10" Y1="60" X2="260" Y2="60" Stroke="Green" StrokeThickness="3"></Line>
- <Line X1="10" Y1="80" X2="260" Y2="80" Stroke="Purple" StrokeThickness="2"></Line>
- <Line X1="10" Y1="100" X2="260" Y2="100" Stroke="Black" StrokeThickness="1"></Line>
- <Line X1="10" Y1="120" X2="260" Y2="120" StrokeDashArray="3" Stroke="Black" StrokeThickness="1"></Line>
- <Line X1="10" Y1="140" X2="260" Y2="140" StrokeDashArray="5" Stroke="Black" StrokeThickness="1"></Line>
- <Line X1="10" X2="260" Y1="160" Y2="160" Stroke="Black" StrokeThickness="6" StrokeEndLineCap="Flat"></Line>
- <Line X1="10" X2="260" Y1="180" Y2="180" Stroke="Black" StrokeThickness="8" StrokeEndLineCap="Triangle"></Line>
- <Line X1="10" X2="260" Y1="200" Y2="200" StrokeEndLineCap="Round" StrokeThickness="10">
- <Line.Stroke>
- <LinearGradientBrush EndPoint="0,0.5" StartPoint="1,0.5">
- <GradientStop Color="Blue"></GradientStop>
- <GradientStop Offset="1" Color="Red"></GradientStop>
- </LinearGradientBrush>
- </Line.Stroke>
- </Line>
- </Grid>
- </Window>
程序運行效果如下:
有一點需要特別注意,初學者認為繪圖一定要在Canvas中完成(誰叫它的名字叫畫布呢),其實不然,繪圖可以在任何一種布局控件中完成,WPF會自動根據容器的不同計算圖形的坐標,日常生活中,常用的繪圖容器有Canvas和Grid。
2 矩形
矩形有筆觸(Stroke,即邊線)和填充(Fill)構成。Stroke屬性的設置和Line一樣,Fill屬性的數據類型是Brush。Brush是一個抽象類,所以我們不可能拿一個Brush類的實例為Fill屬性賦值而只能用Brush派生類來進行賦值。WPF繪圖系統中包含非常豐富的Brush類型,常用的有:
SolidColorBrush:實心畫刷。在XAML中可以使用顏色名稱字符串直接賦值。
LinearGradientBrush:線性漸變畫刷。色彩沿設定的直線方向,按設定的變化點進行漸變。
RadialGradientBrush:徑向漸變畫刷。色彩沿半徑的方向、按設定的變化點進行漸變,形成圓形填充。
ImageBrsh:使用圖片作為填充類容。
DrawingBrush:使用矢量圖(Vector)和位圖(BitMap)作為填充內容。
VisualBrush:WPF中的每個控件都是有FrameWrokElement派生而來的,而FrameWorkElment類又是由Visual類派生而來的。Visual意為“可視”之意,每個控件的可視化形象就可以通過Visual類的方法獲得。獲得這個可視化形象之後,我們可以用這個形象進行填充,這就是VisualBrush。比如我想把窗體上的某個控件拖到另外一個位置,當鼠標松開之前需要在鼠標指針下顯示一個幻影,這個幻影就是使用VisualBrush填充出來的一個矩形,並讓矩形捕捉鼠標的位置、隨鼠標移動。
下面是使用不同畫刷填充矩形的綜合實例:
[html] view plain copy
- <Window x:Class="WpfApplication1.Window46"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="Window46" Height="390" Width="600">
- <Grid>
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="180" />
- <ColumnDefinition Width="10" />
- <ColumnDefinition Width="180" />
- <ColumnDefinition Width="10" />
- <ColumnDefinition Width="180*" />
- </Grid.ColumnDefinitions>
- <Grid.RowDefinitions>
- <RowDefinition Height="160" />
- <RowDefinition Height="10" />
- <RowDefinition Height="160" />
- </Grid.RowDefinitions>
- <!--實心填充-->
- <Rectangle Grid.Row="0" Grid.Column="0" Stroke="Black" Fill="LightBlue"></Rectangle>
- <!--線性漸變-->
- <Rectangle Grid.Row="0" Grid.Column="2">
- <Rectangle.Fill>
- <LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
- <GradientStop Color="#FFB6F8F1" Offset="0"></GradientStop>
- <GradientStop Color="#FF0082BD" Offset="0.25"></GradientStop>
- <GradientStop Color="#FF95DEFF" Offset="0.6"></GradientStop>
- <GradientStop Color="#FF004F72" Offset="1"></GradientStop>
- </LinearGradientBrush>
- </Rectangle.Fill>
- </Rectangle>
- <!--徑向漸變-->
- <Rectangle Grid.Row="0" Grid.Column="4">
- <Rectangle.Fill>
- <RadialGradientBrush>
- <GradientStop Color="#FFB6F8F1" Offset="0"></GradientStop>
- <GradientStop Color="#FF0082BD" Offset="0.25"></GradientStop>
- <GradientStop Color="#FF95DEFF" Offset="0.75"></GradientStop>
- <GradientStop Color="#FF004F72" Offset="1.5"></GradientStop>
- </RadialGradientBrush>
- </Rectangle.Fill>
- </Rectangle>
- <!--圖片填充-->
- <Rectangle Grid.Row="2" Grid.Column="0">
- <Rectangle.Fill>
- <ImageBrush ImageSource="./01077_1.png" Viewport="0,0,0.3,0.3" TileMode="Tile">
-
- </ImageBrush>
- </Rectangle.Fill>
- </Rectangle>
- <Rectangle Grid.Row="2" Grid.Column="2">
- <Rectangle.Fill>
- <DrawingBrush Viewport="0,0,0.2,0.2" TileMode="Tile">
- <DrawingBrush.Drawing>
- <GeometryDrawing Brush="LightBlue">
- <GeometryDrawing.Geometry>
- <EllipseGeometry RadiusX="10" RadiusY="10"></EllipseGeometry>
- </GeometryDrawing.Geometry>
- </GeometryDrawing>
- </DrawingBrush.Drawing>
- </DrawingBrush>
- </Rectangle.Fill>
- </Rectangle>
- <!--無填充,使用線性漸變填充邊框-->
- <Rectangle Grid.Row="2" Grid.Column="5" StrokeThickness="10">
- <Rectangle.Stroke>
- <LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
- <GradientStop Color="White" Offset="0.3"></GradientStop>
- <GradientStop Color="Blue" Offset="1"></GradientStop>
- </LinearGradientBrush>
- </Rectangle.Stroke>
- </Rectangle>
- </Grid>
- </Window>
運行效果如下圖:
使用畫刷的時候,建議先在Blend裡面繪制圖大致的效果然後再在Visual Studio裡面微調。
接下來讓我們看一個VisualBrush的例子。為了簡單起見,目標控件是一個Button,實際工作中換成復雜的控件也一樣。程序的XAML代碼如下:
[html] view plain copy
- <Window x:Class="WpfApplication1.Window47"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="Window47" Height="300" Width="400" Background="Orange">
- <Grid Margin="10">
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="160" />
- <ColumnDefinition Width="*" />
- <ColumnDefinition Width="160" />
- </Grid.ColumnDefinitions>
- <StackPanel Background="White" x:Name="spleft">
- <Button Height="40" Content="OK" x:Name="btnReal" Click="btnReal_Click"></Button>
- </StackPanel>
- <Button Grid.Column="1" Content=">>" Margin="5,0"></Button>
- <StackPanel Grid.Column="2" Background="White" x:Name="spRight">
-
- </StackPanel>
- </Grid>
- </Window>
Button的事件處理器代碼如下:
[csharp] view plain copy
- double o = 1;//不透明度指數
- private void btnReal_Click(object sender, RoutedEventArgs e)
- {
- VisualBrush vb = new VisualBrush(this.btnReal);
- Rectangle rtg = new Rectangle();
- rtg.Width = btnReal.Width;
- rtg.Height = btnReal.Height;
- rtg.Fill = vb;
- rtg.Opacity = o;
- o -= 0.2;
- this.spRight.Children.Add(rtg);
- }
運行效果如下圖:
3. 橢圓
橢圓也是一種常見的幾何圖形,它的使用方法和矩形沒有什麼區別。下面的例子是繪制一個球體,球體的輪廓是正圓(Circle),Width和Height相等的橢圓即為正圓:球體的光影使用徑向漸變實現,XAML代碼如下:
[html] view plain copy
- <Window x:Class="WpfApplication1.Window48"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="Window48" Height="300" Width="300">
- <Grid>
- <Ellipse Height="140" Name="ellipse1" Stroke="Gray" Width="140" Cursor="Hand" ToolTip="A Ball">
- <Ellipse.Fill>
- <RadialGradientBrush GradientOrigin="0.2,0.8" RadiusX="0.75" RadiusY="0.75">
- <RadialGradientBrush.RelativeTransform>
- <TransformGroup>
- <RotateTransform Angle="90" CenterX="0.5" CenterY="0.5"></RotateTransform>
- </TransformGroup>
- </RadialGradientBrush.RelativeTransform>
- <GradientStop Color="#FFFFFFFF" Offset="0" />
- <GradientStop Color="#FF444444" Offset="0.66" />
- <GradientStop Color="#FF999999" Offset="1" />
- </RadialGradientBrush>
- </Ellipse.Fill>
- </Ellipse>
- </Grid>
- </Window>
運行效果如下圖:
與前面提到的一樣,橢圓的繪制和色彩填充在Blend裡面完成的,在VS裡面又做了一些相應的調整。
4 路徑
路徑(Path)可以說是WPF繪圖最強大的工具,一來是因為它完全可以替代其它幾種圖形,而來它可以將直線,圓弧,貝塞爾曲線等基本元素組合起來,形成更復雜的圖形。路徑最重要的一個屬性就是Data,Data的數據類型是Geometry(幾何圖形),我們正是使用這個屬性將一些基本的線段拼接起來,形成復雜的圖形。
為Data屬性賦值的方法有兩種:一種是標簽式的標准語法,另外一種是專門用於繪制幾何圖形的“路徑標記語法”。本小節我們使用標准標簽語法認識各種線段,下一節我們將學習繪制幾何圖形的路徑標記語法。
想要使用Path路徑繪制圖形,首先要知道幾何圖形數據是如何組合到Data屬性中的。Path的Data屬性是Geometry類,但是Geometry類是一個抽象類,所以我們不可能在XAML中直接使用<Geometry>標簽。
[html] view plain copy
- <!--不可能出現-->
- <Path>
- <Geometry>
- <!---->
- </Geometry>
- </Path>
我們可以使用Geometry的子類。Geometry的子類包括:
LineGeometry:直線幾何圖形。
RectangleGeometry:矩形幾何圖形。
EllipseGeometry:橢圓幾何圖形。
PathGeometry:路徑幾何圖形。
StreamGeometry:PathGeometry的輕量級替代品,不支持Binding、動畫等效果。
CombinedGeometry:由多個基本幾何圖形關聯在一起,形成的單一幾何圖形。
GeometryGroup:由多個基本幾何圖形組合在一起,形成的幾何圖形組。
可能讓大家比較迷惑的是:前面已經見過Line,Rectangle,Ellipse等類,怎麼現在又出來了LineGeometry、RectangleGeometry、EllipseGeometry類呢?它們的區別在於前面介紹的Line,Rectangle,Ellipse都是可以獨立存在的對象,而這些*Geometry類只能用於結合成其它幾何圖形、不能獨立存在-----當我們在Blend裡面選中一組獨立的幾何圖形並在菜單裡執行組合路徑命令時,本質上就是把原來獨立的Line,Rectangle,Ellipse對象轉換成了*Geometry對象並結合成一個新的復雜幾何圖形。
回到Data的Path屬性,下面這個例子簡要的展示了各種幾何圖形:
[html] view plain copy
- <Window x:Class="WpfApplication1.Window49"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="Window49" Height="350" Width="340">
- <Grid>
- <Grid.RowDefinitions>
- <RowDefinition Height="160" />
- <RowDefinition Height="160" />
- </Grid.RowDefinitions>
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="160" />
- <ColumnDefinition Width="160" />
- </Grid.ColumnDefinitions>
- <!--直線-->
- <Path Stroke="Blue" StrokeThickness="2" Grid.Row="0" Grid.Column="0">
- <Path.Data>
- <LineGeometry StartPoint="0,0" EndPoint="160,160"></LineGeometry>
- </Path.Data>
- </Path>
- <!--矩形路徑-->
- <Path Stroke="Orange" Fill="Yellow" Grid.Row="0" Grid.Column="1">
- <Path.Data>
- <RectangleGeometry Rect="20,20,120,120" RadiusX="10" RadiusY="10"></RectangleGeometry>
- </Path.Data>
- </Path>
- <!--橢圓路徑-->
- <Path Stroke="Green" Fill="LawnGreen" Grid.Column="0" Grid.Row="1">
- <Path.Data>
- <EllipseGeometry Center="80,80" RadiusX="60" RadiusY="40"></EllipseGeometry>
- </Path.Data>
- </Path>
- <!--自定義路徑-->
- <Path Stroke="Yellow" Fill="Orange" Grid.Row="1" Grid.Column="1">
- <Path.Data>
- <PathGeometry>
- <PathGeometry.Figures>
- <PathFigure StartPoint="25,140" IsClosed="True">
- <PathFigure.Segments>
- <LineSegment Point="20,40"></LineSegment>
- <LineSegment Point="40,110"></LineSegment>
- <LineSegment Point="50,20"></LineSegment>
- <LineSegment Point="80,110"></LineSegment>
- <LineSegment Point="110,20"></LineSegment>
- <LineSegment Point="120,110"></LineSegment>
- <LineSegment Point="140,40"></LineSegment>
- <LineSegment Point="135,140"></LineSegment>
- </PathFigure.Segments>
- </PathFigure>
- </PathGeometry.Figures>
- </PathGeometry>
- </Path.Data>
- </Path>
- </Grid>
- </Window>
運行效果如下圖:
其實LineGeometry、RectangleGeometry、EllipseGeometry都比較簡單,現在著重來看PathGeometry。可以說,WPF繪圖的重點是Path,Path的重點在於PathGeometry。PathGeometry之所以這麼重要的原因是因為Path的Figures屬性可以容納PathFigure對象,而PathFigure對象的Segments屬性又可以容納各種線段用來組合成復雜的圖形。XAML代碼結構如下:
[html] view plain copy
- <Path>
- <Path.Data>
- <PathGeometry>
- <PathGeometry.Figures>
- <PathFigure>
- <PathFigure.Segments>
- <!--線段內容-->
- </PathFigure.Segments>
- </PathFigure>
- </PathGeometry.Figures>
- </PathGeometry>
- </Path.Data>
- </Path>
因為Figures是PathGeometry的默認內容屬性、Segments是PathFigure的默認內容屬性,所以常簡化為這樣:
[html] view plain copy
- <Path>
- <Path.Data>
- <PathGeometry>
- <PathFigure>
- <!--線段內容-->
- </PathFigure>
- </PathGeometry>
- </Path.Data>
- </Path>
了解了上面兩個格式之後,我們可以把眼光集中在各種線段上,它們是:
LineSegment:直線段。
ArcSegment:圓弧線段。
BezierSegment:三次方貝塞爾曲線段(默認的貝塞爾曲線指的就是三次方貝塞爾曲線,所以Cubic一詞被省略)。
QuadraticBezierSegment:二次方貝塞爾曲線段。
PolyLineSegment:多直線段。
PolyBezierSegment:多三次方貝塞爾曲線段。
PolyQuadraticBezierSegment:多二次方貝塞爾曲線段。
在繪制這些線段的時候需要注意,所有的這些線段多是沒有起點的(StartPoint),因為起點就是前一個線段的終點,而第一個線段的起點則是PathFigure的StartPoint。請看下面這些例子:
LineSegment最為簡單,只需要控制它的終點(Point)即可。
[html] view plain copy
- <Window x:Class="WpfApplication1.Window50"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="Window50" Height="307" Width="384">
- <Grid VerticalAlignment="Center" HorizontalAlignment="Center">
- <Path Stroke="Green" Fill="LawnGreen" StrokeThickness="2">
- <Path.Data>
- <PathGeometry>
- <PathFigure StartPoint="0,0" IsClosed="True">
- <LineSegment Point="150,0"></LineSegment>
- <LineSegment Point="150,30"></LineSegment>
- <LineSegment Point="90,30"></LineSegment>
- <LineSegment Point="90,150"></LineSegment>
- <LineSegment Point="60,150"></LineSegment>
- <LineSegment Point="60,30"></LineSegment>
- <LineSegment Point="0,30"></LineSegment>
- </PathFigure>
- </PathGeometry>
- </Path.Data>
- </Path>
- </Grid>
- </Window>
運行效果如下圖:
ArcSegment用來繪制圓弧。point屬性用來指明圓弧連接的終點;圓弧截取至橢圓,SIZE屬性即是完整橢圓的橫軸和縱軸半徑,SweepDirection屬性指明圓弧是順時針方向還是逆時針方向;如果橢圓上的兩個點位置不對稱,那麼這兩點間的圓弧就會分為大弧和小弧,IsLargeArc屬性用於指明是否使用大弧去連接;RotationAngle屬性用來指明圓弧母橢圓的旋轉角度,如下圖所示是對幾個屬性的變化做出的詳細對比:
BezierSegment(三次方貝塞爾曲線)由4個點決定:
(1)起點:即前一個線段的終點或PathFigure的StartPoint。
(2)終點:Point3屬性,即曲線的終點位置。
(3)兩個控制點:Point1和Point2屬性。
初略的說,三次方貝塞爾曲線就是由起點出發走向Point1方向,再走向Point2方向,最後到達終點的平滑曲線,具體的算法請查閱維基百科“被塞爾曲線”詞條。
如下代碼是XAML代碼表示的三次方貝塞爾曲線:
[html] view plain copy
- <Window x:Class="WpfApplication1.Window51"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="Window51" Height="300" Width="300">
- <Grid>
- <Path Stroke="Black" StrokeThickness="2">
- <Path.Data>
- <PathGeometry>
- <PathFigure StartPoint="0,0">
- <BezierSegment Point1="250,0" Point2="50,200" Point3="300,200">
-
- </BezierSegment>
- </PathFigure>
- </PathGeometry>
- </Path.Data>
- </Path>
- </Grid>
- </Window>
運行效果如下圖:
QuadraticBezierSegment(二次方貝塞爾曲線)與BezierSegment類似,只是控制點由兩個變為了一個。也就是說QuadraticBezierSegment由3個點決定:
(1)起點:即前一個線段的終點或PathFigure的StartPoint。
(2)終點:Point2屬性,即曲線的終止位置。
(3)控制點:Point1屬性。
如下的代碼就表示的是二次方貝塞爾曲線:
[html] view plain copy
- <Window x:Class="WpfApplication1.Window52"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="Window52" Height="339" Width="325">
- <Grid>
- <Path Stroke="Blue" StrokeThickness="2">
- <Path.Data>
- <PathGeometry>
- <PathFigure StartPoint="0,300">
- <QuadraticBezierSegment Point1="150,-150" Point2="300,300">
-
- </QuadraticBezierSegment>
- </PathFigure>
- </PathGeometry>
- </Path.Data>
- </Path>
- </Grid>
- </Window>
運行效果如下圖:
至此,簡單的路徑就介紹完了。如果想繪制出復雜的圖形來,我們要做的僅僅是在PathFigure把Segment一段段的加上去。
GeometryGroup也是Geometry的一個派生類,它最大的特點是可以將一組PathGeometry組合在一起,如下面的例子:
[html] view plain copy
- <Window x:Class="WpfApplication1.Window53"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="Window53" Height="300" Width="300">
- <Grid>
- <Path Stroke="Black" Fill="LightBlue" StrokeThickness="1">
- <Path.Data>
- <GeometryGroup>
- <PathGeometry>
- <PathFigure StartPoint="0,0">
- <BezierSegment Point1="250,0" Point2="50,200" Point3="300,200"></BezierSegment>
- </PathFigure>
- </PathGeometry>
- <PathGeometry>
- <PathFigure StartPoint="0,0">
- <BezierSegment Point1="230,0" Point2="50,200" Point3="300,200"></BezierSegment>
- </PathFigure>
- </PathGeometry>
- <PathGeometry>
- <PathFigure StartPoint="0,0">
- <BezierSegment Point1="210,0" Point2="50,200" Point3="300,200"></BezierSegment>
- </PathFigure>
- </PathGeometry>
- <PathGeometry>
- <PathFigure StartPoint="0,0">
- <BezierSegment Point1="190,0" Point2="50,200" Point3="300,200"></BezierSegment>
- </PathFigure>
- </PathGeometry>
- <PathGeometry>
- <PathFigure StartPoint="0,0">
- <BezierSegment Point1="170,0" Point2="50,200" Point3="300,200"></BezierSegment>
- </PathFigure>
- </PathGeometry>
- <PathGeometry>
- <PathFigure StartPoint="0,0">
- <BezierSegment Point1="150,0" Point2="50,200" Point3="300,200"></BezierSegment>
- </PathFigure>
- </PathGeometry>
- <PathGeometry>
- <PathFigure StartPoint="0,0">
- <BezierSegment Point1="130,0" Point2="50,200" Point3="300,200"></BezierSegment>
- </PathFigure>
- </PathGeometry>
- </GeometryGroup>
- </Path.Data>
- </Path>
- </Grid>
- </Window>
運行效果如下圖:
5 路徑標記語法
Path是如此強大,可以讓我們隨心所欲的繪制圖形,然而它的一大缺點是不容忽視的,那就是其標簽語法的繁瑣。一般情況下,復雜圖形(Path)是由數10條線段連接而成,按照標簽式語法,每條線段是一個標簽(Segment)、每個標簽占據一行,一個圖形就要占幾十行代碼。而這僅僅是一個圖形,要組成一個完整的圖畫又往往需要10幾個圖形組合在一起,有可能占據數百行代碼!幸好這種事情沒有發生,因為我們可以借助專供WPF繪圖使用的路徑標記語法(Path MarkUp Syntax)來極大的簡化Path的描述。
路徑標記語法實際上就是各種線段的簡記法,比如<LineSegment point="150,5"/>可以簡寫為"L 150,5",這個L就是路徑標記語法中的一個繪圖命令。不僅如此,路徑標記語法還增加了一些更實用的繪圖命令,比如H用來繪制水平線,“H 180”就是指從當前點畫一條水平直線,終點的橫坐標是180(你不需要考慮縱坐標,縱坐標和當前點一致)。類似的還有V命令,用來畫豎直直線。
使用路徑標記語法繪圖一般分三步:移動至起點---繪圖----閉合圖形。這三步使用的命令稍有區別。移動到起點使用的移動命令M,繪圖使用的是繪圖命令,包括:L,H,V,A,C,Q等,下面會逐一介紹;如果圖形是閉合的,需要使用閉合命令Z,這樣最後一條線段的終點與第一條線段的起點間就會連接上一條直線段。
路徑標記語法是不區分大小寫的,所以A和a,H和h是等價的。在路徑標記語法中使用兩個Double類型的數值來表示一個點,第一個值表示的是橫坐標(記做X),第二個值表示縱坐標(記作y),兩個數字可以使用逗號分割(x,y)又可以使用空格分割(x y)。由於路徑標記語法中使用的空格作為兩個點之間的分割,為了避免混淆,建議使用逗號作為點橫縱坐標的分隔符。
如下圖所示是常用的路徑標記語法的總結:
在上述的命令中,S和T兩個命令比較特殊。S用於繪制平滑的賽貝爾曲線,但只需要給出一個控制點,這個控制點相當於普通賽貝爾曲線的第二個控制點,之所以第一個控制點省略不寫是因為平滑三次方賽貝爾曲線會把前一條貝塞爾曲線的第二空控制點以起點為對稱中心的對稱點當作當作自己的第一個控制點(如果前面的線段不是貝塞爾曲線,則第一個控制點和起點相同)。例如,下面兩條曲線是等價的:
[html] view plain copy
- <Path Stroke="Red" Data="M 0,0 C 30,0 70,100 100,100 S 170,0 200,0"></Path>
- <Path Stroke="Blue" Data="M 0,0 C 30,0 70,100 100,100 C 130,100 170,0 200,0"></Path>
與S相仿,T命令用於繪制平滑二次貝塞爾曲線,繪制的時候如果前面也是一條二次貝塞爾曲線的話,T命令會把前面的這段曲線的控制點以起點為對稱中心的對稱點當作自己的控制點(如果前面的線段不是二次貝塞爾曲線則控制點與起點相同)。下面兩條曲線等價:
[html] view plain copy
- <Path Stroke="Red" Data="M 0,200 Q 100,0 200,200 T 400,200"></Path>
- <Path Stroke="Blue" Data="M 0,200 Q 100,0 200,200 Q 300,400 400,200"></Path>
現在我們就可以使用路徑標記語法來繪圖了!使用方法是吧這這些命令串起來、形成一個字符串,然後賦值給Path的Data屬性。使用Blend繪圖時,Blend會自動使用路徑標記語法來記錄數據而不是是用代碼量巨大的標簽式語法。
6 使用Path剪切界面元素
實際工作中經常會遇到制作不規則的窗體或者控件,WPF在這方面做了良好的支持,僅需使窗體和控件的Clip屬性就可以輕松做到。
Clip屬性被定義在UIEelment類中,因此,WPF窗體的所有控件、圖形都具有這個屬性。Clip屬性的數據類型是Geometry,與Path的Data屬性一致。因此,我們只需要按照需求制作好特殊形狀的Path並把Path的Data屬性值賦值給目標窗體、控件或者其它圖形,對目標的剪切就算完成了。請看下面這個不規則窗體的例子。
[html] view plain copy
- <Window x:Class="WpfApplication1.Window56"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="Window56" Height="250" Width="300" Window AllowsTransparency="True" WindowStartupLocation="CenterScreen" Background="Yellow">
- <Grid VerticalAlignment="Center" HorizontalAlignment="Center">
- <Path Stroke="Orange" Fill="Yellow" x:Name="clipPath0" Visibility="Hidden" Data="M 55,100 A 50,50 0 1 1 100,60 A 110,95 0 0 1 200,60 A 50,50 0 1 1 250,100 A 110,95 0 1 1 55,100 Z">
- </Path>
- <Button Content="Clip" Width="80" Height="25" Click="Button_Click" HorizontalAlignment="Center" VerticalAlignment="Center"></Button>
- </Grid>
- </Window>
如果想讓一個窗體可以被裁切,那麼其AllowsTransparency必須要設置為True,這個屬性設為True之後,WindowStyle必須要設置為None。
Button的事件處理器中代碼如下:
[csharp] view plain copy
- private void Button_Click(object sender, RoutedEventArgs e)
- {
- this.Clip = clipPath0.Data;
- }
運行程序,單擊按鈕,運行效果如下圖:
1.2 圖形的效果和濾鏡
以往,程序員們很頭疼的一件事情就是要精確實現UI設計師們給出的樣稿。有些時候程序員可以使用設計師提供的圖片作為背景或者貼圖,但這些圖片往往都是位圖而非矢量圖,所以當應用了這些圖片的窗體或控件的尺寸發生改變時,圖片就會出現鋸齒、馬賽克或失真等情況。對於那些對用戶體驗要求比較高的軟件程序員不能直接使用位圖了,只能用代碼來實現設計師的設計。要知道,設計師手裡用的是PhotoShop,FireWorks這些專業的設計工具,加個陰影,生成個發光效果就是點點鼠標的事,同樣的效果讓程序員使用C#實現就沒有那麼容易了,有的時候甚至需要用到一些在游戲裡面才會用到的技術和知識,這無疑增加了開發的難度和成本。
WPF的出現無疑是程序員的福音,因為不但像陰影,發光效果可以使用一兩個屬性來實現,就連通道、動態模糊這些高級的效果也可以輕松實現。同時,設計師和程序員還可以像為PhotoShop開發濾鏡一樣為WPF開發效果類庫,屆時只需要把類庫引入到項目中就可以使用其中的效果了(微軟官方網站和一些開源網站上已經有很多效果類庫可供使用)。
在UIElement類的成員中你可以找到BitmapEffect和Effect這兩個屬性,這兩個屬性都是為UI元素添加效果。你可能會問:為做同一件事准備了兩個屬性,難道不沖突嗎?答案是:的確沖突。WPF最早的版本裡面只有BitmapEffect這個屬性,這個屬性使用CPU的運算能力為UI元素添加效果,這樣做的問題就是效果一多或者讓帶有效果UI元素參與動畫,程序的性能會因為CPU資源被大量占用而大幅降低(要麼反應變慢,要麼刷新或者動畫變的很卡)。隨後的版本中,微軟決定轉用顯卡GPU的運算能力為UI元素添加效果,於是添加了Effect這個屬性。這樣即減少了對CPU的浪費又將應用程序的視覺效果拉平到與游戲程序一個級別。
因為有Effect這個屬性替換BitmapEffect,所以你在MSDN文檔裡面看到BitmapEffect被標記為已過時,不過在WPF4.0中Bitmapeffect仍然可以使用,也就是說未來兩三年裡,bitmapEffect仍然可以使用。下面讓我們嘗試如何使用這兩種效果:
1.2.1 簡單易用的BitmapEffect
BitmapEffect定義在UIElement類中,它的數據類型是BitmapEffect類。BitmapEffect是一個抽象類,所以我們只能使用它的派生類來為UIElement的BitmapEffect屬性賦值。BitmapEffect類的派生類並不多,包括以下幾個:
BevelBitmapEffect:斜角效果。
BitmapEffectGroup:復合效果(可以把多個BitmapEffect組合在一起)。
BlurBitmapEffect:模糊效果。
DropShadowBitmapEffect:投影效果。
EmbossBitmapEffect:浮雕效果。
OuterGlowBitmapEffect:外發光效果。
每個效果都有自己的一系列屬性來做調整,比如你可以調整投影效果的投影高度,陰影深度和角度,讓用戶感覺光是由某個角度投射下來;你也可以調整外發光效果的顏色和延展距離。下面一個DropShadowBitmapEffect的簡單例子:
[html] view plain copy
- <Window x:Class="WpfApplication1.Window57"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="Window57" Height="300" Width="300">
- <Grid>
- <Button Width="80" Height="50">
- <Button.BitmapEffect>
- <DropShadowBitmapEffect Direction="-45" Opacity="0.75" Color="Red" ShadowDepth="7"></DropShadowBitmapEffect>
- </Button.BitmapEffect>
- </Button>
- </Grid>
- </Window>
運行效果如下圖:
對於每個BitmapEffect的派生類MSDN都有相當詳細的記述,請大家自行查閱。
1.2.2 豐富多彩的Effect
繪圖軟件Photoshop能夠大獲全勝的一個重要因素就是它的插件標准是公開的,這樣,眾多的第三方公司和人員就可以為它設計各種各樣的插件、極大的豐富它的功能----眾人拾柴火焰高。在眾多的Photoshop插件中,最重要的一類就是濾鏡,或者說是圖片的效果。比如說我要把一張圖片制作成老照片的效果,在沒有濾鏡的情況下,就需要手工調整很多圖片屬性才能做到,而使用濾鏡,則只需要把濾鏡加在圖片上即可。顯然使用濾鏡效果可以獲得如下好處:
- 提高工作效率。
- 得到更專業的效果。
- 對使用者的技術水平要求相對較低。
WPF中引進了這種濾鏡插件的思想,其成功就是UIElement類的Effect屬性。Effect屬性的類型是Effect類,Effect是抽象類,也就是說UIElement的Effect屬性可以接收Effect類的任何一個派生類實例作為自己的值。Effect類位於System.Windows.Media.Effects名稱空間中,它的派生類有3個,分別是:
- BlurEffect:模糊效果。
- DropShadowEffect:投影效果。
- ShaderEffect:著色器效果(抽象類)。
你可能會想----強大的Effect派生類怎麼還沒有已經廢棄的BitmapEffect類多呢?答案是這樣的:因為模糊和投影在編程中使用的最多,所以.NET Framwork內建了這兩個效果。這兩個效果使用起來非常方便,而且請注意,這兩個效果是使用GPU進行渲染,而不像BitmapEffect那樣使用CPU渲染。ShaderEffect仍然是個抽象類,它就是流給濾鏡開發人員的接口。只要你開發派生自ShaderEffect的效果類,別人就可以直接拿來用。
開發著色器效果需要使用Pixel Shader語言(簡寫和Photoshop一樣,也是PS)和一些DirectX知識,超出了本書的范圍。感興趣的讀者可以在微軟的官網上找到它的SDK和開發文檔。
對於大多數WPF開發人員來說,我們需要的是現成的濾鏡效果,所以官方的濾鏡包可以叢這裡下載:http://wpffx.codeplex.com。解壓下載的ZIP文件,可以看到分別為WPF和Silveright准備的兩套濾鏡。進入WPF文件夾,ShaderEffectLibrary文件夾裡的項目就是效果庫。效果庫的使用方法如下:
首先到http://wpf.codeplex.com/releases/view/14962下載Shader Effect BuildTask And Templates.zip。解壓ZIP文件之後按照其中的Readme文檔進行安裝,配置。這是著色器效果的編譯/開發環境,沒有它,著色器效果項目將不能被編譯。如果你想開發自己的效果濾鏡,也必須安裝這個環境。檢驗安裝是否成功的方法是啟動VS,查看是否可以新建WPF Shader Effect Library項目,如下圖所示:
新建一個WPF解決方案,把ShaderEffectLibrary中的項目添加進來,並為WPF項目添加對WPFShaderEffectLibrary項目的引用,你就可以使用效果庫裡面的效果了。
使用濾鏡庫,只需要設置幾個屬性,層次分明、動感十足的圖片效果就出來了!這樣的工作即可以由設計師來完成,也可以由程序員來完成。如果對效果不滿意,直接在XAML文件裡面修改並保存就可以了,而不必再像之前那樣再用Photoshop等工具進行返工。同時,同一張原圖片可以加載為不同的效果,也不必像之前一樣先由設計師制作出多張圖片再添加進應用程序,這樣,程序的編譯結果也會小很多。
1.3 圖形的變形
當我們看到“變形”這個詞時,首先會想起什麼?拉長、擠扁?放大、縮小?還是... 變形金剛?其實WPF中的“變形“的含義很廣,尺寸、位置、坐標系比例、旋轉角度等的變化都算變形。
WPF中的變形是和UI元素分開的。舉個例子,你可以設計一個”向左旋轉45度“的變形,然後把這個變形賦值給不同的UI元素的變形控制屬性,這些UI元素都會向左旋轉45度了。這種將元素和變形控制屬性分開的設計方案即減少了為UIElement類添加過多的屬性,又提高了變形類實例的復用性,可謂一舉兩得。這種設計模式非常符合策略模式中的”有一個“比”是一個“更加靈活的思想。
控制編寫的屬性有兩個,分別是:
RenderTransform:呈現變形,定義在UIElement類中。
LayoutTransform:布局變形,定義在FramworkElement類中。
因為FramworkElment類派生自UIElement類,而控件的基類Control類又派生自FramworkElment類中,所以在控件級別,你兩個屬性都可以看到。這兩個屬性都是依賴屬性,它們的數據類型都是Transform抽象類,也就是說,Transform類的派生類均可為這兩個屬性賦值。
Transform抽象類的派生類有如下一些:
MatrixTransform:矩陣變形,把容納被變形UI元素的矩形頂點看做是一個矩形進行變形。
RotateTransform:旋轉變形,以給定的點為旋轉中心,以角度為單位進行旋轉變形。
ScaleTransform:坐標系變形,調整被變形元素的坐標系,可產生縮放效果。
SkewTransform:拉伸變形,可在橫向和縱向上對被變形元素進行拉伸。
TranslateTransform:偏移變形,使被變形元素在橫向或者縱向上偏移一個給定的值。
TransformGroup:變形組,可以把多個獨立的變形合成為一個變形組、產生復合變形效果。
1.3.1 呈現變形
什麼是呈現呢?相信大家都見過海市蜃樓吧!遠遠望去,遠方的天空中漂浮著一座城市,而實際上那裡沒有城市,有的只是沙漠和海洋... ...,海市蜃樓形成的原因是密度不均的空氣使光線產生折射,最終讓人看到城市的影像呈現在本不應該出現的位置上---這就是城市影像的呈現出現了變形。WPF的RederTransform屬性就是要起到這個作用,讓UI元素呈現出來的屬性與它本來的屬性不一樣!比如,一個按鈕本來處於Canvas或者Grid的左上角,而我可以使用RenderTransform讓它呈現在右下角並且旋轉45°。
觀察下面這個例子:
[html] view plain copy
- <Window x:Class="WpfApplication1.Window59"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="Window59" Height="334" Width="485">
- <Grid Margin="10" Background="AliceBlue">
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="Auto">
-
- </ColumnDefinition>
- <ColumnDefinition Width="*">
-
- </ColumnDefinition>
- </Grid.ColumnDefinitions>
- <Grid.RowDefinitions>
- <RowDefinition Height="Auto">
-
- </RowDefinition>
- <RowDefinition Height="*"></RowDefinition>
- </Grid.RowDefinitions>
- <Button Width="80" Height="80" Content="OK">
- <Button.RenderTransform>
- <!--復合變形-->
- <TransformGroup>
- <!--旋轉變形-->
- <RotateTransform CenterX="40" CenterY="40" Angle="45"></RotateTransform>
- <!--偏移變形-->
- <TranslateTransform X="300" Y="150"></TranslateTransform>
- </TransformGroup>
- </Button.RenderTransform>
- </Button>
- </Grid>
- </Window>
在布局Grid裡,布局分為兩行兩列,並且第一行行高,第一列列寬都是由Button來決定的。同時,我為Button的RenderTransform設置了一個復合變形,使用Transform將一個偏移變形和一個旋轉變形組合在了一起。偏移變形將Button的呈現(而不是Button本身)向右移動300像素,向下移動150像素;旋轉變形將Button的呈現向右旋轉45°。在窗體的設計器裡面,我們可以清晰的看到Button的位置並沒有改變(第一行和第一列並沒有變化),但Button卻出現在了右下(300,150)的位置,並向右旋轉了45°。如下圖所示:
運行效果如下圖:
用戶並不能覺察到究竟是控件本身的位置、角度發生了變化,還是呈現的位置發生了變化。
為什麼需要呈現變形呢?答案是:為了效率!在窗體上移動UI元素本身會導致窗體布局的改變,而窗體的布局的每一個(哪怕是細微的)變化都將導致所有的窗體元素的尺寸測算函數,位置測算函數、呈現函數等的調用,造成系統資源占用激增、程序性能陡降。而使用呈現變形則不會遇到這樣的問題,呈現變形值改變元素顯示在哪裡,所以不牽扯布局的變化、只涉及窗體的重繪。所以,當你需要制作動畫的時候,請切記要使用RenderTransform。
1.3.2 布局變形
與呈現變形不同,布局變形會影響窗體的布局、導致窗體布局的重新測算。因為窗體布局的重新測算和繪制會影響程序的性能,所以布局變形一般只用在靜態變形上,而不用於繪制動畫。
考慮這樣一個需求:制作一個文字縱向排列的淺藍色標題欄。如果我們使用呈現變形,代碼如下:
[html] view plain copy
- <Window x:Class="WpfApplication1.Window60"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="Window60" Height="338" Width="471">
- <Grid x:Name="titleBar" Background="LightBlue">
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="Auto"></ColumnDefinition>
- <ColumnDefinition Width="*"></ColumnDefinition>
- </Grid.ColumnDefinitions>
- <TextBlock FontSize="24" Text="Hello Transformer" VerticalAlignment="Bottom" HorizontalAlignment="Center">
- <TextBlock.RenderTransform>
- <RotateTransform Angle="-90"></RotateTransform>
- </TextBlock.RenderTransform>
- </TextBlock>
- </Grid>
- </Window>
設計器中的效果如下:
盡管我們讓顯示文字的TextBlock“看起來”旋轉了90°,但TextBlock本身並沒有變化,改變的只是它的顯示,所以,它的寬度仍然是吧寬度設為Auto的第一列撐的很寬。顯然這不是我們希望看到的。
分析需求,我們實際需要的是靜態改變TextBlock的布局,因此應該使用LayoutTransform。僅需要對上面的代碼進行一處改動:
[html] view plain copy
- <Window x:Class="WpfApplication1.Window60"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="Window60" Height="338" Width="471">
- <Grid x:Name="titleBar" Background="LightBlue">
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="Auto"></ColumnDefinition>
- <ColumnDefinition Width="*"></ColumnDefinition>
- </Grid.ColumnDefinitions>
- <TextBlock FontSize="24" Text="Hello Transformer" VerticalAlignment="Bottom" HorizontalAlignment="Center">
- <TextBlock.LayoutTransform>
- <RotateTransform Angle="-90"></RotateTransform>
- </TextBlock.LayoutTransform>
- </TextBlock>
- </Grid>
- </Window>
設計器中的效果如圖所示:
1.4 動畫
何為動畫?動畫自然就是“會動的畫”。所謂“會動”不光指位置會移動,還包括角度的旋轉、顏色的變化、透明度的增減等。細心的讀者已經發現,動畫本質就是在一個時間段裡對象位置、角度、顏色、透明度等屬性值的連續變化。這些屬性中,有些是對象自身的屬性,有些則是上一節所學的圖形變形的屬性。有一點需要注意,WPF規定,可以用來制作動畫的屬性必須是依賴屬性。
變化即是運動。“沒有脫離運動的物體,也沒有脫離物體的運動”,唯物主義如是說。WPF的動畫也是一種運動,這種運動的主體就是各種UI元素,這種運動本身就是施加在UI控件上的一些Timerline派生類的實例。在實際工作中,我們要做的事情往往就是先設計好一個動畫構思、用一個Timerline派生類的實例加以表達,最後讓某個UI元素來執行這個動畫、完成動畫與動畫主體的結合。
簡單的動畫用一個元素來完成就可以了,就像一個演員的獨角戲,WPF把簡單動畫稱為AnimationTimeline。復雜的(即並行的,復合的)動畫就需要UI上多個元素協同完成,就像電影中的一段場景。復雜動畫的協同包括有哪些UI元素參與動畫、每個元素的動畫行為是什麼、動畫何時開始何時結束等。WPF把一組協同的動畫也稱做Storyboard。
Timeline、AnimationTimeline、Storyboard的關系如下圖所示:
本節分兩部分,先研究了如何設計簡單獨立的動畫,再研究如何把簡單的動畫組合在一起形成場景。
1.4.1 簡單獨立動畫
前面說過,動畫就是“會動的畫”,而這個會動指的是能夠讓UI元素變形的某個屬性值產生了連續的變化。任何一個屬性都有自己的數據類型,比如UIElement的Width和Height屬性為Double類型,Window的Title屬性為string類型。幾乎針對每個可能的數據類型,WPF的動畫子系統都為其准備了相應的動畫類,這些動畫類均派生自AnimationTimeline。它們包括:
上面列出的這些類都帶有Base後綴,說明它們都是抽象基類。完整的情況下,這些抽象的基類又能派生出三種動畫,即簡單動畫、關鍵幀動畫、沿路徑運動的動畫。例如DoubleAnimationBase,它完整的派生出了3個具體的動畫,如下圖所示:
而針對Int類型的Int32AnimationBase只派生出了Int32Animation和Int32AnimationUsingKeyFrames兩個具體的動畫類。BooleanAnimationBase和CharAnimationBase的派生類則更少,只有關鍵幀動畫類。
因為WPF動畫系統中Double類型屬性用的最多,而且DoubleAnimationBase的派生類也最完整,所以本節只講述DoubleAnimationBase的派生類。學習完這個類,其它的動畫類型亦可觸類旁通。
1,簡單的線性動畫
所謂簡單的線性動畫,就是指僅有變化起點、變化終點、變化幅度、變化時間4個要素構成的動畫。
變化時間(Duration屬性):必須指定,數據類型是Duration。
變化終點(To屬性):如果沒有指定變化終點,程序將采用上一次的動畫的終點或默認值。
變化起點(From屬性):如果沒有指定變化的起點則以變化目標屬性的當前值為起點。
變化幅度(By屬性):如果同時指定了變化終點,變化幅度將被忽略。
讓我們分析一個例子,簡單的XAML代碼如下:
[html] view plain copy
- <Window x:Class="WpfApplication1.Window61"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="Window61" Height="372" Width="475">
- <Grid>
- <Button Width="80" Height="80" HorizontalAlignment="Left" VerticalAlignment="Top">
- <Button.RenderTransform>
- <TranslateTransform X="0" Y="0" x:Name="tt"></TranslateTransform>
- </Button.RenderTransform>
- </Button>
- </Grid>
- </Window>
用戶界面上只包含了一個Button,這個Button的RederTransform屬性值是一個名為tt的TranslateTransform對象,改變這個對象的X,Y值就會讓Button的顯示位置(而不是現在的真實位置)變化。Button的Click事件處理器代碼如下:
[csharp] view plain copy
- private void Button_Click(object sender, RoutedEventArgs e)
- {
- DoubleAnimation dax = new DoubleAnimation();
- DoubleAnimation day = new DoubleAnimation();
- //指定起點
- dax.From = 0;
- day.From = 0;
- //指定終點
- Random rdm = new Random();
- dax.To = rdm.NextDouble() * 300;
- day.To = rdm.NextDouble() * 300;
- //指定時長
- Duration duration = new Duration(TimeSpan.FromMilliseconds(3000));
- dax.Duration = duration;
- day.Duration = duration;
- //動畫主體是TranslatTransform變形,而非Button
- this.tt.BeginAnimation(TranslateTransform.XProperty,dax);
- this.tt.BeginAnimation(TranslateTransform.YProperty,day);
- }
因為TranslateTransform的X,Y屬性均為Double類型,所有我們選用DoubleAnimation來使之產生變化。代碼中聲明的dax、day兩個DoubleAnimation變量並分別為之創建引用實例。接下來的代碼依次為它們設置了起始值、終止值、變化時間。最後,調用BeginAnimation方法,讓dax作用在TranslateTransform的XProperty屬性上,讓day作用在TranslateTransform的YProperty屬性上。運行程序,每次單擊按鈕,按鈕都會從起始位置(窗體的左上角)向窗體的右下角長寬不超過300像素的矩形內的某點運動,完成運動的時長為300毫秒。運行效果如下圖:
這段代碼有以下幾處值得注意的地方:
- 因為指定了daX和daY的起始值為0,所以每次按鈕都會“跳”回窗體的左上角開始動畫。如果想讓按鈕從當前位置開始下一次動畫,只需要把“dax.From=0;”和"day.From=0"去掉即可。
- 盡管表現出來的是button在移動,但DoubleAnimation的作用目標並不是Button而是TranslateTransform實例,因為TranslateTransform實例是Button的RenderTransform屬性,所以Button“看上去”是移動了。
- 前面說過,能用來制作動畫效果的屬性必須是依賴屬性,TranslateTransform的XProperty和YProperty就是兩個依賴屬性值。
- UIElement和Animation兩個類都定義了BeginAnimation這個方法。TranslateTransform派生自Animation類,所以具有這個方法。這個方法的調用者就是動畫要作用的目標對象,兩個參數分別指明被作用的依賴屬性(TranslateTransform.XProperty和TranslateTransform.YProperty)和設計好的動畫(dax和day)。可以猜想,如果要動畫改變Button的寬度和高度(這兩個屬性也是double類型),也應該首先創建DoubleAnimation實例,然後設置起至值和動畫時間,最後調用Button的BeginAnimation方法,使用動畫對象影響Button的WidthProperty和HeightProperty。
如果把事件處理器中的代碼改成這樣:
[csharp] view plain copy
- private void Button_Click(object sender, RoutedEventArgs e)
- {
- DoubleAnimation dax = new DoubleAnimation();
- DoubleAnimation day = new DoubleAnimation();
- //指定起點
- //dax.From = 0;
- //day.From = 0;
- //指定終點
- //Random rdm = new Random();
- //dax.To = rdm.NextDouble() * 300;
- //day.To = rdm.NextDouble() * 300;
- dax.By=100D;
- day.By = 100D;
- //指定時長
- Duration duration = new Duration(TimeSpan.FromMilliseconds(300));
- dax.Duration = duration;
- day.Duration = duration;
- //動畫主體是TranslatTransform變形,而非Button
- this.tt.BeginAnimation(TranslateTransform.XProperty,dax);
- this.tt.BeginAnimation(TranslateTransform.YProperty,day);
- }
運行的效果如下:
2 高級動畫控制
使用From、To、By、Duration幾個屬性進行組合已經可以制作很多不同效果的動畫了,然而WPF的動畫系統的控制屬性遠遠不止這些。如果想制作出更加復雜或逼真的動畫,還需要使用如下一些效果:
對於這些屬性,大家可以自己動手嘗試---對它們進行組合往往可以產生很多意想不到的效果。
在這些屬性中,EasingFunction是一個擴展性非常強的屬性。它的取值類型是一個IEasingFunction接口類型,而WPF自帶的IEasingFunction派生類就有10多種,每個派生類都能產生不同的結束效果。比如BounceEase可以產生乒乓球彈跳式效果,我們可以直接拿來使用而不必花精力親自創作。
如果把前面的例子改成這樣:
[csharp] view plain copy
- private void Button_Click(object sender, RoutedEventArgs e)
- {
- DoubleAnimation dax = new DoubleAnimation();
- DoubleAnimation day = new DoubleAnimation();
-
- //設置反彈
- BounceEase be = new BounceEase();
- //設置反彈次數為3
- be.Bounces = 3;
- be.Bounciness = 3;//彈性程度,值越大反彈越低
- day.EasingFunction = be;
-
- //設置終點
- dax.To = 300;
- day.To = 300;
-
- //指定時長
- Duration duration = new Duration(TimeSpan.FromMilliseconds(2000));
- dax.Duration = duration;
- day.Duration = duration;
- //動畫主體是TranslatTransform變形,而非Button
- this.tt.BeginAnimation(TranslateTransform.XProperty,dax);
- this.tt.BeginAnimation(TranslateTransform.YProperty,day);
- }
運行效果如下圖:
3 關鍵幀動畫
動畫是UI元素屬性連續發送變化產生的視覺效果。屬性每次細微的變化都會產生一個新的畫面,每個新畫面就稱為一幀,幀的連續播放就產生了動畫效果。如同電影一樣,單位時間內播放的幀數越多,動畫的效果就會越細致。前面講到的簡單動畫只設置了起點和終點,之間的動畫幀都是由程序計算出來並繪制的,程序員無法進行控制。關鍵幀動畫則允許程序員為一段動畫設置幾個“裡程碑”,動畫執行到裡程碑所在的時間點時,被動畫控制的屬性值也必須達到設定的值,這些時間線上的“裡程碑”就是關鍵幀。
思考這樣一個需求:我想讓一個Button用900毫秒的時間從左上角移動到右下角,但移動的路線不是直接走動而是走Z字形。如下圖所示:
如果我們不知道有關鍵幀動畫可用而只使用簡單的動畫,那麼我們需要創建若干個簡單的動畫分別控制TranslateTransform的X和Y,比較棘手的是需要控制這些動畫之間的協同。協同策略有兩種,一種是靠時間來協同,也就是設置後執行動畫的BeginTime以等待前面動畫執行完畢,另一種是靠事件協同,也就是為先執行的動畫添加Complated事件處理器,在事件處理器中開始下一段動畫。因為是多個動畫的協同,所以在動畫需要改變的時候,代碼的改動會比較大。
使用關鍵幀動畫情況就會大有改觀----我們只需要創建兩個DoubleAnimationUsingKeyFrames實例,一個控制TranslateTransForm的X屬性,另一個控制TranslateTransForm的Y屬性即可。每個DoubleAnimationUsingKyeFrames各擁有3個關鍵幀用於指明X或Y在三個時間點(兩個拐點和一個終點)應該達到什麼樣的值。
程序的XAML代碼如下:
[html] view plain copy
- <Window x:Class="WpfApplication1.Window61"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="Window61" Height="372" Width="475">
- <Grid>
- <Button Width="80" Height="80" HorizontalAlignment="Left" VerticalAlignment="Top" Click="Button_Click">
- <Button.RenderTransform>
- <TranslateTransform X="0" Y="0" x:Name="tt"></TranslateTransform>
- </Button.RenderTransform>
- </Button>
- </Grid>
- </Window>
Button的Click事件處理器代碼如下:
[csharp] view plain copy
- private void Button_Click(object sender, RoutedEventArgs e)
- {
- DoubleAnimationUsingKeyFrames dakX = new DoubleAnimationUsingKeyFrames();
- DoubleAnimationUsingKeyFrames dakY = new DoubleAnimationUsingKeyFrames();
- //設置動畫總時長
- dakX.Duration = new Duration(TimeSpan.FromMilliseconds(900));
- dakY.Duration = new Duration(TimeSpan.FromMilliseconds(900));
- //創建,添加關鍵幀
- LinearDoubleKeyFrame x_kf_1 = new LinearDoubleKeyFrame();
- LinearDoubleKeyFrame x_kf_2 = new LinearDoubleKeyFrame();
- LinearDoubleKeyFrame x_kf_3 = new LinearDoubleKeyFrame();
- x_kf_1.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(300));
- x_kf_1.Value = 200;
- x_kf_2.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(600));
- x_kf_2.Value = 0;
- x_kf_3.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(900));
- x_kf_3.Value = 200;
- dakX.KeyFrames.Add(x_kf_1);
- dakX.KeyFrames.Add(x_kf_2);
- dakX.KeyFrames.Add(x_kf_3);
-
- LinearDoubleKeyFrame y_kf_1 = new LinearDoubleKeyFrame();
- LinearDoubleKeyFrame y_kf_2 = new LinearDoubleKeyFrame();
- LinearDoubleKeyFrame y_kf_3 = new LinearDoubleKeyFrame();
- y_kf_1.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(300));
- y_kf_1.Value = 0;
- y_kf_2.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(600));
- y_kf_2.Value = 180;
- y_kf_3.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(900));
- y_kf_3.Value = 180;
- dakY.KeyFrames.Add(y_kf_1);
- dakY.KeyFrames.Add(y_kf_2);
- dakY.KeyFrames.Add(y_kf_3);
-
- //執行動畫
- tt.BeginAnimation(TranslateTransform.XProperty, dakX);
- tt.BeginAnimation(TranslateTransform.YProperty,dakY);
- }
在這組關鍵幀動畫中,我們使用了最簡單的關鍵幀LinearDoubleKeyFrame,這種關鍵幀的特點就是只需要你給定時間點(KeyTime屬性)和到達時間點時的目標屬性值(Value屬性)動畫就會讓目標屬性值在兩個關鍵幀之間勻速運動。比如這兩句代碼:
[csharp] view plain copy
- x_kf_1.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(300));
- x_kf_1.Value = 200;
- x_kf_2.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(600));
- x_kf_2.Value = 0;
x_kf_1關鍵幀處在時間線300毫秒處,目標屬性值在這一時刻必須達到200(是什麼屬性這個時候並不知道,但要求這個屬性值在這個時間一定要達到200),類似,x_kf_2在時間線的位置是600毫秒處,目標屬性的值為0。當動畫開始執行之後,程序會自動計算出目標屬性在這兩個關鍵幀之間的勻速變化。
前面的代碼中,為關鍵幀的KeyTime屬性使用的是KeyTime.FromTimeSpan靜態方法,這樣可以得到一個絕對的時間點。使用KeyTime.FromPercent靜態方法則可以獲得百分比計算的相對時間點,程序將整個關鍵幀的動畫時長(Duration)視為100%。我們就可以把前面的代碼改成這樣:
[csharp] view plain copy
- x_kf_1.KeyTime = KeyTime.FromPercent(0.33);
- x_kf_1.Value = 200;
- x_kf_2.KeyTime = KeyTime.FromPercent(0.66);
- x_kf_2.Value = 0;
- x_kf_3.KeyTime = KeyTime.FromPercent(1);
- x_kf_3.Value = 200;
- dakX.KeyFrames.Add(x_kf_1);
- dakX.KeyFrames.Add(x_kf_2);
- dakX.KeyFrames.Add(x_kf_3);
之後我們無論將dakX的Duration改為多少,3個關鍵幀都會將這個時間劃分為均等的3段。
4 特殊關鍵幀
DoubleAnimationUsingKeyFrames的KeyFrames屬性的數據類型是DoubleKeyFrameCollection,此集合類可接收的元素類型是DoubleKeyFrame。DoubleKeyFrame是一個抽象類,前面使用的LinearDoubleKeyFrame就是它的派生類之一。DoubleKeyFrame的所有派生類如下:
LinearDoubleKeyFrame:線性變化關鍵幀,目標屬性值的變化是線性的、均勻的,即變化速率不變。
DisCreteDoubleKeyFrame:不連續變化關鍵幀,目標屬性值變化是跳躍性的,躍遷的。
SplineDoubleKeyFrame:樣條函數式變化幀,目標屬性的變化值是一條貝塞爾曲線。
EasingDoubleKeyFrame:緩沖是變化關鍵幀,目標屬性值以某種緩沖形式變化。
4個派生類中最常用的就是SplineDoubleKeyFrame(SplineDoubleKeyFrame可以替換LinearDoubleKeyFrame)。使用SplineDoubleKeyFrame可以方便的制作非勻速動畫,因為它使用一條賽貝爾曲線來控制目標屬性的變化速率。這條用於控制目標屬性變化速率的貝塞爾曲線的起點是(0,0)和(1,1),分別映射著目標屬性的變化起點和變化終點,意思是目標屬性由0%變化到100%。這條貝塞爾曲線有兩個控制點----ControlPoint1和ControlPoint2,意思是貝塞爾曲線從起點出發先想ControlPoint1移動、再向ControlPoint2移動,最後到達終點,形成一條平滑的曲線。如果設置ControlPoint1和ControlPoint2的橫坐標值相等,比如(0,0)、(0.5,0.5)、(1,1)則貝塞爾曲線是一條直線,這時候SplineDoubleKeyFrame和LinearDoubleKeyFrame是等價的。當控制點的橫縱坐標不相等時,貝塞爾曲線就會出現很多變化。如下圖所示,這些是貝塞爾曲線控制點處的典型位置是出現的速率曲線,X1,Y1是ControlPoint0的坐標,X2,Y2是ControlPoint2的坐標。
下面是一個SplineDoubleKeyFrame的一個實例。程序的XAML代碼如下:
[html] view plain copy
- <Window x:Class="WpfApplication1.Window62"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="Window62" Height="303" Width="544">
- <Grid Margin="10" Background="AliceBlue">
- <Button Width="80" Height="80" Content="Move" HorizontalAlignment="Left" VerticalAlignment="Top" Click="Button_Click">
- <Button.RenderTransform>
- <TranslateTransform x:Name="tt" X="0" Y="0"></TranslateTransform>
- </Button.RenderTransform>
- </Button>
- </Grid>
- </Window>
Button的Click事件處理器代碼如下:
[csharp] view plain copy
- private void Button_Click(object sender, RoutedEventArgs e)
- {
- //創建動畫
- DoubleAnimationUsingKeyFrames dakX = new DoubleAnimationUsingKeyFrames();
- dakX.Duration = new Duration(TimeSpan.FromMilliseconds(1000));
-
- //創建、添加關鍵幀
- SplineDoubleKeyFrame kf = new SplineDoubleKeyFrame();
- kf.KeyTime = KeyTime.FromPercent(1);
- kf.Value = 400;
-
- KeySpline ks = new KeySpline();
- ks.ControlPoint1 = new Point(0,1);
- ks.ControlPoint2 = new Point(1,0);
-
- kf.KeySpline = ks;
- dakX.KeyFrames.Add(kf);
-
- //執行動畫
- this.tt.BeginAnimation(TranslateTransform.XProperty ,dakX);
- }
關鍵幀動畫會控制Button的位置變形、讓Button橫向運動。整個動畫只有一個關鍵幀,這個關鍵幀使用的是SplineDoubleKeyFrame,變化速率控制曲線的兩個控制點分別是(0,1)和(1,0)。與上圖中的最後一幅圖一致,因此目標屬性會以快--慢---快的形式變化。程序的執行效果如下圖所示:
5. 路徑動畫
如何讓目標對象沿著一條給定的路徑移動呢?答案是使用DoubleAnimationUsingPath類。DoubleAnimationUsingPath需要一個PathGeometry來指明移動路徑,PathGeometry的數據信息可以用XAML中的Path語法書寫。PathGeometry的另外一個重要屬性是Source,Source屬性的數據類型是PathAnimationSource枚舉,枚舉值可取X、Y或Angle。如果路徑動畫Source屬性的取值是PathAnimationSource.X,意味著這個動畫關注的是曲線上每一點橫坐標的變化。如果路徑動畫Source屬性的取值是PathAnimationSource.Y,意味著這個動畫關注的是曲線上每一點縱坐標的變化;如果路徑動畫的Source屬性取值是PathAnimationSource.Angle,意味著這個動畫關注的是曲線上每一點切線方向的變化。
下面這個例子講的是讓一個Button沿著一條貝塞爾曲線做波浪運動。程序的XAML代碼如下:
[html] view plain copy
- <Window x:Class="WpfApplication1.Window63"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="Window63" Height="314" Width="517">
- <Grid x:Name="layoutRoot">
- <Grid.Resources>
- <!--移動路徑-->
- <PathGeometry x:Key="movePath" Figures="M 0,50 C 300,-100 300,400 600,120"></PathGeometry>
- </Grid.Resources>
- <Button Content="Move" Width="80" Height="80" HorizontalAlignment="Left" VerticalAlignment="Top" Click="Button_Click">
- <Button.RenderTransform>
- <TranslateTransform X="0" Y="0" x:Name="tt"></TranslateTransform>
- </Button.RenderTransform>
- </Button>
- </Grid>
- </Window>
Button的Click事件處理代碼如下:
[csharp] view plain copy
- private void Button_Click(object sender, RoutedEventArgs e)
- {
- //從XAML代碼中獲取移動路徑數據
- PathGeometry pg = this.layoutRoot.FindResource("movePath") as PathGeometry;
- Duration duration = new Duration(TimeSpan.FromMilliseconds(600));
-
- //創建動畫
- DoubleAnimationUsingPath dpX = new DoubleAnimationUsingPath();
- dpX.Duration = duration;
- dpX.PathGeometry = pg;
- dpX.Source = PathAnimationSource.X;
-
- DoubleAnimationUsingPath dpY = new DoubleAnimationUsingPath();
- dpY.Duration = duration;
- dpY.PathGeometry = pg;
- dpY.Source = PathAnimationSource.Y;
-
- //執行動畫
- this.tt.BeginAnimation(TranslateTransform.XProperty,dpX);
- this.tt.BeginAnimation(TranslateTransform.YProperty,dpY);
-
- }
感興趣的話,黑可以為動畫添加自動返回和循環控制代碼:
[csharp] view plain copy
- dpX.AutoReverse = true;
- dpX.RepeatBehavior = RepeatBehavior.Forever;
- dpY.AutoReverse = true;
- dpY.RepeatBehavior = RepeatBehavior.Forever;
程序運行的效果如下圖:
1.4.2 場景
場景,StroyBoard就是並行執行一組動畫(前面講述的關鍵幀動畫則是串行的執行一組動畫)。
如果你是一位導演,當你對照劇本構思一個場景的時候腦子裡一定想的是應該有多少個演員參加到了這個場景、它們都是什麼演員、主角/配角/群眾演員分別什麼時候到場、每個演員該說什麼?做什麼?... ...演員具體用誰?由場景的需要來定。到時候開機的時候,一聲令下,所有演員都會按照預先分配好的腳本進行表演,一個影視片段就算錄成了。
設計WPF的場景時情況也差不多,先是把一組獨立的動畫組織在一個StoryBoard元素中、安排好它們的協作關系,然後指定哪個動畫由哪個UI元素,哪個屬性負責完成。StoryBoard設計好後,你可以為它選擇一個恰當的觸發時機,比如按鈕按下時或者下載開始時。一旦觸發條件被滿足,動畫場景就會開始執行,用戶就會看到執行效果。
下面是一個SotryBoard例子。程序的XAML代碼如下:
[html] view plain copy
- <Window x:Class="WpfApplication1.Window64"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="Window64" Height="159" Width="461">
- <Grid>
- <Grid.RowDefinitions>
- <RowDefinition Height="38" />
- <RowDefinition Height="38" />
- <RowDefinition Height="38" />
- </Grid.RowDefinitions>
- <Grid.ColumnDefinitions>
- <ColumnDefinition />
- <ColumnDefinition Width="60" />
- </Grid.ColumnDefinitions>
- <!--跑道(紅)-->
- <Border Grid.Row="0" BorderBrush="Gray" BorderThickness="1">
- <Ellipse Width="36" Height="36" Fill="Red" HorizontalAlignment="Left" x:Name="ballR">
- <Ellipse.RenderTransform>
- <TranslateTransform X="0" Y="0" x:Name="ttR">
- </TranslateTransform>
- </Ellipse.RenderTransform>
- </Ellipse>
- </Border>
- <!--跑道(綠)-->
- <Border Grid.Row="1" BorderBrush="Gray" BorderThickness="1,0,1,1">
- <Ellipse Width="36" Height="36" Fill="Green" HorizontalAlignment="Left" x:Name="ballG">
- <Ellipse.RenderTransform>
- <TranslateTransform X="0" Y="0" x:Name="ttG">
- </TranslateTransform>
- </Ellipse.RenderTransform>
- </Ellipse>
- </Border>
- <!--跑道(藍)-->
- <Border Grid.Row="2" BorderBrush="Gray" BorderThickness="1,0,1,1">
- <Ellipse Width="36" Height="36" Fill="Blue" HorizontalAlignment="Left" x:Name="ballB">
- <Ellipse.RenderTransform>
- <TranslateTransform X="0" Y="0" x:Name="ttB">
- </TranslateTransform>
- </Ellipse.RenderTransform>
- </Ellipse>
- </Border>
- <!--按鈕-->
- <Button Content="Go" Grid.RowSpan="3" Grid.Column="1" Click="Button_Click"></Button>
- </Grid>
- </Window>
程序的UI效果圖如下圖,單擊按鈕後,三個小球分別在不同的時間開始向右以不同的速度移動。
Button的事件處理器代碼如下:
[csharp] view plain copy
- private void Button_Click(object sender, RoutedEventArgs e)
- {
- Duration duration = new Duration(TimeSpan.FromMilliseconds(600));
-
- //紅色小球勻速運動
- DoubleAnimation daRx = new DoubleAnimation();
- daRx.Duration = duration;
- daRx.To = 400;
-
- //綠色小球做變速運動
- DoubleAnimationUsingKeyFrames dakGx = new DoubleAnimationUsingKeyFrames();
- dakGx.Duration = duration;
- SplineDoubleKeyFrame kfg = new SplineDoubleKeyFrame(400,KeyTime.FromPercent(1));
- kfg.KeySpline = new KeySpline(1,0,0,1);
- dakGx.KeyFrames.Add(kfg);
-
- //藍色小球變速運動
- DoubleAnimationUsingKeyFrames dakBx = new DoubleAnimationUsingKeyFrames();
- dakBx.Duration = duration;
- SplineDoubleKeyFrame kfb = new SplineDoubleKeyFrame(400,KeyTime.FromPercent(1));
- kfb.KeySpline = new KeySpline(0,1,1,0);
- dakBx.KeyFrames.Add(kfb);
-
- //創建場景
- Storyboard storyBoard = new Storyboard();
- Storyboard.SetTargetName(daRx,"ttR");
- Storyboard.SetTargetProperty(daRx, new PropertyPath(TranslateTransform.XProperty));
-
- Storyboard.SetTargetName(dakGx, "ttG");
- Storyboard.SetTargetProperty(dakGx, new PropertyPath(TranslateTransform.XProperty));
-
- Storyboard.SetTargetName(dakBx, "ttB");
- Storyboard.SetTargetProperty(dakBx, new PropertyPath(TranslateTransform.XProperty));
-
- storyBoard.Duration = duration;
- storyBoard.Children.Add(daRx);
- storyBoard.Children.Add(dakBx);
- storyBoard.Children.Add(dakGx);
-
- storyBoard.Begin(this);
- storyBoard.Completed += (a, b) => { MessageBox.Show(ttR.X.ToString()); };
- }
毋庸置疑,使用C#代碼實現StoryBoard非常的復雜,出了拿來研究或者遇到非要使用C#動態代碼創建StoryBoard的情況,不然我們都是在XAML裡面創建StoryBoard的。StoryBoard一般放在UI元素的Trigger裡,Trigger在觸發時會執行<BeginStoryBoard>標簽中的SotryBoard實例:
[html] view plain copy
- <!--按鈕-->
- <Button Content="Go" Grid.RowSpan="3" Grid.Column="1" Click="Button_Click">
- <Button.Triggers>
- <EventTrigger RoutedEvent="Button.Click">
- <BeginStoryboard>
- <Storyboard Duration="0:0:0.6">
- <!--紅色小球動畫-->
- <DoubleAnimation Duration="0:0:0.6" To="400" Storyboard.TargetName="ttR" Storyboard.TargetProperty="X">
-
- </DoubleAnimation>
- <!--綠色小球動畫-->
- <DoubleAnimationUsingKeyFrames Duration="0:0:0.6" Storyboard.TargetProperty="X" Storyboard.TargetName="ttG">
- <SplineDoubleKeyFrame KeyTime="0:0:0.6" Value="400" KeySpline="1,0,0,1"></SplineDoubleKeyFrame>
- </DoubleAnimationUsingKeyFrames>
- <!--藍色小球動畫-->
- <DoubleAnimationUsingKeyFrames Duration="0:0:0.6" Storyboard.TargetName="ttB" Storyboard.TargetProperty="X">
- <SplineDoubleKeyFrame KeyTime="0:0:0.6" Value="400" KeySpline="0,1,1,0"></SplineDoubleKeyFrame>
- </DoubleAnimationUsingKeyFrames>
- </Storyboard>
- </BeginStoryboard>
- </EventTrigger>
- </Button.Triggers>
- </Button>
除了為Button添加了Trigger並去掉對Click事件的訂閱之外,XAML代碼的其它部分不做任何改動。可以看到,XAML代碼編寫動畫比C#代碼簡潔了很多-----Blend生成的StoryBoard代碼與之非常類似。
轉載請注明出處:http://blog.csdn.net/fwj380891124