8.1变化特效 当使用Canvas布局时,可以使用Canvas.Left和Canvas.Top属性改变一个对象相对Canvas的位置,当使用Grid或StackPanel布局时,可以使用Margin属性声明元素的各方向间距,然而在这些属性中并不包括直接去改变某个Win10UI对象形状的方法,变化的实现会依赖于RenderTransform类,RenderTransform包含的变换属性成员就是专门来改变Win10UI对象形状的,它可以实现对元素拉伸,旋转,扭曲等效果,同时变换特效也常用于辅助产生各种动画效果. 8.11.变换的原理二维变换矩阵 变换定义如何将一个坐标空间中的点映射或变化到另一个坐标空间,在Windows 10里通过仿射变换实现. 仿射变换可以理解为对坐标进行放缩,旋转,平移后取得新坐标的值,仿射变换可以让UI元素产生视觉的旋转,它的原理并不是让UI元素的位置变化,而是变化平面X,Y的坐标系,间接的让UI元素的坐标发生转变,而如何让坐标系的旋转精确的控制UI元素的旋转,这就是仿射变换矩阵的作用了. Windows10变换映射是由变换Matrix来描述,Matrix是表示用于二维空间变换的3X3仿射变换矩阵,3X3矩阵用于在二维平面中进行变换.通过让仿射变换矩阵相乘可形成任意数目的线性变换,例如先旋转扭曲,再平移,仿射矩阵变换用于操作二维平面中的对象或坐标系,由于仿射变换时,平行的边依然平行,所以我们无法对一个矩形的位图进行随意变换,比如无法拉伸一个角,也无法把它变成梯形,仿射变换矩阵的最后一列等于0,0,1, 因此只需指定前两列的成员.矢量用行矢量表示 TranslateTransform 能够让某对象的位置发生平移变化 RotateTransform 能够让某对象产生旋转变化,根据中心点进行顺时针旋转或逆时针旋转 ScaleTransform 能够让某对象产生缩放变化 SkewTransform 能够让某对象产生扭曲变化 TransformGroup 能够让某对象缩放,旋转,扭曲等变化效果合并起来使用. MatrixTransform 能够让某对象通过矩阵算法实现更为复杂的变形 变换元素包括平移变换,旋转变换,缩放变换,扭曲变换,矩形变换和组合变换元素,变换特效常用于在不改变自身的情况下,使对象产生变形效果,所以变换元素常辅助产生win10中的各种动画效果 8.12.平移变换 TranslateTransform 能够让某对象的位置发生平移变化,按指定的X和Y移动元素,TranslateTransform类对移动不支持绝对定位的面板内的元素特别有用. 通过将TranslateTransform应用到元素的RenderTransform属性,可以移动StackPanel内的元素,使用TranslateTransform的X属性指定将元素沿X轴移动的量,使用Y属性指定将元素沿Y轴移动的量,最后将TranslateTransform应用于元素的RenderTransform属性.
1 2 3 4 5 6 <Rectangle Height="50" Width="50" Fill="#CCCCCCFF" Stroke="Blue" StrokeThickness="2" Canvas.Left="100" Canvas.Top="100" > <Rectangle.RenderTransform> <TranslateTransform X="50" Y="50" /> </Rectangle.RenderTransform> </Rectangle> </PivotItem>
8.13.旋转变换(RotateTransform) RotateTransform:能够让某对象产生旋转变化,根据中心点进行顺时针旋转或逆时针旋转,按指定的Angle旋转元素,RotateTransform围绕点CenterX和CenterY将对象旋转指定的Angle.在使用RotateTransform时,请注意变换将围绕点(0,0)旋转某个特定对象的坐标系.因此,根据对象的位置,对象可能不会就地围绕其中心旋转.例如,如果对象位于X轴上距0为200个单位的位置,旋转30°可以让该对象沿着原点为圆心,以200为半径所画的圆摆动30°.若要就地旋转某个对象,请将RotateTransform的CenterX和CenterY设置为该对象的旋转中心. 以Polyline对象的左上角为旋转点将其旋转了45°.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <Canvas height="200" Width="200" > <!--以Polyline 对象的左上角(0,0)为旋转点将其旋转了45°--> <Polyline Points="25,25,0,50,25,75,50,50,25,25,25,0" Stroke="Blue" StrokeThickness="10" Canvas.Left="75" Canvas.Top="50" > <Polyline.RenderTransform> <RotateTransform CenterX="0" CenterY="0" Angle="45" /> </Polyline.RenderTransform> </Canvas> <Canvas Height="200" Width="200" > <Polyline Points="25,25,0,50,25,75,50,50,25,25,25,0" Stroke="Blue" StrokeThickness="10" Canvas.Left="75" Canvas.Top="50" > <Polyline.RenderTransform> <RotateTransform CenterX="25" CenterY="50" Angle="45" /> </Polyline.RenderTransform> </Polyline> </Canvas>
8.14.缩放变换(ScaleTransform) ScaleTransform 能够让某对象产生缩放变化,按指定的ScaleX和ScaleY量按比例缩放元素.使用ScaleX和ScaleY量按比例缩放元素.使用ScaleX和ScaleY属性可以按照指定的系数调整元素的大小.例如,ScaleX值为1.5时,会将元素拉伸到其原始宽度的150%.ScaleY值为0.5时,会将元素的高度缩小50%.使用CenterX和CenterY属性可以指定缩放操作的中心点,默认情况下,ScaleTransform的中心点是(0,0),该点与矩形的左上角相对应,这会导致该元素移动并使其看上去更大,原因是当应用Transform时,对象所在的坐标空间会改变. 使用ScaleTransform将长和宽均为50的Rectangle尺寸放大一倍,对于CenterX和CenterY来说,ScaleTransform的值均为0.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <Canvas Height="200" Width="200" > <Rectangle Height="50" Width="50" Fill="#CCCCCCFF" Stroke="Blue" StrokeThickness="2" Canvas.Left="100" Canvas.Top="100" > <Rectangle.RenderTransform> <ScaleTransform CenterX="0" CenterY="0" ScaleX="2" ScaleY="2" /> </Rectangle.RenderTransform> </Rectangle> <Rectangle Height="50" Width="50" Fill="#CCCCCCFF" Canvas.Left="100" Canvas.Top="100" Stroke="Blue" StrokeThickness="2" > <Rectangle.RenderTransform> <ScaleTransform CenterX="25" CenterY="25" ScaleX="2" ScaleY="2" /> </Rectangle.RenderTransform> </Rectangle> </Canvas>
8.15.扭曲变换(SkewTransform) SkewTransform:能够让某对象产生扭曲变化,按指定的AngleX和AngleY量扭曲元素.扭曲是一种以非均匀方式拉伸坐标空间的变换.SkewTransform的一个典型用法是在二维对象中模拟三维深度.使用CenterX和CenterY属性可以指定SkewTransform的中心点,使用AngleX和AngleY属性可以指定X轴和Y轴的扭曲角度. 使当前坐标系沿着这些轴扭曲,若要预测扭曲变换的效果,请考虑AngleX相对于原始坐标系扭曲X轴的值,因此,如果AngleY为30,则会将该形状的Y轴值从原点扭曲30°.在X或Y轴中将坐标系变换30°效果不相同.
1 2 3 4 5 6 <Rectangle height="50" width="50" Fill="#CCCCCCFF" Stroke="Blue" Strokethickness="2" Canvas.Left="100" Canvas.Top="100" > <Rectangle.RenderTransform> <SkewTransform CenterX="0" CenterY="0" AngleX="45" AngleY="0" /> </Rectangle.RenderTransform> </Rectangle>
8.16.组合变换(TransformGroup) TransformGroup:能够让某对象的缩放,扭曲等变化效果合并起来使用,将多个TransformGroup对象组合为可以随后应用于变换属性的单一Transform,在复合变换中,单个变换的顺序非常重要.例如,依次旋转,缩放,平移与依次平移,旋转和缩放得到的结果不同,造成顺序很重要的一个原因就是像旋转和缩放这样的变换是针对坐标系的原点进行的,缩放以原点为中心的对象与缩放已离开原点的对象所得到的结果不同.同样,旋转以原点为中心的对象与旋转已离开原点的对象所得到的结果也不同.
1 2 3 4 5 6 7 8 <Button RenderTransformOrigin="0.5,0.5" HorizontalAlignment="Center" >Click <Button.RenderTransform> <TransformGroup> <ScaleTransform ScaleY="3" /> <RotateTransform Angle="45" /> </TransformGroup> </Button.RenderTransform> </Button>
8.17.矩阵变换(MatrixTransform) MatrixTransform是通过矩阵演算的方式来计算变换后的坐标
1 2 3 4 5 6 7 8 9 10 11 <MatrixTransform Matrix="M11,M12,M21,M22,OffsetX,OffsetY" /> <Button MinWidth="100" >Click <Button.RenderTransform> <MatrixTransform x:Name="myMatrixTransform" > <MatrixTransform.Matrix> <Matrix OffsetX="10" OffsetY="100" M11="3" M12="2" /> </MatrixTransform.Matrix> </MatrixTransform> </Button.RenderTransform> </Button>
8.2.三维特效 Win10UI元素的三维特效是通过UIElement的PlaneProjection属性来进行设置的. 8.22.三维旋转 PlaneProjection三维旋转相关的属性 1.CenterOfRotationX/CenterOfRotationY/CenterOfRotatinZ.表示旋转中心X轴/Y轴/Z轴坐标 可以通过使用这三个属性,设置三维旋转的旋转中心,默认情况下,旋转轴直接穿过对象的中心,这表示对象围绕其中心旋转.CenterOfRotationX和CenterOfRotationY的默认值为0.5,而CenterOfRotationZ的默认值为0. CenterOfRotationX取值0表示UI元素最左边的边缘,1是UI元素最右边的边缘. CenterOfRotationY取值0表示UI元素最上边的边缘,1是UI元素最上边的边缘. CenterOfRotationZ取值为负表示将旋转中心移到该对象后面,取值为正数表示将旋转中心移到该对象上方. 2.RotationX/RotationY/RotationZ表示沿X/Y/Z轴旋转的角度. 默认值都是0 可以超过360.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 <Grid x:Name="SanWeiContentPanel" Margin="12,0,12,0" > <Grid.RowDefinitions> <RowDefinition Height="*" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <!--旋转的对象--> <TextBlock Grid.Row="0" Text="3D" FontSize="120" Foreground="Red" HorizontalAlignment="Center" VerticalAlignment="Center" > <TextBlock.Projection> <PlaneProjection x:Name="planeProjection" /> </TextBlock.Projection> </TextBlock> <StackPanel Grid.Row="1" > <StackPanel Orientation="Horizontal" > <RadioButton x:Name="rotationRadioButton" Content="Rotation" Checked="rotationRadioButton_Checked" /> <RadioButton x:Name="centerOfRotationRadioButton" Content="CenterOfRotation" Checked="centerOfRotationRadioButton_Checked" /> </StackPanel> <TextBlock x:Name="infoTextBlock" TextWrapping="Wrap" /> <TextBlock x:Name="xTextBlock" Text="沿着X轴旋转" /> <Slider x:Name="xSlider" Minimum="0" Maximum="100" ValueChanged="xSlider_ValueChanged" /> <TextBlock x:Name="yTextBlock" Text="沿着Y轴旋转" /> <Slider x:Name="ySlider" Minimum="0" Maximum="100" ValueChanged="ySlider_ValueChanged" /> <TextBlock x:Name="zTextBlock" Text="沿着Z轴旋转" /> <Slider x:Name="zSlider" Minimum="0" Maximum="100" ValueChanged="zSlider_ValueChanged" /> </StackPanel> </Grid> //X滑块的滑动事件 private void xSlider_ValueChanged(object sender, RangeBaseValueChangedEventArgs e) { if (centerOfRotationRadioButton.IsChecked==false ) { //Slider控制的值范围是0-100,所以旋转的角度控制在0-360 double xValue = e.NewValue * 360 / 100; planeProjection.RotationX = xValue; } else { //中心点的坐标控制在0-1的值范围中变动 planeProjection.CenterOfRotationX = e.NewValue / 100; } } private void ySlider_ValueChanged(object sender, RangeBaseValueChangedEventArgs e) { if (centerOfRotationRadioButton.IsChecked==false ) { double yValue = e.NewValue * 360 / 100; planeProjection.RotationY = yValue; } else { planeProjection.CenterOfRotationY = e.NewValue / 100; } } private void zSlider_ValueChanged(object sender, RangeBaseValueChangedEventArgs e) { if (centerOfRotationRadioButton.IsChecked==false ) { double zValue = e.NewValue * 360 / 100; planeProjection.RotationZ = zValue; } else { planeProjection.CenterOfRotationZ = e.NewValue / 100; } }
8.23.三维平移 设置UI元素平移的距离的属性分为两种类型,一种是以屏幕为参考对象来定义坐标轴,另外一种类型是以UI对象本身作为参考对象来定义坐标轴,如果这个UI对象本身并没有做旋转相关的特效,这两种类型的属性所显示的效果是一样的,当UI对象应用了一些旋转特效,这两种类型还是有较大的区别的. GlobalOffsetX/GlobalOffsetY/GlobalOffsetZ表示相对于屏幕沿X轴/Y轴/Z轴平移对象,LocalOffsetX/LocalOffsetY/LocalOffsetZ表示沿对象旋转后对象平面的X/Y/Z轴平移对象. 下面的示例把TextBlock控件沿着X轴旋转45°,然后通过Slider控件来控制修改TextBlock控件相对于屏幕沿X/Y/Z平移的特效效果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 <PivotItem Header="SanweiPingyi" > <Grid x:Name="SanweiContentPanel" Margin="12,0,12,0" > <Grid.RowDefinitions> <RowDefinition Height="*" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <!--平移的对象--> <TextBlock Grid.Row="0" Text="3D" FontSize="120" Foreground="red" HorizontalAlignment="Center" VerticalAlignment="Center" > <TextBlock.Projection> <PlaneProjection x:Name="pingyiProjection" RotationX="45" /> </TextBlock.Projection> </TextBlock> <StackPanel Grid.Row="1" > <StackPanel Orientation="Horizontal" > <RadioButton x:Name="globalRadioButton" Content="GlobalOffset" Checked="rotationRadioButton_Checked" /> <RadioButton x:Name="localRadioButton" Content="LocalOffset" Checked="centerOfRotationRadioButton_Checked" /> </StackPanel> <TextBlock x:Name="infoTextBlock1" TextWrapping="Wrap" /> <TextBlock x:Name="xTextBlock1" Text="沿着X轴旋转" /> <Slider x:Name="xSlider1" Minimum="0" Maximum="100" ValueChanged="xSlider_ValueChanged1" /> <TextBlock x:Name="yTextBlock1" Text="沿着Y轴旋转" /> <Slider x:Name="ySlider1" Minimum="0" Maximum="100" ValueChanged="ySlider_ValueChanged1" /> <TextBlock x:Name="zTextBlock1" Text="沿着Z轴旋转" /> <Slider x:Name="zSlider1" Minimum="0" Maximum="100" ValueChanged="zSlider_ValueChanged1" /> </StackPanel> </Grid> </PivotItem> /// <summary> /// 三维平移 z轴移动的距离 /// </summary> /// <param name="sender" ></param> /// <param name="e" ></param> private void zSlider_ValueChanged1(object sender, RangeBaseValueChangedEventArgs e) { if (globalRadioButton.IsChecked == true ) { pingyiProjection.GlobalOffsetZ = e.NewValue; } else { pingyiProjection.LocalOffsetZ= e.NewValue; } ShowCenterOfRotationValue(); } /// <summary> /// 展示Textblock控件当前各个位置平移的值 /// </summary> private void ShowCenterOfRotationValue () { infoTextBlock1.Text = $"GlobaloffsetX:{pingyiProjection.GlobalOffsetX}" + $"Y:{pingyiProjection.GlobalOffsetY} Z:{pingyiProjection.GlobalOffsetZ} LocalOffsetX:{pingyiProjection.LocalOffsetX} Y:{pingyiProjection.LocalOffsetY} Z:{pingyiProjection.LocalOffsetZ}" ; } private void ySlider_ValueChanged1(object sender, RangeBaseValueChangedEventArgs e) { if (globalRadioButton.IsChecked == true ) { pingyiProjection.GlobalOffsetY= e.NewValue; } else { pingyiProjection.LocalOffsetY= e.NewValue; } ShowCenterOfRotationValue(); } private void xSlider_ValueChanged1(object sender, RangeBaseValueChangedEventArgs e) { if (globalRadioButton.IsChecked == true ) { pingyiProjection.GlobalOffsetX = e.NewValue; } else { pingyiProjection.LocalOffsetX = e.NewValue; } ShowCenterOfRotationValue(); }
8.24.用矩阵实现三维特效 用矩阵实现三维特效主要是依赖Matrix3DProjection和Matrix3D类型来实现,Matrix3DProjection是Matrix3D周围的包装类. Matrix3D类表示一个转换矩阵,该矩阵确定三维(3D)显示对象的位置和方向,该矩阵可以执行转换功能,包括平移(沿X,Y和Z轴重新定位),旋转和缩放(调整大小).Matrix3D类还可以执行透视投影,这会将3D坐标空间中的点映射到二维(2D)视图. Matrix3D类使用1个4x4正方形矩阵,即一个由四行和四列数字构成的表,其中容纳了用于转换的数据.矩阵的前三行容纳每个3D轴(X,Y,Z)的数据.平移信息位于最后一列中,方向和缩放数据位于前三个列中,缩放因子是位于前三个列中的对角数字,单一矩阵可以将多个转换组合在一起,并一次性对3D显示对象应用这些转换.例如,可以将一个矩阵应用于3D坐标,以便依次执行旋转和平移.如果三维特效之前的点坐标为(X,Y,Z),实现矩阵三维特效后的点坐标(X,Y
,Z)的计算公式如下: X
=M11X+M21 Y+M31Z+OffsetX. Y=M12*X+M22*Y+M32*Z+OffsetY. Z
=M13 X+M23Y+M33 Z+OffsetZ. Matrix3D的行向量语法 M11 M12 M13 M14 M21 M22 M23 M24 M31 M32 M33 M34 OffsetX OffsetY OffsetZ M44 为了更好理解矩阵,总共有五种不同类型的矩阵,4X4矩阵结构,单位矩阵,平移矩阵,缩放矩阵和旋转矩阵. 单位矩阵表示三维物体在世界空间内的初始位置.如果将一个矩阵乘以单位矩阵还会得到原来的矩阵,没有变换. 单位矩阵 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 缩放矩阵 表示用来对物体进行缩放变换,只需将三维物体乘以缩放矩阵就可以实现缩放的效果.在表中Sx,Sy,Sz分别表示沿着不同的方向进行缩放的比例. 缩放矩阵 Sx 0 0 0 0 Sy 0 0 0 0 Sz 0 0 0 0 1 //向X,Y,Z轴缩放
1 2 3 4 5 6 7 8 private Matrix3D CreateScaleTransform(double sx,double sy,double sz){ Matrix3D m=new Matrix3D(); m.M11=sx;m.M12=0.0;m.M13=0.0;m.M14=0.0; m.M21=0.0;m.M22=sy;m.M23=0.0;m.M24=0.0; m.M31=0.0;m.M32=0.0;m.M33=sz;m.M34=0.0; m.OffsetX=0.0;m.OffsetY=0.0;m.OffsetZ=0.0;m.M44=1.0; return m; }
平移矩阵表示用来对物体进行平移变换,只需将三维物体乘以平移矩阵就可以实现平移效果,在表中Tx,Ty和Tz分别表示沿着不同方向进行缩放的比例. 平移矩阵 1 0 0 0 0 1 0 0 0 0 1 0 Tx Ty Tz 1 //向x,y,z轴移动
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 /// <summary> /// 平移矩阵 向x,y,z轴移动 /// </summary> /// <param name="tx" ></param> /// <param name="ty" ></param> /// <param name="tz" ></param> /// <returns></returns> private Matrix3D TranslationTransform(double tx,double ty,double tz) { Matrix3D m = new Matrix3D(); m.M11 = 1.0;m.M12 = 0.0;m.M13 = 0.0;m.M14 = 0.0; m.M21 = 0.0;m.M22 = 1.0;m.M23 = 0.0;m.M24 = 0.0; m.M31 = 0.0;m.M32 = 0.0;m.M33 = 1.0;m.M34 = 0.0; m.OffsetX = tx;m.OffsetY = ty;m.OffsetZ = tz;m.M44 = 1.0; return m; }
沿着X,Y,Z轴旋转的矩阵分别如下所示 0表示旋转的角度 X轴旋转矩阵 1 0 0 0 0 cos0 sin0 0 0 -sin0 cos0 0 0 0 0 1 RotationX Y轴旋转矩阵 cos0 0 sin0 0 0 1 0 0 -sin0 0 cos0 0 0 0 0 1 RotationY Z轴旋转矩阵 cos0 sin0 0 0 -sin0 cos0 0 0 0 0 1 0 0 0 0 1 RotationZ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 ///旋转矩阵 /// <summary> /// 沿X轴移动 /// </summary> /// <param name="theta" ></param> /// <returns></returns> private Matrix3D RotateXTransform(double theta) { double sin=Math.Sin(theta); double cos=Math.Cos(theta); Matrix3D m=new Matrix3D(); m.M11 = 1.0;m.M12 = 0.0;m.M13 = 0.0;m.M14 = 0.0; m.M21 = 0.0;m.M22 = cos; m.M23 = sin;m.M24 = 0.0; m.M31= 0.0; m.M32 = -sin;m.M24 = cos;m.M34 = 0.0; m.OffsetX=0.0; m.OffsetY= 0.0;m.OffsetZ = 0.0;m.M44 = 1.0; return m; } /// <summary> /// 沿Y轴移动 /// </summary> /// <param name="theta" ></param> /// <returns></returns> private Matrix3D RotateYTransform(double theta) { double sin=Math.Sin(theta); double cos=Math.Cos(theta); Matrix3D m=new Matrix3D(); m.M11 = cos;m.M12 = 0.0;m.M13 = -sin;m.M14 = 0.0; m.M21 = 0.0;m.M22 = 1.0;m.M23 = 0.0;m.M24=0.0; m.M31 = sin;m.M32 = 1.0;m.M33 = cos;m.M34 = 0.0; m.OffsetX = 0.0;m.OffsetY = 0.0;m.OffsetY = 0.0;m.M44 = 1.0; return m; } /// <summary> /// 沿Z轴移动 /// </summary> /// <param name="theta" ></param> /// <returns></returns> private Matrix3D RotateZTransform(double theta) { double cos=Math.Cos(theta); double sin=Math.Sin(theta); Matrix3D m=new Matrix3D(); m.M11 = cos;m.M12 = sin;m.M13= 0.0;m.M14 = 0.0; m.M21 = -sin;m.M22 = cos;m.M23 = 0.0;m.M24 = 0.0; m.M31 = 0.0;m.M32 = 0.0;m.M33 = 1.0;m.M34= 0.0; m.OffsetX = 0;m.OffsetY = 0.0;m.OffsetZ = 0.0;m.M44= 1.0; return m; }
动画编程 实现动画的原始办法是配置一个定时器,然后根据定时器的频率循环的回调.在回调方法中,可以手动更新目标属性,根据时间的变化用数学计算来决定当前值,直到它达到最终值,这时就可以停止定时器,并移除时间处理程序. 9.12.动画的目标属性 普通的win10动画一定需要一个动画的目标属性,通过改变这个属性值从而实现动画的效果.可以把目标属性分为以下3类: 1.普通的UI控件属性,如宽度,高度等.这些属性和布局系统相关,当这些属性改变时会重新触发布局系统的工作. 2.变换特效属性 变换特效的属性可以作为动画的目标属性,实现从一个变换状态转换到另外一个变换状态.变换特效是不会重新触发UI布局系统的.要实现把矩形的宽度慢慢方法到两倍的动画,可以对Width属性进行动画处理,也可以对ScaleTransform对象的属性进行处理,这时候就应该选择用变换特效属性来实现,除非变换特效属性无法满足动画的实现效果,才去选择对普通的UI控件属性进行动画处理. 3.三维特效属性. 9.13.动画的类型 Win10主要提供两类动画–线性插值动画和关键帧动画,线性插值动画也称为From/To/By动画,用来反映某个对象在指定时间范围内持续渐变的过程.关键帧动画可以指定任意数量的目标值,并可以控制它们的内插方法.还有一种比较少用的动画——基于帧动画.通常一些复杂的动画才会使用基于帧动画. 基于帧动画 需要做的全部工作是响应静态的CompositionTarget.Rendering事件,触发该事件可以为每帧获取内容.线性插值动画和关键帧动画是要对UI元素的某个属性或某种变换变换进行动画清理的,也就是动态的在时间轴上改变UI元素的某个属性或者某种变换,所以也可以根据动画所要改变的对象来分为针对属性的动画和针对变换的动画,Storyboard类提供TargetName和TargetProperty附加属性,通过在动画上设置这些属性,将告诉动画对哪些内容进行处理.不过在动画以对象作为处理目标之前,必须使用x:Name属性为该对象提供一个名称,否则必须间接地以属性作为目标.针对属性地动画需要将TargetName和TargetProperty附加属性赋值为元素地名称和元素地属性名称,基于帧动画比较特殊,这是一种低级地动画处理方式.相当于每一帧地动画都需要通过事件来重绘界面. 9.2.线性插值动画 线性插值实际上就是通过给定两个关键帧图形线性地求出两帧之间地中间帧图形,这里的线性插值动画是把两个对应的开始值和结束值之间间隔划分,然后线性的实现等量递增或递减的效果.在Windows10中,线性插值动画表现为:界面上某个元素的某个属性在开始值和结束值之间递增或递减,比如淡入淡出效果,时钟转动. 9.21.动画的基本语法. Win10动画类位于System.Windows.Media.Animation命令空间下,Win10的线性插值动画和关键帧动画都是基于Timeline(时间线)的动画,所有的动画都是继承于Timeline类,Timeline用来表示动画的某一时刻或某段时间的范围.用来记录动画的状态,行为,顺序,起始位置和结束位置及动画持续时间. Storyboard是Win10动画的基本单元,派生于Timeline类,用来分配动画时间,可以使用同一个故事板对象产生一种或多种动画效果,并且允许控制动画的播放,暂停,停止及在何时何地播放,使用故事板时,必须指定TargetProperty(目标属性)和TargetName(目标名称)属性,这两个属性把故事板和所有产生的动画效果衔接起来,起到桥梁的作用.
1 2 3 4 5 6 <Storyboard x:Name="storyboard1" > <DoubleAnimation EnableDependentAnimation="True" Storyboard.TargetName="ellipse1" Storyboard.TargetProperty="Width" From="150" To="300" Duration="0:0:3" /> </Storyboard>
9.22.线性动画的基本语法 System.Windows.Media.Animation命名空间,并且都以Animation结尾,这些类主要有DoubleAnimation类,ColorAnimation类和PointAnimation类.这三个类分别对Double,Color和Point属性进行动画处理.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <StackPanel Margin="12,0,12,0" > <StackPanel.Resources> <Storyboard x:Name="myStoryboard" > <DoubleAnimation From="0" To="300" AutoReverse="True" RepeatBehavior="Forever" Duration="0:0:3" Storyboard.TargetName="rect" Storyboard.TargetProperty="Width" EnableDependentAnimation="True" /> </Storyboard> </StackPanel.Resources> <Rectangle x:Name="rect" Width="0" Fill="Red" Height="100" /> <Button Content="启动动画" Click="Button_Click_3" /> </StackPanel> /// <summary> /// 播放动画 /// </summary> /// <param name="sender" ></param> /// <param name="e" ></param> private void Button_Click_3(object sender, RoutedEventArgs e) { myStoryboard.Begin(); }
1.From属性 From值是Width属性的开始数值,如果多次单击,每次都会将Width重新设置为0,并重新开始动画. 每个依赖属性每次只能响应一个动画,如果开始第二个动画,第一个动画就会自动放弃,在许多情况下,可能不希望动画从最初的From值开始: 1.动画多次启动需要在上次基础上延续下去,需要创建一个能够被多次触发,并且逐次累加效果的动画,例如创建一个每次单击时都大一点的按钮. 2.创建相互重叠的动画,可以使用PointerEnter事件触发一个扩展按钮的动画,并使用PointerLeave事件触发一个将按钮缩小为原始尺寸的互补动画,如果连续快速的将鼠标多次移动到这种按钮上并移开,每个新动画就会打断上一个动画,导致按钮跳回到From属性设置的值. 如果当矩形正在增大时单击按钮,按钮宽度就会被重新设置为0个像素.
1 2 3 <Storyboard x:Name="myStoryboard" > <DoubleAnimation To="300" EnableDependentAnimation="True" AutoReserve="True" RepeatBehavior="Forever" Duration="0:0:3" Storyboard.TargetName="rect" Storyboard.TargetProperty="Width" /> </Storyboard>
2.To属性 3.By属性 不使用To属性,也可以使用By属性,By属性用于创建通过设置变化的数量改变数值的动画,而不是通过设置达到的目标改变数值. From 0By100 4.Duration属性 它是在动画开始和结束之间的时间间隔,没有指定,默认1秒 Duration类型还提供了两个特殊的不能通过Timespan对象表示的数值,Duration.Automatic和Duration.Forever. 5.AutoReverse属性 指定时间线在到达其Duration的终点后是否倒退.如果将此动画属性设置为true,则动画在到达其Duration终点后将倒退,即从其终止值向其起始值反向播放,默认情况下,该属性为false. 6.RepeatBehavior属性 指定时间线播放次数,默认情况下,时间线重复次数为1.0,即播放一次时间线,根本不进行重复,RepeatBehavior属性的设置有三种语法: Forever:一直重复运行下去. 二是设置重复运行的次数,叫做迭代形式,迭代形式占位符是一个整数,用于指定动画应重复的次数.迭代次数后面总是跟一个小写的原义字符x.可以将它想象为一个乘法字符,3x表示3倍. 三是设置动画运行的时间跨度,这个时间跨度和Duration属性有很大区别,这个时间表示的是动画从运行到停止的时间,Duration属性的时间表示动画重复一次的时间.时间跨度的语法格式是”[天.]小时:分钟:秒[.秒的小数部分]”,方括号[]表示可选值,重复15秒可以设置RepeatBehavior=”0:0:15”.小时,分钟和数值可以是0-59中的任意整数,天的值可以很大,但其具有未指定的上限.秒的小数部分(包含小数点)的小数值必须介于0-1之间. 9.23.DoubleAnimation实现变换动画. 下面示例将Storyboard和DoubleAnimation与ScaleTransform一起使用,以便在动画运行时,Rectangle的高度慢慢向下伸长至原来的两倍.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <Canvas> <Canvas.Resources> <Storyboard x:Name="storyBoard" > <!--对ScaleTransform对象的ScaleY属性应用动画,表示沿着Y轴缩放倍数变化的动画--> <DoubleAnimation Storyboard.TargetName="scacleTransform" Storyboard.TargetProperty="ScaleY" From="1" To="2" Duration="0:0:3" RepeatBehavior="Forever" AutoReverse="True" > </DoubleAnimation> </Storyboard> </Canvas.Resources> <Rectangle x:Name="rectangle" Height="50" Width="50" Canvas.Left="75" Canvas.Top="75" Fill="Blue" > <Rectangle.RenderTransform> <!--<注意需要对ScaleTransform命名,否则无法定义动画的目标对象>--> <ScaleTransform x:Name="scaleTransform" > </ScaleTransform> </Rectangle.RenderTransform> </Rectangle> </Canvas> private void Page_Loaded(object sender, RoutedEventArgs e) { storyBoard.Begin(); }
9.24.ColorAnimation实现颜色渐变动画 ColorAnimation类是用于属性为Color类型的UI元素的线性插值动画的类型,与色调相关的渐变动画可以使用DoubleAnimation类来实现,通常使用UI元素的Fill属性,Background属性值等来实现对象的填充色调的变化效果.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <StackPanel> <StackPanel.Resources> <Storyboard x:Name="storybord1" > <ColorAnimation From="Red" To="Yellow" Storyboard.TargetName="button" Storyboard.TargetProperty="(Button.Background).(SolidColorBrush.Color)" Duration="0:0:5" > </ColorAnimation> </Storyboard> </StackPanel.Resources> <Button Content="开始动画" Margin="12" Click="Button_Click_4" HorizontalAlignment="Stretch" /> <Button Content="Test" x:Name="button" Margin="12" Click="button_Click_5" HorizontalAlignment="Stretch" /> </StackPanel> storybord1.Begin();
9.25.PointAnimation实现Path图形动画 PointAnimation类是用于属性为Point类型的UI元素的线性插值动画的类型,在两个Point值之间做线性内插动画处理,用于改变某些UI元素对象的X,Y值,如元素的Center属性.如果要对Path图形应用动画,动态地改变Path图形上的点,需要用PathGeometry的方式来创建Path图形. 我们用一个例子来演示如何实现Path图形的动画,这个动画的运行效果是一个四分之三的圆通过线性动画慢慢变成一个完整的圆,不断地重复这样的动画效果,这个Path圆形的构造原理是从圆最右边的点出发,使用BezierSegment画4条曲线和一条直线连接到中心点形成一个闭合的图形.对第4条曲线BezierSegment的终点Point3进行动画处理,让其从圆的最上边的点坐标运动到最右边的点坐标.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <StackPanel> <StackPanel.Resources> <Storyboard x:Name="storyboard1" > <PointAnimation From="50,0" To="100,50" Duration="0:0:3" Storyboard.TargetName="bezierSegment" Storyboard.TargetProperty="Point3" RepeatBehavior="5" EnableDependentAnimation="True" /> </Storyboard> </StackPanel.Resources> <Button Content="运行动画" Click="Button_Click_6" Margin="12" HorizontalAlignment="Stretch" /> <!--<Data属性由4个BezierSegment曲线和1条直线LineSegment组成>--> <Path Fill="#FF4080FF" HorizontalAlignment="Left" Height="100" Margin="162,164,0,0" Stretch="Fill" VerticalAlignment="Top" Width="100" > <Path.Data> <PathGeometry> <PathFigure StartPoint="100,50" > <BezierSegment Point1="100,77.6142" Point2="77.6142,100" Point3="50,100" /> <BezierSegment Point1="22.3858,100" Point2="0,77.6142" Point3="0,50" /> <BezierSegment Point1="0,22.3858" Point2="22.3858,0" Point3="50,0" /> <!--BezierSegment表示是圆右上的弧线,对其终点Point3进行动画处理--> <BezierSegment x:Name="bezierSegment" Point1="77.6142,0" Point2="100,22.3858" Point3="100,50" /> <LineSegment Point="50,50" /> </PathFigure> </PathGeometry> </Path.Data> </Path> </StackPanel>
9.3.关键帧动画 1.关键帧动画的概念 一个动画轨迹里有多个关键帧,每个关键帧具有自己的相关信息,每个关键帧还同时保存自己在整个动画轨迹里所处的时间点.在实际运行时,根据当前时间,通过对两个关键帧的插值可以得到当前帧.动画运行时,随着时间的变化,插值得到的当前帧也是变化的,从而产生了动画的效果,由于关键帧包括长度,颜色,位置等信息,所以可以实现运动动画,缩放动画,渐变动画,旋转动画以及混合动画. 2.关键帧动画与线性插值动画的区别 关键帧动画的目标值是使用关键帧对象进行描述的,因此称作”关键帧动画”,通过指定多个值来创建关键帧动画,关键帧还会启用不同的插入逻辑,每个插入逻辑根据动画类型作为不同的”KeyFrame”子类实现,确切的说,每个关键帧动画类型具有KeyFrame类的Discrete,Linear,Spline和Easing变体,用于指定其关键帧,例如,若要指定以Double为目标并使用关键帧的动画,则可声明具有DiscreteDoubleKeyFrame,LinearDoubleKeyFrame,SplineDoubleKeyFrame和EasingDoubleKeyFrame的关键帧.可以在一个KeyFrame集合中使用所有这些类型,用以更改每次新关键帧到达的插入. 3.关键帧动画需要注意的属性 对于插入行为,每个关键帧控制该插入,直至到达其KeyTime时间.其Value也会在该时间到达,如果有更多关键帧超出范围,则该值将称为序列中下一个关键帧的起始值. 关键帧动画的持续时间为隐式持续时间,它等于其任一关键帧中设置的最高KeyTime值.如果需要,可以设置一个显式Duration,但应注意该值不应小于关键帧中的KeyTime,否则将会截断部分动画,除了Duraiton,还可以在关键帧动画上设置基于Timeline的属性,因为关键帧动画类也派生自Timeline.这些属性主要有: 1.AutoReverse:在到达最后一个关键帧后,从结束位置开始反向重复帧.这使得动画的显示持续时间加倍. 2.BeginTime:延迟动画的起始部分.帧内KeyTime值的时间线在BeginTime到达前不开始计数, 因此不存在截断帧的风险. 3.FillBehavior:控制到达最后一帧发生的操作.FillBehavior不会对任何中间关键帧产生任何影响. 4.RepeatBehavior:如果该数不是时间线的隐式持续时间的整数倍数,则可能会截断关键帧序列中的部分动画. 4.关键帧动画的类别 关键帧动画分为线性关键帧,样条关键帧和离散关键帧三种类型.关键帧动画类属于System.Windows.Media.Animation命名空间.并遵守下列命名规定:<类型>AnimationUsingKeyFrame. 其中<类型>是该类进行动画处理的值的类型. 关键帧的分类 所属类型 对应的关键帧动画类 支持的动画过渡方法 Color ColorAnimationUsingKeyFrame 离散,线性,样条 Double DoubleAmimationUsingKeyFrame 离散,线性,样条 Point Object 离散 9.32.线性关键帧 线性关键帧动画是由许多比较短的段构成的动画.每段表示动画中的初始值,最终值成中间值,当运行动画时,它从一个值光滑的移动到另外一个值,使用线性过度,指定时间段内,动画的播放速度是固定的,比如,如果关键帧段在5秒内,从0过度到10.
1 2 3 4 <PointAnimation Storyboard.TargetName="myradialgradientbrush" Storyboard.TargetProperty="GradientOrigin" From="0.1,0.7" To="0.3,0.7" Duration="0:0:10" AutoReverse="True" RepeatBehavior="Forever" > </PointAnimation>
可以使用一个效果相同的PointAnimationUsingKeyFrames对象代替上面的PointAnimation对象.
1 2 3 4 5 <PointAnimationUsingKeyFrames Storyboard.TargetName="myradialgradientbrush" Storyboard.TargetProperty="GradientOrigin" AutoReverse="True" RepeatBehavior="Forever" > <LinearPointKeyFrame Value="0.1,0.7" KeyTime="0:0:0" /> <LinearPointKeyFrame Value="0.3,0.7" KeyTime="0:0:10" /> </PointAnimationUsingKeyFrames>
这个动画包含两个关键帧,当动画开始时第一个关键帧设置Point值(如果希望在RadialGradientBrush画刷中设置的当前值,可以省略这个关键帧).第二个关键帧定义结束值,这是10秒之后到达的数值,PointAnimationUsingKeyFrames对象执行线性插值,这样第一个关键帧平滑移动到第二个关键帧,就像PointAnimation对象使用From值和To值一样. 每个关键帧动画都使用自己的关键帧对象(LinearPointKeyFrame),对于大部分内容,这些类时相同的,它们包含一个保存目标值的Value属性和一个指示帧何时到达目标值的KeyTime属性,唯一的区别是Value属性的数据类型,在LinearPointKeyFrame类中是Point类型,在DoubleKeyFrame类中是double类型. 除了指定动画的Duration外,还需要指定向每个关键帧分配持续时间内的多长一段时间.你可以为动画的每个关键帧描述其KeyTime来实现此目的.每个关键帧的KeyTime都指定了该关键帧的结束时间.KeyTime属性并不能指定关键时间播放的长度,关键帧播放时间长度由关键帧的结束时间,前一个关键帧的结束时间以及动画的持续时间来确定.可以以时间值或百分比形式来指定关键时间,也可以将其指定为特殊值Uniform或Paced. 9.33.样条关键帧 为了使若干个关键帧间的动画连续流畅,经常采用样条关键帧插值法.具有二阶连续性,即C2连续性,在win10中,每个支持线性关键帧的类也支持样条关键帧,并且它们使用”Spline+数据类型+KeyFrame”形式进行命名. 和线性关键帧一样,样条关键帧使用插值从一个值平滑的移动到另外一个值,区别是每个样条关键帧都有一个KeySpline属性.可以使用该属性定义一个影响插值方式的三次贝塞尔样条. 样条关键帧使用的是三次方贝塞尔曲线来计算动画运动的轨迹.贝塞尔曲线的每一个顶点都有两个控制点,用于控制在该顶点两侧的曲线的弧度,它是应用于二维图形应用程序的数学曲线.曲线的定义有四个点,起始点,终止点,以及两个相互分离的中间点,滑动两个中间点,贝塞尔曲线的形状会发生变化,三次贝塞尔曲线,则需要一个起点,一个终点,两个控制点来控制曲线的形状. P0,P1,P2,P3四个点在平面或三维空间中定义了三次方贝塞尔曲线,曲线起始于P0,走向P1,并从P2的方向来到P3.曲线一般不会经过P1或P2,这两个点只是提供方向信息.P0和P1之间的距离,决定了曲线在趋进P3之前,走向P2方向的长度有多长,曲线的参数形式为 B(t)=P0(1-t)3+3P1t(1-t)2+3P2t2(1-t)+P3t3,t属于[0,1] 样条关键帧可用于达到更真实的记时效果,使用其他关键帧,可以指定一个Value和KeyTime,使用样条关键帧,还需要指定一个KeySpline.
1 <SplineDoubleKeyFrame Value="500" KeyTime="0:0:7" KeySpline="0.0,1.0 1.0,0.0" />
样条关键帧根据KeySpline属性的值在值之间创建可变的过度,KeySpline属性是从(0,0)延伸到(1,1)的贝塞尔曲线的两个控制点,用于控制在该顶点两侧的曲线的弧度,描述了动画的加速.第一个控制点控制贝塞尔曲线前半部分的曲线因子,第二个控制点控制贝塞尔线段后半部分的曲线因子.此属性基本上定义了一个时间关系间的函数,其中函数-时间图形采用贝塞尔曲线的形状,所得到的曲线是对该样条关键帧的更改速率所进行的描述.曲线陡度越大,关键帧更改其值的速度越快,曲线趋于平缓,关键帧更改其值的速度也趋于缓慢. 在XAML属性字符串中指定一个KeySpline值,该字符串具有四个以空格或逗号分隔的Double值,如KeySpline=”0.0,1.0 1.0,0.0”.这些值用作贝塞尔曲线的两个控制点X,Y对.X是时间,Y是对值的函数修饰符. 每个值应该始终介于0-1之间.控制点更改该曲线的形状,并因此会更改样条动画的函数随时间变化的行为. 每个控制点会影响控制样式动画速率的概念曲线的形状,同时更改(0,0)和(1,1)之间的线性进度,keySplines的语法必须指定且仅指定两个控制点,如果曲线只需要一个控制点,可以重复同一个控制点,如果不将控制点修改为KeySpline,则从(0,0)到(1,1)的直线是线性插入的时间函数的表示形式. 下面的示例,通过对比在Canvas控件上移动的两个矩形,演示了一个样条关键帧动画的运动轨迹和一个线性关键帧动画运行轨迹的对比.第一个矩形使用一个具有SplineDoubleKeyFrame对象的DoubleAnimationUsingKeyFrames动画来控制Canvas.Top属性使它从400到0按照样条关键帧的轨迹变化和使用一个具有LinearDoubleKeyFrame对象的DoubleAnimationUsingKeyFrames动画来控制Canvas.Left属性使它从0到400按照线性关键帧的轨迹变化,第二个矩形使用了两个具有DoubleAnimationUsingKeyFrames动画来控制Canvas.Top和Canvas.Left属性,使其匀速的从左下角向右上角运动,两个矩形同时到达目标位置(10秒之后),但是第一个矩形在其运动过程中会有明显的加速和减速,加速时会超过第二个矩形,而减速又会落后于第二个矩形.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 <Grid x:Name="LayoutRoot" Background="Transparent" > <Grid.Resources> <Storyboard x:Name="SplineKeyStoryBoard" > <!--对第一个矩形的Canvas.Top属性使用样条关键帧动画--> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="srect" Storyboard.TargetProperty="(Canvas.Top)" Duration="0:0:10" RepeatBehavior="Forever" > <SplineDoubleKeyFrame Value="0" KeyTime="0:0:10" KeySpline="0.0,1.0 1.0,0.0" /> </DoubleAnimationUsingKeyFrames> <!--对第一个矩形的Canvas.Left属性使用线性关键帧动画--> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="srect" Storyboard.TargetProperty="(Canvas.Left)" Duration="0:0:10" RepeatBehavior="Forever" > <LinearDoubleKeyFrame Value="400" KeyTime="0:0:10" /> </DoubleAnimationUsingKeyFrames> <!--对第二个矩形的Canvas.Top属性使用线性关键帧动画--> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="srect2" Storyboard.TargetProperty="(Canvas.Top)" Duration="0:0:10" RepeatBehavior="Forever" > <LinearDoubleKeyFrame Value="0" KeyTime="0:0:10" /> </DoubleAnimationUsingKeyFrames> <!--对第二个矩形的Canvas.Left属性使用线性关键帧动画--> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="srect2" Storyboard.TargetProperty="(Canvas.Left)" Duration="0:0:10" RepeatBehavior="Forever" > <LinearDoubleKeyFrame Value="400" KeyTime="0:0:10" /> </DoubleAnimationUsingKeyFrames> </Storyboard> </Grid.Resources> <Canvas Margin="12,0,12,0" > <!--第一个矩形的运动轨迹时采用样条关键帧的方式从左下角向右上角用变化的加速度运动--> <Rectangle x:Name="srect" Width="50" Height="50" Fill="Purple" Canvas.Top="400" Canvas.Left="0" /> <!--第二个矩形的运动轨迹是采用线性关键帧的方式从左下角向右上角匀速运动--> <Rectangle x:Name="srect2" Width="50" Height="50" Fill="Red" Canvas.Top="400" Canvas.Left="0" /> <Button Content="运行动画" Canvas.Top="500" Click="Button_Click_7" /> </Canvas> </Grid>
9.34.离散关键帧 离散关键帧就不会进行平滑的过度,而是当到达关键时间时,属性突然改变到新数值,离散关键帧根本不使用任何插入,使用离散关键帧,动画函数将从一个值跳到下一个没有内插的值.动画在持续时间结束之前不会更改其输出值,直到结束时间点,才会修改.也就是说在KeyTime到达后,只是简单的应用新的Value,离散关键帧可以比线性和样条支持更多类型属性进行动画处理.无法线性变化的属性使用离散关键帧. 线性关键帧类使用Linear+数据类型+KeyFrame的形式进行命名.离散关键帧类使用Discrete+数据类型+KeyFrame形式进行命名.当运行这个动画时,中心点会在合适的时间从一个位置跳到下一个位置,这是不平稳的动画效果.下面来看一个针对Point属性的离散关键帧动画,通过改变椭圆填充LinearGradientBrush画刷的开始点的值从而实现了椭圆的颜色渐变的变化效果.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <Grid> <Grid.Resources> <Storyboard x:Name="Discretestoryboard" > <PointAnimationUsingKeyFrames Storyboard.TargetName="myLinearGradientBrush" Storyboard.TargetProperty="StartPoint" EnableDependentAnimation="True" RepeatBehavior="Forever" > <DiscretePointKeyFrame Value="0.1,0.3" KeyTime="0:0:0" /> <DiscretePointKeyFrame Value="0.2,0.4" KeyTime="0:0:1" /> <DiscretePointKeyFrame Value="0.3,0.5" KeyTime="0:0:2" /> <DiscretePointKeyFrame Value="0.4,0.6" KeyTime="0:0:3" /> <DiscretePointKeyFrame Value="0.5,0.7" KeyTime="0:0:4" /> <DiscretePointKeyFrame Value="0.6,0.8" KeyTime="0:0:5" /> <DiscretePointKeyFrame Value="0.7,0.9" KeyTime="0:0:6" /> </PointAnimationUsingKeyFrames> </Storyboard> </Grid.Resources> <StackPanel> <Ellipse x:Name="ellipse" > <Ellipse.Fill> <LinearGradientBrush x:Name="myLinearGradientBrush" StartPoint="0,0" EndPoint="1,0" > <LinearGradientBrush.GradientStops> <GradientStop Color="White" Offset="0.001" /> <GradientStop Color="Blue" Offset="1" /> </LinearGradientBrush.GradientStops> </LinearGradientBrush> </Ellipse.Fill> </Ellipse> <Button Content="启动动画" Height="100" Click="Button_Click_8" /> </StackPanel> </Grid> private void Button_Click_8(object sender, RoutedEventArgs e) { Discretestoryboard.Begin(); }
有一种动画类型是可以将动画化的值应用于其类型不是Double,Point,Color的属性的唯一方法,它就是关键帧动画ObjectAnimationUsingKeyFrames.使用Object值的动画非常不同.因为不可能在帧之间插入值.当帧的KeyTime到达时,动画化的值将立即设置为关键帧的Value中指定的值,由于没有任何插入,因此只有一种关键帧用于ObjectAnimationUsingKeyFrame.关键帧集合:DiscreteObjectKeyFrame.DiscreteObjectKeyFrame的Value通常使用属性元素语法设置.因为设置的对象值通常不可表示为字符串以采用属性语法填充Value.如果使用引用,例如StaticResource,则可以使用属性语法. 在使用的Button控件的默认样式里面可以发现,按钮的点击状态和不可用状态都使用了ObjectAnimationUsingKeyFrames的离散关键帧动画来改变按钮的背景画刷颜色.因为系统的背景资源、是SolidColorBrush对象,而不仅仅是Color值,但由于SolidColorBrush不是Double,Point或Color,因此必须使用ObjectAnimationUsingKeyFrames才能使用该资源. 另外,还可使用ObjectAnimationUsingKeyFrames来设置使用枚举值的属性的动画,如果把按钮的Disabled状态改成要隐藏按钮,可以获取Visibility枚举常量的Visibility属性,在这种情况下,可以使用属性语法设置该值,使用枚举值设置属性,例如Collapsed.
1 2 3 4 5 6 7 <VisualState x:Name="Disabled" > <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentContainer" Storyboard.TargetProperty="Visibility" > <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed" /> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState>
9.4.缓动函数动画 Window10内置了11种缓动函数动画:BackEase,BounceEase,CircleEase,CubicEase,ElasticEase,ExponentialEase,PowerEase,QuadraticEase,QuinticEase和SineEase. 有些缓动函数具有其自己的属性,例如BounceEase具有两个属性(Bounces和Bounciness),用于修改该特定的BounceEase随时间变化的函数行为,其他缓动函数(CubicEase)不具有除所有缓动函数共享的EasingMode属性之外的任何属性,并且始终产生相同的随时间变化的函数行为,根据你在具有多个属性的缓动函数上设置的属性,这些缓动函数中的某些函数会有些重叠,例如,QuadraticEase与其Power等于2的PowerEase完全相同.并且CircleEase基本上就是具有默认值的ExponentialEase. BackEase缓动函数是唯一的,因为它可以更改正常范围之外的值(在由From/To设置时)或关键帧的值,它通过更改相反方向的值启动动画,按照预期从正常的From/To行为开始,再次返回至From或起始值,然后按正常行为运行动画. 缓动函数可以以三种方式应用于动画: 1.通过在关键帧动画中使用缓动关键,使用EasingColorKeyFrame.EasingFunction,EasingDoubleKeyFrame,EasingFunction或EasingPointKeyFrame.EasingFunction. 2.通过在线性插值动画类型上设置EasingFunction属性,使用ColorAnimation.EasingFunction,DoubleAnimation.EasingFunction或PointAnimation.EasingFunction. 3.通过将GeneratedEasingFunction设置为VisualTransition的一部分,这种方式专用于定义控件的视觉状态. 9.42.BackEase动画 BackEase:在某一动画开始沿指示的路径进行动画处理前稍稍收回该动画的移动通过指定动画的EasingMode属性值,可以控制动画中Back行为发生的时间. 用于此动画的函数公式如下所示:f(t)=t3-ta sin(t*Π) a:amplitude EasingMode=”EaseIn” EasingMode=”EaseOut” EasingMode=”EaseInOut” 因为此动画导致值在前进前收回,所以动画可能会意外插入到负数中,当对不支持负数的属性进行动画处理时,可能会引发错误.如果将此动画应用到对象的Height(例如,对于EaseIn的EasingMode,应为0-200),则该动画将尝试对Height插入负数,从而引发错误. 下面用一个示例来演示BackEase的EaseInOut的动画效果,注意动画快要结束时可以发现第一个椭圆是先变大再变小,第二个椭圆的运动轨迹与上面的图形是相似的.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 <Grid Background="Transparent" > <Grid.Resources> <Storyboard x:Name="Easestoryboard" > <!--第一个椭圆的动画--> <DoubleAnimation From="1" To="2" Duration="0:0:3" Storyboard.TargetName="ellipselScaleTransform" Storyboard.TargetProperty="ScaleX" > <DoubleAnimation.EasingFunction> <BackEase Amplitude="0.3" EasingMode="EaseInOut" /> </DoubleAnimation.EasingFunction> </DoubleAnimation> <DoubleAnimation From="1" To="2" Duration="0:0:3" Storyboard.TargetName="ellipselScaleTransform" Storyboard.TargetProperty="ScaleY" > <DoubleAnimation.EasingFunction> <BackEase Amplitude="0.3" EasingMode="EaseInOut" /> </DoubleAnimation.EasingFunction> </DoubleAnimation> <!--第二个椭圆的动画--> <DoubleAnimation From="0" To="400" Duration="0:0:3" Storyboard.TargetName="ellipse4" Storyboard.TargetProperty="(Canvas.Left)" > </DoubleAnimation> <DoubleAnimation From="400" To="0" Duration="0:0:3" Storyboard.TargetName="ellipse4" Storyboard.TargetProperty="(Canvas.Top)" > <DoubleAnimation.EasingFunction> <BackEase Amplitude="0.3" EasingMode="EaseInOut" /> </DoubleAnimation.EasingFunction> </DoubleAnimation> </Storyboard> </Grid.Resources> <Canvas Margin="12,0,12,0" > <!--第一个椭圆展示了BackEase的放大动画效果--> <Ellipse Name="ellipse3" Width="80" Height="80" Fill="Blue" > <Ellipse.RenderTransform> <ScaleTransform x:Name="ellipselScaleTransform" > </ScaleTransform> </Ellipse.RenderTransform> </Ellipse> <!--第二个椭圆展示了BackEase的运动轨迹--> <Ellipse x:Name="ellipse4" Fill="Red" Width="80" Height="80" Canvas.Left="0" Canvas.Top="400" /> <Button Margin="0,500,0,0" Content="启动动画" Height="80" Click="Button_Click_10" /> </Canvas> </Grid> private void Button_Click_10(object sender, RoutedEventArgs e) { Easestoryboard.Begin(); }
9.43.BounceEase动画 BounceEase:创建弹跳效果.f(t)表示动画进度,t表示时间 EasingMode=”EaseIn” EasingMode=”EaseOut” EasingMode=”EaseInOut” 可以使用Bounces属性指定弹跳次数并使用Bounciness属性指定弹跳程度(弹性大小).Bounciness属性指定下一个反弹的幅度缩放,例如反弹度值2会使渐入中下一个反弹的幅度翻倍,并且会使渐出中下一个反弹的幅度减半. 下面用一个示例演示BounceEase的EaseOut的动画效果,第一个椭圆会有几次变大变小的效果,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 <Grid Background="Transparent" > <Grid.Resources> <Storyboard x:Name="Bouncestoryboard" > <!--第一个椭圆的动画--> <DoubleAnimation From="0" To="200" Duration="0:0:3" EnableDependentAnimation="True" Storyboard.TargetName="ellipse5" Storyboard.TargetProperty="Width" > <DoubleAnimation.EasingFunction> <BounceEase Bounces="2" EasingMode="EaseOut" Bounciness="2" /> </DoubleAnimation.EasingFunction> </DoubleAnimation> <DoubleAnimation From="80" To="200" Duration="0:0:3" EnableDependentAnimation="True" Storyboard.TargetName="ellipse5" Storyboard.TargetProperty="Height" > <DoubleAnimation.EasingFunction> <BounceEase Bounces="2" EasingMode="EaseOut" Bounciness="2" /> </DoubleAnimation.EasingFunction> </DoubleAnimation> <!--第二个椭圆的动画--> <DoubleAnimation From="0" To="400" Duration="0:0:3" Storyboard.TargetName="ellipse6" Storyboard.TargetProperty="(Canvas.Left)" > </DoubleAnimation> <DoubleAnimation From="400" To="0" Duration="0:0:3" Storyboard.TargetName="ellipse6" Storyboard.TargetProperty="(Canvas.Top)" > <DoubleAnimation.EasingFunction> <BounceEase Bounces="2" EasingMode="EaseOut" Bounciness="2" /> </DoubleAnimation.EasingFunction> </DoubleAnimation> </Storyboard> </Grid.Resources> <Canvas Margin="12,0,12,0" > <!--第一个椭圆展示了BounceEase的动画效果--> <Ellipse Name="ellipse5" Width="80" Height="80" Fill="Blue" /> <!--第二个椭圆展示了BounceEase的运动轨迹--> <Ellipse x:Name="ellipse6" Fill="Red" Width="80" Height="80" Canvas.Left="0" Canvas.Top="400" /> <Button Margin="0,500,0,0" Content="启动动画" Height="80" Click="Button_Click_11" /> </Canvas> </Grid> private void Button_Click_11(object sender, RoutedEventArgs e) { Bouncestoryboard.Begin(); }
9.44.CircleEase动画 CircleEase:创建使用循环函数加速和/或减速的动画.通过指定EasingMode,可以控制动画加速与减速. 用于此函数的函数公式如下: f(t)=1-(1-t2)平方根 t的有效值为-1<=t<=1,大于1的值将被计算为1,而小于-1的值则被计算为-1 这意味着此时间间隔之外的值的动画继续,但缓动函数在进入无效的域时暂停,并在离开无效的域时恢复. 下面用一个示例来演示一下CircleEase的EaseOut的动画效果,第一个椭圆的宽度会先快速的拉长然后再慢慢的拉长,第二个椭圆的运动轨迹与上面EaseOut的动画轨迹图形是相似的,类似沿着一个四分之一圆的轨迹进行运动.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <Grid Background="Transparent" > <Grid.Resources> <Storyboard x:Name="CircleEasestoryboard" > <!--第一个椭圆的动画--> <DoubleAnimation From="80" To="400" Duration="0:0:3" EnableDependentAnimation="True" Storyboard.TargetName="ellipse7" Storyboard.TargetProperty="Width" > <DoubleAnimation.EasingFunction> <CircleEase EasingMode="EaseOut" /> </DoubleAnimation.EasingFunction> </DoubleAnimation> <!--第二个椭圆的动画--> <DoubleAnimation From="0" To="400" Duration="0:0:3" Storyboard.TargetName="ellipse8" Storyboard.TargetProperty="(Canvas.Left)" > </DoubleAnimation> <DoubleAnimation From="400" To="0" Duration="0:0:3" Storyboard.TargetName="ellipse8" Storyboard.TargetProperty="(Canvas.Top)" > <DoubleAnimation.EasingFunction> <CircleEase EasingMode="EaseOut" /> </DoubleAnimation.EasingFunction> </DoubleAnimation> </Storyboard> </Grid.Resources> <Canvas Margin="12,0,12,0" > <!--第一个椭圆展示了CircleEase的动画效果--> <Ellipse x:Name="ellipse7" Width="80" Height="80" Fill="Blue" /> <!--第二个椭圆展示了CircleEase的运动轨迹--> <Ellipse x:Name="ellipse8" Fill="Red" Width="80" Height="80" Canvas.Left="0" Canvas.Top="400" /> <Button Margin="0,500,0,0" Content="启动动画" Height="80" Click="Button_Click_12" /> </Canvas> </Grid> private void Button_Click_12(object sender, RoutedEventArgs e) { CircleEasestoryboard.Begin(); }
9.45.CubicEase动画 CubicEase:创建使用公式f(t)=t3加速和/或减速的动画,通过指定EasingMode,可以控制动画加速或减速. 下面来演示CircleEase的EaseOut的动画效果,第一个椭圆展示出来的是一种很自然的翻转效果,像是上面被推了一下然后突然翻转过来到前端时间比较慢,后半段时间速度就快速增大,第二个椭圆的运动轨迹与上面EaseInOut动画轨迹是相似的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 <Grid Background="Transparent" > <Grid.Resources> <Storyboard x:Name="Cubicstoryboard" > <!--第一个椭圆的动画--> <DoubleAnimation From="0" To="180" Duration="0:0:3" Storyboard.TargetName="ellipselPlaneProjection" Storyboard.TargetProperty="RotationX" > <DoubleAnimation.EasingFunction> <CubicEase EasingMode="EaseInOut" /> </DoubleAnimation.EasingFunction> </DoubleAnimation> <!--第二个椭圆的动画--> <DoubleAnimation From="0" To="400" Duration="0:0:3" Storyboard.TargetName="ellipse10" Storyboard.TargetProperty="(Canvas.Left)" > </DoubleAnimation> <DoubleAnimation From="400" To="200" Duration="0:0:3" Storyboard.TargetName="ellipse10" Storyboard.TargetProperty="(Canvas.Top)" > <DoubleAnimation.EasingFunction> <CubicEase EasingMode="EaseInOut" /> </DoubleAnimation.EasingFunction> </DoubleAnimation> </Storyboard> </Grid.Resources> <Canvas Margin="12,0,12,0" > <!--第一个椭圆展示了CubicEase的动画效果--> <Ellipse x:Name="ellipse9" Width="200" Height="200" Fill="Blue" > <Ellipse.Projection> <PlaneProjection RotationX="0" x:Name="ellipselPlaneProjection" > </PlaneProjection> </Ellipse.Projection> </Ellipse> <!--第二个椭圆展示了CubicEase的运动轨迹--> <Ellipse x:Name="ellipse10" Fill="Red" Width="80" Height="80" Canvas.Left="0" Canvas.Top="400" /> <Button Margin="0,500,0,0" Content="启动动画" Height="80" Click="Button_Click_13" /> </Canvas> </Grid> private void Button_Click_13(object sender, RoutedEventArgs e) { Cubicstoryboard.Begin(); }
9.46.ElasticEase动画 ElasticEase:创建类似于弹簧在停止前来回震荡的动画,通过指定EasingMode属性值,可以控制动画中”弹簧”行为发生的时间. 可以使用Oscillations属性指定动画来回振动的次数,以及使用Springiness属性指定振动弹性的张紧程度, 因为此动画导致值来回振动,所以此动画可能会意外插入到负数中.当对不支持负数的属性进行动画处理时,这可能会引发错误. 下面用一个示例来演示ElasticEase的EaseOut的动画效果,第一个椭圆和线条展示出来的是用有弹性的绳子栓住吊球,然后往下掉落的这种自然效果,线条相当于是有弹性的绳子,而第一个椭圆相当于吊球,第二个椭圆模拟了上面EaseOut的动画轨迹图形
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 <Grid Background="Transparent" > <Grid.Resources> <Storyboard x:Name="Elasticstoryboard" > <!--线条的动画--> <DoubleAnimation From="0" To="400" Duration="0:0:3" EnableDependentAnimation="True" Storyboard.TargetName="line1" Storyboard.TargetProperty="Y2" > <DoubleAnimation.EasingFunction> <ElasticEase EasingMode="EaseOut" Oscillations="7" /> </DoubleAnimation.EasingFunction> </DoubleAnimation> <!--第一个椭圆的动画--> <DoubleAnimation From="0" To="400" Duration="0:0:3" Storyboard.TargetName="ellipse11" Storyboard.TargetProperty="(Canvas.Top)" > <DoubleAnimation.EasingFunction> <ElasticEase EasingMode="EaseOut" Oscillations="7" /> </DoubleAnimation.EasingFunction> </DoubleAnimation> <!--第二个椭圆的动画--> <DoubleAnimation From="0" To="400" Duration="0:0:3" Storyboard.TargetName="ellipse12" Storyboard.TargetProperty="(Canvas.Left)" > </DoubleAnimation> <DoubleAnimation From="400" To="200" Duration="0:0:3" Storyboard.TargetName="ellipse12" Storyboard.TargetProperty="(Canvas.Top)" > <DoubleAnimation.EasingFunction> <ElasticEase EasingMode="EaseOut" Oscillations="7" /> </DoubleAnimation.EasingFunction> </DoubleAnimation> </Storyboard> </Grid.Resources> <Canvas Margin="12,0,12,0" > <!--线条展示了ElasticEase的动画效果--> <Line x:Name="line1" X1="50" Y1="0" X2="50" Y2="50" Stroke="Blue" StrokeThickness="10" Fill="Blue" /> <!--第一个椭圆展示了ElasticEase的动画效果--> <Ellipse Name="ellipse11" Fill="Blue" Width="100" Height="100" /> <!--第二个椭圆展示了ElasticEase的运动轨迹--> <Ellipse x:Name="ellipse12" Fill="Red" Width="80" Height="80" Canvas.Left="0" Canvas.Top="400" /> <Button Margin="0,500,0,0" Content="启动动画" Height="80" Click="Button_Click_14" /> </Canvas> </Grid> private void Button_Click_14(object sender, RoutedEventArgs e) { Elasticstoryboard.Begin(); }
9.47.ExponentialEase动画 ExponentialEase:创建使用指数公式加速和/或减速的动画.通过指定EasingMode,可以控制动画加速与减速 下面来演示ExponentialEase的EaseIn的动画效果,第一个动画开始时非常缓慢,然后突然间加速冲到最底下, 第二个类似EaseIn动画轨迹图形
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <Grid Background="Transparent" > <Grid.Resources> <Storyboard x:Name="ExponentialStoryboard" > <!--第一个椭圆的动画--> <DoubleAnimation From="0" To="400" Duration="0:0:3" Storyboard.TargetName="ellipse13" Storyboard.TargetProperty="(Canvas.Top)" > <DoubleAnimation.EasingFunction> <ExponentialEase Exponent="10" EasingMode="EaseIn" /> </DoubleAnimation.EasingFunction> </DoubleAnimation> <!--第二个椭圆的动画--> <DoubleAnimation From="0" To="400" Duration="0:0:3" Storyboard.TargetName="ellipse14" Storyboard.TargetProperty="(Canvas.Left)" > </DoubleAnimation> <DoubleAnimation From="400" To="200" Duration="0:0:3" Storyboard.TargetName="ellipse14" Storyboard.TargetProperty="(Canvas.Top)" > <DoubleAnimation.EasingFunction> <ExponentialEase Exponent="10" EasingMode="EaseIn" /> </DoubleAnimation.EasingFunction> </DoubleAnimation> </Storyboard> </Grid.Resources> <Canvas Margin="12,0,12,0" > <!--第一个椭圆展示了ExponentialEase的动画效果--> <Ellipse x:Name="ellipse13" Width="50" Height="50" Fill="Blue" /> <!--第二个椭圆展示了ExponentialEase的运动轨迹--> <Ellipse x:Name="ellipse14" Fill="Red" Width="80" Height="80" Canvas.Left="0" Canvas.Top="400" /> <Button Margin="0,500,0,0" Content="启动动画" Height="80" Click="Button_Click_15" /> </Canvas> </Grid> private void Button_Click_15(object sender, RoutedEventArgs e) { ExponentialStoryboard.Begin(); }
9.48.PowerEase/QuadraticEase/QuarticEase/QuinticEase动画 PowerEase:创建使用公式f(t)=tp次方(其中,p等于Power属性)加速和/减速的动画 公式f(t)=tp次方,因此,PowerEase函数可由QuadraticEase(ft=t2),CubicEase(ft=t3),QuarticEase(ft=t4)和QuinticEase(ft=t5)替代. 如果希望用PowerEase函数来创建与QuadraticEase函数(ft=t2)创建的行为相同的行为,应将Power属性指定为2,QuadraticEase:创建使用ft=t2加速和/或减速的动画.QuarticEase:创建使用公式ft=t4加速和/或减速的动画 下面演示PowerEase的EaseIn的动画效果,通过Slider控件设置PowerEase的Power值,Power值越大第一个椭圆后面掉落的加速度就越大,加速的时间也越短.第二个椭圆的运动轨迹模拟了上面EaseIn的动画轨迹图形.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 <Grid Background="Transparent" > <Grid.Resources> <Storyboard x:Name="powerStoryboard" > <!--第一个椭圆的动画--> <DoubleAnimation From="0" To="400" Duration="0:0:3" Storyboard.TargetName="ellipse15" Storyboard.TargetProperty="(Canvas.Top)" > <DoubleAnimation.EasingFunction> <PowerEase EasingMode="EaseIn" x:Name="powerEase1" /> </DoubleAnimation.EasingFunction> </DoubleAnimation> <!--第二个椭圆的动画--> <DoubleAnimation From="0" To="400" Duration="0:0:3" Storyboard.TargetName="ellipse16" Storyboard.TargetProperty="(Canvas.Left)" > </DoubleAnimation> <DoubleAnimation From="400" To="200" Duration="0:0:3" Storyboard.TargetName="ellipse16" Storyboard.TargetProperty="(Canvas.Top)" > <DoubleAnimation.EasingFunction> <PowerEase EasingMode="EaseIn" x:Name="powerEase2" /> </DoubleAnimation.EasingFunction> </DoubleAnimation> </Storyboard> </Grid.Resources> <Canvas Margin="12,0,12,0" > <Ellipse Name="ellipse15" Width="50" Height="50" Fill="Blue" /> <Ellipse x:Name="ellipse16" Fill="Red" Width="80" Height="80" Canvas.Left="0" Canvas.Top="400" /> <Button Margin="0,500,0,0" Content="启动动画" Height="80" Click="Button_Click_16" /> <Slider x:Name="powerSlider" Margin="200,500,0,0" Width="200" Background="Red" Value="50" Maximum="100" Minimum="0" /> </Canvas> </Grid> private void Button_Click_16(object sender, RoutedEventArgs e) { powerEase1.Power = powerSlider.Value; powerEase2.Power= powerSlider.Value; powerStoryboard.Begin(); }
9.49.SineEase动画 SineEase:创建使用正弦公式加速和/或减速的动画. ft=1-[sin(1-t)*(Π/2)] 下面用一个示例演示SineEase的EaseIn动画效果,相比PowerEase平缓了许多.第二个模拟了EaseIn动画轨迹图形
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <Grid Background="Transparent" > <Grid.Resources> <Storyboard x:Name="SineStoryboard" > <DoubleAnimation From="0" To="400" Duration="0:0:3" Storyboard.TargetName="ellipse17" Storyboard.TargetProperty="(Canvas.Top)" > <DoubleAnimation.EasingFunction> <SineEase EasingMode="EaseIn" /> </DoubleAnimation.EasingFunction> </DoubleAnimation> <DoubleAnimation From="0" To="400" Duration="0:0:3" Storyboard.TargetName="ellipse18" Storyboard.TargetProperty="(Canvas.Left)" > </DoubleAnimation> <DoubleAnimation From="400" To="200" Duration="0:0:3" Storyboard.TargetName="ellipse18" Storyboard.TargetProperty="(Canvas.Top)" > <DoubleAnimation.EasingFunction> <SineEase EasingMode="EaseIn" /> </DoubleAnimation.EasingFunction> </DoubleAnimation> </Storyboard> </Grid.Resources> <Canvas Margin="12,0,12,0" > <Ellipse Name="ellipse17" Width="50" Height="50" Fill="Blue" /> <Ellipse x:Name="ellipse18" Fill="Red" Width="80" Height="80" Canvas.Left="0" Canvas.Top="400" /> <Button Margin="0,500,0,0" Content="启动动画" Height="80" Click="Button_Click_17" /> </Canvas> </Grid> private void Button_Click_17(object sender, RoutedEventArgs e) { SineStoryboard.Begin(); }
9.5.基于帧动画 9.51.原理 基于帧动画的创建主要是依赖CompositionTarget类,CompositionTarget是一个静态类,表示应用程序要在其上进行绘制的显示画面.每次绘制场景时,都会引发Rendering事件,创建基于帧动画的语法很简单,只需要为静态的CompositionTarget.Rendering事件关联事件处理程序,然后在事件处理程序处理动画的内容. 如果要结束动画,就移除CompositionTarget.Rendering关联事件处理程序.由此可见,基于帧动画只能使用C#代码进行创建,不能像其他动画用Xaml来编写,同时也无法在样式,控件模板或数据模板中定义. 当构建基于帧的动画时:它们不是依赖于时间的.动画可能在性能好的设备上运动更快,因为帧速率会增加,从而会更频繁的调用CompositionTarget.Rendering事件.CompositionTarget.Rendering事件的调用频率并不是一个固定值,它是和设备及当前应用程序的运行状况紧密相关,如果程序在基于帧动画的时候还在处理其他耗时操作时,CompositionTarget.Rendering事件的调用频率就会比较低.由于CompositionTarget.Rendering事件可以根据当前的状况来调整动画的频率使得动画更加流畅,所以它比直接使用定时器DispatcherTimer做原始的动画处理更加优越. 9.52.基于帧动画的应用场景 1.线性插值动画和关键帧动画实现不了或很难实现的动画. 2.创建一个基于物理的动画或者构建粒子效果模型如火焰,雪以及气泡. 9.53.基于帧动画的实现 下面通过一个例子来演示基于帧动画的运用,动画要实现的效果是通过在Canvas面板中触摸滑动来控制矩形Rectangle滑块的运动,滑块会往面板触摸的方向运动,但是滑块不能离开Canvas面板.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 <Canvas Background="Gray" PointerMoved="Canvas_PointerMoved" > <Rectangle x:Name="prectangle" Height="50" Width="100" RadiusX="12.5" RadiusY="12.5" > <Rectangle.Fill> <LinearGradientBrush> <GradientStop Color="Black" Offset="0" /> <GradientStop Color="White" Offset="0.5" /> <GradientStop Color="Black" Offset="1" /> </LinearGradientBrush> </Rectangle.Fill> </Rectangle> </Canvas> ///基于帧动画 /// CompositionTarget.Rendering += CompositionTarget_Rendering; this.prectangle.RenderTransform = translateTransform; /// <summary> /// 处理基于帧的事件,动画的逻辑在这个事件里面进行处理 /// </summary> /// <param name="sender" ></param> /// <param name="e" ></param> private void CompositionTarget_Rendering(object sender, object e) { var currentTime=DateTime.Now; //计算两帧之间的时间差,把时间差作为计算位移的系数 double elapsedTime = (currentTime - preTime).TotalSeconds; preTime=currentTime; //控制矩形的移动不超过画布面板的边界 translateTransform.X += mouseLocation.X * elapsedTime; if (translateTransform.X > 300) translateTransform.X = 300; if (translateTransform.X<0)translateTransform.X = 0; translateTransform.Y+= mouseLocation.Y * elapsedTime; if (translateTransform.Y > 450) translateTransform.Y = 450; if (translateTransform.Y < 0)translateTransform.Y= 0; } //触摸点的位置 Point mouseLocation; //用于矩形的位移变换改变矩形的位置 TranslateTransform translateTransform=new TranslateTransform(); //用于保存上一帧时间,计算时间差 DateTime preTime=DateTime.Now; /// <summary> /// 通过PointerMoved事件获取当前的触摸点相对于矩形的坐标,滑动点在矩形左边X值为负值,右边X值为正值.在上边Y值为负值,在下便Y值为正值. /// </summary> /// <param name="sender" ></param> /// <param name="e" ></param> private void Canvas_PointerMoved(object sender, PointerRoutedEventArgs e) { mouseLocation = e.GetCurrentPoint(this.prectangle).Position; }
9.61.帧速率 帧速率是用于测量显示帧数的量度,测量单位为”每秒显示帧数”,是指每秒钟刷新画面的帧数,判断一个动画是否能够流畅的运行,就需要关注动画的帧速率指标是否足够高. 在Win10里面虽然不能直接设置动画帧速率,但是可以测量出来,在应用程序里面把属性Application.Current.Host.Settings.EnableFrameRateCounter设置为true将可以在设备的顶部左侧看到帧速率的计数变化情况,默认情况采用Debug调试状态下就会显示出应用当前的帧速率.帧速率的范围是0-60,帧速率越大,应用程序的响应性能越高.帧速率的值一般应在20以上,该值小于30表示存在性能问题. 默认情况下会在文件App.xaml.cs中添加启用帧速率计数器的代码.
1 2 3 if (System.Diagnostics.Debugger.IsAttached) { this.DebugSettings.EnableFrameRateCounter = true ; }
表示当启动Debug状态调试应用程序时会启用帧速率计数器.其中Application.Current.Host.Settings.EnableFrameRateCounter=true表示启用帧速率计数器,设置为false则禁用帧速率计数器. 9.62.UI线程和构图线程 2.构图线程 工作是合并图形纹理并将其传递到GPU以供绘制,设备上的GPU将在称为自动缓存的进程中自动缓存并处理运行在构图线程上的动画,构图线程处理与变换特效(RenderTransform)和三维特效(Projection)属性关联的动画(如针对ScaleTransform,TranslateTransform,RotateTransform,PlaneProjection的属性改变的Storyboard动画)都是完全运行在构图线程上的.另外,Opacity和Clip属性设置也由构图线程处理.但是,如果使用OpacityMask或非矩形剪辑,则这些操作将传递到UI线程. 3.动画和线程 下面通过一个例子来演示用两种不同的方法来实现一个相同的动画效果,所实现的动画效果是让矩形的高度慢慢变成原来的两倍,第一种方式是用线性插值动画对矩形的Height属性进行动画处理,第二种方式也是线性插值动画,但是针对的动画目标属性是ScaleTransform的ScaleY属性,然后用一个按钮单击事件阻塞UI线程2秒钟,可以看到针对Height属性的动画会暂停2秒钟再继续运行,而针对ScaleTransform的ScaleY属性不会受UI线程阻塞的影响.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 <StackPanel> <StackPanel.Resources> <Storyboard x:Name="heightStoryboard" > <!--针对Height属性的动画--> <DoubleAnimation Storyboard.TargetName="rectangle1" Storyboard.TargetProperty="Height" RepeatBehavior="Forever" EnableDependentAnimation="True" From="100" To="200" Duration="0:0:2" > </DoubleAnimation> </Storyboard> <Storyboard x:Name="scaleTransformStoryboard" > <!--针对ScaleTransform的ScaleY属性的动画--> <DoubleAnimation Storyboard.TargetName="scaleTransform1" Storyboard.TargetProperty="ScaleY" RepeatBehavior="Forever" From="1" To="2" Duration="0:0:2" > </DoubleAnimation> </Storyboard> </StackPanel.Resources> <Button Content="阻塞UI线程" Click="Button_Click_18" /> <Button x:Name="heightAnimationButton" Content="Height属性动画" Click="heightAnimationButton_Click" /> <Button x:Name="scaleTransformAnimationButton" Content="ScaleTransform属性动画" Click="scaleTransformAnimationButton_Click" /> <Rectangle Height="100" Fill="Blue" x:Name="rectangle1" > <Rectangle.RenderTransform> <ScaleTransform x:Name="scaleTransform1" /> </Rectangle.RenderTransform> </Rectangle> </StackPanel> /// <summary> /// 阻塞线程2秒钟 /// </summary> /// <param name="sender" ></param> /// <param name="e" ></param> private void Button_Click_18(object sender, RoutedEventArgs e) { Task.Delay(2000).Wait(); } private void heightAnimationButton_Click(object sender, RoutedEventArgs e) { //播放改变高度属性的动画,高度由100变成200 scaleTransformStoryboard.Stop(); heightStoryboard.Begin(); } private void scaleTransformAnimationButton_Click(object sender, RoutedEventArgs e) { heightStoryboard.Stop(); scaleTransformStoryboard.Begin(); }
9.7.模拟实现微信的彩蛋动画 9.72.星星创建工厂 实现星星的创建工厂StarFactory类,StarFactory类的作用是创建动画里面的星星对象,动画的实现需要向调用StarFactory类创建出星星对象,然后对星星进行动画处理,所以StarFactory类是一个非常单一的星星构造工厂,不涉及动画的操作,只涉及Path对象的创建 下面是星星图形形状的构造封装的3个方法:1._RefactorPoints方法是用于取两个点线段之间的1/3,2/3点和两点间的随机点,最后再加上原来的两个点,返回5个点的点集合;2._RecurseSide方法是封装两个点之间递归之后所取到的点集合;3._CreatePath方法则是把这些点集合连接起来创建一个Path图形表示星星图形.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 public class StarFactory { public StarFactory () { } /// <summary> /// 把两个点转换成多级的点的集合 /// </summary> /// <param name="a" ></param> /// <param name="b" ></param> /// <param name="level" ></param> /// <returns></returns> private static List<Point> _RecurseSide(Point a,Point b,int level) { ///递归完成后,返回此线段 if (level==0) { return new List<Point> { a,b}; } else { //首先,需要建立起第一次递归的点的列表,一直递归到级别为0 List<Point> newPoints = new List<Point>(); //把区域分成5个点 foreach (Point item in _RefactorPoints(a,b)) { newPoints.Add(item); } List<Point> aggregatePoints= new List<Point>(); //把每一个线段进一步分解 for (int i = 0; i < newPoints.Count; i++) { int y = i + 1 == newPoints.Count ? 0 : i + 1; aggregatePoints.AddRange(_RecurseSide(newPoints[i], newPoints[y],level-1)); } return aggregatePoints; } } static Random _random= new Random(); /// <summary> /// 通过输入两个点来构建一个有多个三角形组成的Star形状 /// </summary> /// <param name="a" ></param> /// <param name="b" ></param> /// <returns></returns> /// <exception cref="NotImplementedException" ></exception> private static IEnumerable<Point> _RefactorPoints(Point a, Point b) { //第一个点 yield return a; double dX = b.X - a.Y; double dY= b.Y - a.Y; //第一点到第二个点1/3处的一个点 yield return new Point(a.X+dX/3.0,a.Y+dY/3.0); double factor =_random.NextDouble()- 0.5; double vX = (a.X + b.X) / (2.0 + factor )+Math.Sqrt(3.0+factor )*(b.Y-a.Y)/(6.0+factor *2.0); double vY = (a.Y + b.Y) / (2.0 + factor ) + Math.Sqrt(3.0+factor )*(a.X-b.X)/(6.0+factor *2.0); //中间的三角形的顶点 yield return new Point(vX,vY); //第二个点到第一个点1/3处的一个点 yield return new Point(b.X-dX/3.0,b.Y-dY/3.0); //第二个点 yield return b; } /// <summary> /// 使用一系列点来创建路径图形 /// </summary> /// <returns></returns> private static Path _CreatePath(List<Point> points) { PathSegmentCollection segments= new PathSegmentCollection(); bool first = true ; //把点添加到线段里面 foreach (Point item in points) { if (first) { first = false ; } else { segments.Add(new LineSegment { Point = item }); } } PathGeometry pathGeometry= new PathGeometry(); //通过线段构建几何图形 pathGeometry.Figures.Add(new PathFigure { IsClosed = true , StartPoint = points[0], Segments = segments }); return new Path { Data = pathGeometry }; } }
2.星星颜色的随机生成 这里用LinearGradientBrush来填充Path图形,封装的方法_GetColor表示创建随机的颜色对象,_ColorFactory表示对Path图形填充随机的颜色画刷.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 /// <summary> /// 添加颜色到路径图形 /// </summary> /// <param name="input" ></param> private static void _ColorFactory(Path input) { LinearGradientBrush brush = new LinearGradientBrush(); brush.StartPoint = new Point(0,0); brush.EndPoint= new Point(1.0,1.0); GradientStop start=new GradientStop(); start.Color = _GetColor(); start.Offset = 0; GradientStop middle=new GradientStop(); middle.Color = _GetColor(); middle.Offset=_random.NextDouble(); GradientStop end=new GradientStop(); end.Color= _GetColor(); end.Offset = 1.0; brush.GradientStops.Add(start); brush.GradientStops.Add(middle); brush.GradientStops.Add(end); input.Fill = brush; } /// <summary> /// 获取一个随机的颜色 /// </summary> /// <returns></returns> private static Color _GetColor () { Color color = new Color(); color.A = (byte)(_random.Next(200) + 20); color.R = (byte)(_random.Next(200) + 50); color.G = (byte)(_random.Next(200) + 50); color.B = (byte)(_random.Next(200) + 50); return color; }
3.创建星星对象 创建星星对象需要先有3个点,然后再利用这3个点根据创建星星图形的原理(3*4的n次方),n表示星星递归的层次.创建星星图形,然后用随机颜色画刷填充图形.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 const int MIN = 0; const int MAX = 2; //随机数产生器 static readonly Random _random=new Random(); /// <summary> /// 创建一个Star /// </summary> /// <returns></returns> public static Path Create () { Point a=new Point(0,0); Point b = new Point(_random.NextDouble() * 70.0 + 15.0, 0); Point c=new Point(0,b.X); int levels=_random.Next(MIN,MAX); List<Point> points=new List<Point>(); points.AddRange(_RecurseSide(a,b,levels)); points.AddRange(_RecurseSide(b,c,levels)); points.AddRange(_RecurseSide(c,a,levels)); //画边 Path retVal=_CreatePath(points); //添加颜色 _ColorFactory(retVal); //建立一个旋转的角度 RotateTransform rotate=new RotateTransform(); rotate.CenterX = 0.5; rotate.CenterY = 0.5; rotate.Angle=_random.NextDouble()*360.0; retVal.SetValue(Path.RenderTransformProperty,rotate); return retVal; }
9.73.实现单个星星的动画轨迹 要实现对星星实体(StarEntity类)的封装,在StarEntity类里面要实现基于帧动画,在帧刷新事件处理程序里实现星星飘落的动画逻辑.首先需要处理的是确定星星在区域最顶部的随机位置,下落的随机速度和方向,然后在动画的过程中需要去判断星星是否碰撞到了区域的左边距或右边距,碰撞之后则需要再反弹回来往另外一边运动,最后还需要判断星星是否已经落到了最底下,如果落到了区域最底下,则需要移除CompositionTarget.Rendering事件和从画布上移除星星图形,还要触发StarflakeDied事件告知调用方星星已经销毁掉了.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 public class StarEntity { //左边距 const double LEFT = 460; //上边距 const double TOP = 800; //离开屏幕 const double GONE = 480; //随机近似数 private double _affinity; //Star实体的唯一ID private Guid _identifier=Guid.NewGuid(); //随机数产生器 private static Random _raodom=new Random(); //Star所在的画布 private Canvas _surface; //获取Star所在的画布 private Canvas Surface { get { return _surface; } } //x,y坐标和相对速度 private double x, y, velocity; //Star的路径图形 private Path _starflake; /// <summary> /// 当Star飘落到底下时回收Star事件 /// </summary> private EventHandler StarflakeDied; //获取Star实体的唯一ID public Guid Identifier { get { return _identifier; } } //默认的构造器 public StarEntity(Action<Path> insert) : this(insert, true ) { } /// <summary> /// 星星对象构造方法 是否从顶下落下 /// </summary> /// <param name="insert" ></param> /// <param name="v" ></param> public StarEntity(Action<Path> insert, bool fromTop) { _starflake = StarFactory.Create(); ///产生0-1的随机数 _affinity=_raodom.NextDouble(); ///设置速度,初始化x,y轴 velocity=_raodom.NextDouble()*2; x = _raodom.NextDouble() * LEFT; y=fromTop?0:_raodom.NextDouble() * TOP; //设置Star在画布的位置 _starflake.SetValue(Canvas.LeftProperty,x); _starflake.SetValue(Canvas.TopProperty,y); //添加到画布上 insert(_starflake); //记录下Star的画布 _surface = _starflake.Parent as Canvas; //订阅基于帧动画事件 CompositionTarget.Rendering += CompositionTarget_Rendering; } /// <summary> /// 基于帧动画事件处理 /// </summary> /// <param name="sender" ></param> /// <param name="e" ></param> /// <exception cref="NotImplementedException" ></exception> private void CompositionTarget_Rendering(object sender, object e) { _Frame(); } /// <summary>44 /// Star下落的每一帧的处理 /// </summary> private void _Frame () { //下降的y轴的大小 y = y + velocity + 3.0 * _raodom.NextDouble() - 1.0; //判断是否离开了屏幕 if (y>GONE) { CompositionTarget.Rendering -= CompositionTarget_Rendering; _surface.Children.Remove(_starflake); //通知外部,Star已经被清除 EventHandler handler = StarflakeDied; if (handler!=null) { handler(this,EventArgs.Empty); } } else { //水平轻推 double xFactor = 10.0 * _affinity; if (_affinity < 0.5) xFactor *= -1.0;//小于0.5向左边移动,大于0.5向右边移动,等于0.5垂直下降 { x = x + _raodom.NextDouble() * xFactor; } //左边的边缘 if (x<0) { x = 0; _affinity = 1.0 - _affinity; } //右边的边缘 if (x>LEFT) { x = LEFT; _affinity=1.0- _affinity; } _starflake.SetValue(Canvas.LeftProperty,x); _starflake.SetValue(Canvas.TopProperty,y); } //转动 RotateTransform rotate = (RotateTransform)_starflake.GetValue(Path.RenderTransformProperty); rotate.Angle += _raodom.NextDouble() * 4.0 * _affinity; } /// <summary> /// 重载获取唯一的对象码 /// </summary> /// <returns></returns> public override int GetHashCode () { return Identifier.GetHashCode(); } public override bool Equals(object obj) { return obj is StarEntity && ((StarEntity)obj).Identifier.Equals(Identifier); } }
9.74.封装批量星星飘落的逻辑 实现一个StarBehavior类用附加属性的方式在Canvas上添加批量的星星飘落的动画,StarBehavior类里面通过AttachStarFlake属性表示是否在该Canvas面板上添加星星飘落动画,当设置为true则表示触发动画的开始,false则表示停止添加星星,直到星星全部飘落到底下时动画停止.在开始播放动画时会初始化多个StarEntity对象,并运行其飘落的动画效果,当飘落到底下StarEntity对象被销毁时,会触发StarflakeDied事件,在StarflakeDied事件里面继续初始化新的StarEntity对象,如果动画停止了,beginning=false,则不再创建新的StarEntity对象.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 using App1.Models; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; namespace App1.Behaviors { /// <summary> /// StarBehavior类管理附加属性的行为触发批量星星的构造和动画的实现 /// </summary> public static class StarBehavior { //屏幕上生成的Star数量 const int CAPACITY = 75; //动画是否已经开始的标识符 private static bool begining=false ; //Star对象列表 private static List<StarEntity> _starflakes=new List<StarEntity>(CAPACITY); //添加动画效果的属性 public static bool GetAttachStarFlake(DependencyObject obj) { return (bool)obj.GetValue(AttachStarFlakeProperty); } public static void SetAttachStarFlake(DependencyObject obj, bool value) { obj.SetValue(AttachStarFlakeProperty, value); } // Using a DependencyProperty as the backing store for AttachStarFlake. This enables animation, styling, binding, etc... public static readonly DependencyProperty AttachStarFlakeProperty = DependencyProperty.RegisterAttached("AttachStar" , typeof(bool), typeof(StarBehavior), new PropertyMetadata(false ,new PropertyChangedCallback(_Attach))); /// <summary> /// 附加属性改变事件处理方法 /// </summary> /// <param name="d" ></param> /// <param name="e" ></param> /// <exception cref="NotImplementedException" ></exception> private static void _Attach(DependencyObject d, DependencyPropertyChangedEventArgs e) { Canvas canvas= d as Canvas; if (canvas != null && e.NewValue!=null && e.NewValue.GetType().Equals(typeof(bool))) { if ((bool)e.NewValue) { //画布上还有子元素证明星星还没完全飘落下去 if (canvas.Children.Count>0) { return ; } //开始动画 begining=true ; for (int i = 0; i < _starflakes.Capacity; i++) { StarEntity starflake = new StarEntity((o) => canvas.Children.Add(o)); starflake.StarflakeDied +=new EventHandler(Starflake_StarflakeDied); _starflakes.Add(starflake); } } else { //结束动画 begining = false ; } } } //回收Star事件 private static void Starflake_StarflakeDied(object sender, EventArgs e) { StarEntity starflake=sender as StarEntity; //获取Star的面板,用来添加一个新的Star Canvas canvas = starflake.Surface; _starflakes.Remove(starflake); if (begining) { //如果动画还在继续,运行一个Star消失之后再创建一个新的Star StarEntity newFlake = new StarEntity((o)=>canvas.Children.Add(o),true ); newFlake.StarflakeDied += Starflake_StarflakeDied; _starflakes.Add(newFlake); } } } }
9.75.Star飘落演示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <Grid> <Canvas x:Name="myCanvas" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" > </Canvas> <Button x:Name="btn" VerticalAlignment="Bottom" Content="开始星星飘落" Click="btn_Click" /> </Grid> /// <summary> /// 按钮事件,播放动画和停止动画 /// </summary> /// <param name="sender" ></param> /// <param name="e" ></param> private async void btn_Click(object sender, RoutedEventArgs e) { if ((bool)myCanvas.GetValue(StarBehavior.AttachStarFlakeProperty) == false ) { //要等所有的星星全部落下去之后才可以再次播放动画 if (myCanvas.Children.Count > 0) { await new MessageDialog("星星动画未完全结束" ).ShowAsync(); return ; } myCanvas.SetValue(StarBehavior.AttachStarFlakeProperty, true ); button.Content = "停止" ; } else { myCanvas.SetValue(StarBehavior.AttachStarFlakeProperty, false ); btn.Content = "开始" ; } }
样式和模板 10.11.创建样式 10.21.控件模板(ControlTemplate)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <Button Content="Hello World" > <Button.Template> <ControlTemplate> <Grid> <Ellipse Width="{TemplateBinding Button.Width}" Height="{TemplateBinding Height}" Fill="{TemplateBinding Button.Background}" Stroke="Red" /> <TextBlock Margin="5,0,0,0" FontSize="50" VerticalAlignment="Center" HorizontalAlignment="Center" Text="{TemplateBinding Button.Content}" /> <TextBlock FontSize="50" Foreground="Red" VerticalAlignment="Center" HorizontalAlignment="Center" Text="{TemplateBinding Button.Content}" /> </Grid> </ControlTemplate> </Button.Template> </Button>
10.22.ContentControl和ContentPresenter ContentPresenter是用来显示控件ContentControl的Content属性的,ContentPresneter也有Content属性,默认的情况下可以把Content定义的内容投影到ContentControl的Content里面
1 2 3 4 5 6 7 8 9 10 11 12 <Button> <Button.Template> <ControlTemplate> <Grid> <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalAlignment}" VerticalAlignment="{TemplateBinding VerticalAlignment}" /> </Grid> </ControlTemplate> </Button.Template> <Button.Content> <Rectangle Fill="Red" Height="50" Width="50" /> </Button.Content> </Button>
10.23.视觉状态管理(VisualStatesManager) 通过ControlTemplate修改过的Button控件,当我们单击控件时发现比系统默认的Button控件少了一些点击的状态,这时候就需要使用视觉状态管理类VisualStateManager了 VisualStateManager的作用是控制控件的状态转换,不同状态下的UI显示效果的区别及转换过程动画.视觉状态管理主要包括VisualStates(视觉状态),VisualStateGroups(视觉状态组)和VisualTransitions(视觉过度转换).VisualStates是指控件在不同状态下显示的效果,如Button控件默认就包含(Normal,MouseOver,Pressed,Disabled,Unfocused,Focused)六种状态.Visual State Groups是为有互斥效果的控件提供的,对于相同的视觉状态组是互斥的,对于不同的视觉状态组是不互斥的,Visual Transitions是视觉状态切换时的过度的动画效果. VisualStateManager通过在控件上设置VisualStateManager.VisualStateGroups附加属性向控件添加VisualStates和VisualTransitions.VisualStates是VisualState的集合,里面定义了多个VisualState表示控件在不同状态下的视觉表现效果,使用了Storyboard故事板属性来实现当前控件状态的转换,VisualTransitions是VisualTransition的集合,但是VisualTransitions不是必须的,如果控件在不同状态之间转换的时候不需要动画效果,就可以省略掉VisualTransitions.VisualTransition主要有三个属性:From(当前的状态),To(转换的状态)和GeneratedDuration(转换时间).状态的转换是通过调用VisualStateManager类的GoToState方法来实现的. VisualStateManager的Xaml语法如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 <Button Content="Hello World" LostFocus="Button_LostFocus" Tapped="Button_Tapped" > <Button.Template> <ControlTemplate TargetType="Button" > <Border> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CommonStates" > <VisualStateGroup.Transitions> <VisualTransition From="Test1" To="Test2" GeneratedDuration="0:0:1.5" > <Storyboard> </Storyboard> </VisualTransition> </VisualStateGroup.Transitions> <!--创建状态Test1把Border背景的颜色改成红色--> <VisualState x:Name="Test1" > <Storyboard> <ColorAnimation Storyboard.TargetName="BorderBrush" Storyboard.TargetProperty="Color" To="Red" /> </Storyboard> </VisualState> <!--创建状态Test2把Border背景的颜色改成蓝色--> <VisualState x:Name="Test2" > <Storyboard> <ColorAnimation Storyboard.TargetName="BorderBrush" Storyboard.TargetProperty="Color" To="Blue" /> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <Border.Background> <!--定义Border背景的颜色,用于测试不同状态的显示效果--> <SolidColorBrush x:Name="BorderBrush" Color="Black" /> </Border.Background> <Grid> <Ellipse x:Name="ellipse19" Width="{TemplateBinding Button.Width}" Height="{TemplateBinding Height}" Fill="{TemplateBinding Button.Background}" Stroke="Red" /> <TextBlock Margin="5,0,0,0" FontSize="50" VerticalAlignment="Center" HorizontalAlignment="Center" Text="{TemplateBinding Button.Content}" /> <TextBlock FontSize="50" Foreground="Red" VerticalAlignment="Center" HorizontalAlignment="Center" Text="{TemplateBinding Button.Content}" /> </Grid> </Border> </ControlTemplate> </Button.Template> </Button> /// <summary> /// 跳转到状态Test1 /// </summary> /// <param name="sender" ></param> /// <param name="e" ></param> private void Button_LostFocus(object sender, RoutedEventArgs e) { VisualStateManager.GoToState(sender as Button,"Test1" ,true ); } /// <summary> /// 跳转到状态Test2 /// </summary> /// <param name="sender" ></param> /// <param name="e" ></param> private void Button_Tapped(object sender, TappedRoutedEventArgs e) { VisualStateManager.GoToState(sender as Button,"Test2" ,true ); }
10.24.数据模板(DataTemplate) DateTemplate和ControlTemplate所负责的任务是不一样的,ControlTemplate用于描述控件本身,而DataTemplate用于描述控件的数据对象的视觉格式.这两者并不是毫无关联,相反它们通常需要合作完成一些工作.在控件的模板上这两者有着非常微妙的关系,可以利用DataTemplate去辅助ControlTemplate实现一些效果.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 <Button Content="Hello World" LostFocus="Button_LostFocus" Tapped="Button_Tapped" > <Button.Template> <ControlTemplate TargetType="Button" > <Border> <VisualStateManager.VisualStateGroups> <VisualStateGroup> <VisualStateGroup.Transitions> <!--状态Test1转换为状态Test2的颜色变化动画--> <VisualTransition From="Test1" To="Test2" GeneratedDuration="0:0:1.5" > <Storyboard> </Storyboard> </VisualTransition> </VisualStateGroup.Transitions> <!--创建状态Test1把Border背景的颜色改成红色--> <VisualState x:Name="Test3" > <Storyboard> <ColorAnimation Storyboard.TargetName="BorderBrush1" Storyboard.TargetProperty="Color" To="Red" /> </Storyboard> </VisualState> <!--创建状态Test2把Border背景的颜色改成蓝色--> <VisualState x:Name="Test4" > <Storyboard> <ColorAnimation Storyboard.TargetName="BorderBrush1" Storyboard.TargetProperty="Color" To="Blue" /> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <Border.Background> <SolidColorBrush x:Name="BorderBrush1" Color="Black" /> </Border.Background> <Grid> <Ellipse x:Name="ellipse20" Width="{Binding Width}" Height="{Binding Height}" Fill="{Binding Background}" Stroke="Red" /> <TextBlock Margin="5,0,0,0" FontSize="50" VerticalAlignment="Center" HorizontalAlignment="Center" Text="{Binding}" /> <TextBlock FontSize="50" Foreground="Red" VerticalAlignment="Center" HorizontalAlignment="Center" Text="{Binding}" /> </Grid> </Border> </ControlTemplate> </Button.Template> </Button>
10.25.ItemTemplate,ContentTemplate和DataTemplate ContentControl类是内容控件的基类,如Button,CheckBox,最明显的特征是这个控件有Content属性,具有Content属性的系统控件都是ContentControl的子类,ItemsControl类是列表控件的基类,如ListBox,它和ContentControl类类似,只不过ContentControl类是单项内容,ItemsControl是多项内容. 所有继承自ContentControl内容控件的ContentTemplate属性和所有继承自ItemsControl列表控件的ItemTemplate属性,都是DataTemplate类型,意思就是可以通过DataTemplate定义ContentControl和ItemsControl的控件的UI效果和数据显示. 10.26.数据模板的使用 DataTemplate是一种可视化的数据模板,其强大的作用在于可以把数据通过绑定的方式展现在控件上. 用DataTemplate去实现UI控件的内容显示,其实DataTemplate最主要的作用并不是去取代ControlTemplate的样式定义,而是通过数据绑定把数据控件的数据源信息展现到控件上.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <Style x:Name="ItemsControlStyle" TargetType="ItemsControl" > <Setter Property="Template" > <Setter.Value> <ControlTemplate TargetType="ItemsControl" > <ScrollViewer BorderBrush="Red" BorderThickness="6" > <StackPanel Orientation="Horizontal" Background="Blue" > <Border BorderBrush="Yellow" BorderThickness="3" > <ItemsPresenter/> </Border> </StackPanel> </ScrollViewer> </ControlTemplate> </Setter.Value> </Setter> </Style> ObservableCollection<People> peoples = new ObservableCollection<People>(); peoples.Add(new People () { FirstName="lee2" ,LastName="Terry2" }); peoples.Add(new People () { FirstName="lee3" ,LastName="Terry3" }); peoples.Add(new People () { FirstName = "lee4" , LastName = "Terry4" }); peoples.Add(new People () { FirstName = "lee5" , LastName = "Terry5" }); itemsControl.ItemsSource = peoples; itemsControl1.ItemsSource = peoples; <ItemsControl x:Name="itemsControl1" ItemTemplate="{StaticResource PersonNameDataTemplate}" Style="{StaticResource ItemsControlStyle}" />
10.27.读取和更换数据模板 动态更换样式 1.定义3个DataTemplate资源,一个是非选中状态,一个是选中状态,还有一个是默认的状态,其实默认状态和非选中状态是一样的,但是默认状态的数据项样式不能在C#中再次调用.在两个模板中都添加了Tap事件,用户捕获点击事件.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 <ListBox x:Name="listbox" ItemTemplate="{StaticResource dataTemplateDefaultKey}" /> <!--选中数据项的样式--> <DataTemplate x:Key="dataTemplateSelectKey" x:Name="dataTemplateSelectName" > <Grid Tapped="StackPanel_Tapped" Background="Red" > <TextBlock Text="{Binding LastName}" FontSize="50" /> </Grid> </DataTemplate> <!--默认数据项的样式,注意默认的数据项样式不能在C <DataTemplate x:Key="dataTemplateDefaultKey" x:Name="dataTemplateDefaultName" > <StackPanel Orientation="Horizontal" Tapped="StackPanel_Tapped" > <TextBlock Text="{Binding LastName}" /> <TextBlock Text="," /> <TextBlock Text="{Binding FirstName}" /> </StackPanel> </DataTemplate> <!--非选中数据项的样式--> <DataTemplate x:Key="dataTemplateNoSelectKey" x:Name="dataTemplateNoSelectName" > <StackPanel Orientation="Horizontal" Tapped="StackPanel_Tapped" > <TextBlock Text="{Binding LastName}" /> <TextBlock Text="," /> <TextBlock Text="{Binding FirstName}" /> </StackPanel> </DataTemplate> private void StackPanel_Tapped(object sender, TappedRoutedEventArgs e) { //获取到的对象是ListBoxItem ListBoxItem myListBoxItem = (ListBoxItem)(listbox.ContainerFromItem((sender as Panel).DataContext)); //在ListBoxItem中查找ContentPresenter ContentPresenter myContentPresenter = FindVisualChild<ContentPresenter>(myListBoxItem); //ContentPresenter myContentPresenter = (ContentPresenter)(listbox.ContainerFromItem((sender as Panel).DataContext)); if (myContentPresenter.ContentTemplate.Equals(dataTemplateSelectName)) { myContentPresenter.ContentTemplate = dataTemplateNoSelectName; } else { myContentPresenter.ContentTemplate = dataTemplateSelectName; } } private childItem FindVisualChild<childItem>(DependencyObject obj) where childItem:DependencyObject { for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++) { DependencyObject child=VisualTreeHelper.GetChild(obj, i); if (child != null && child is childItem) return (childItem)child; else { childItem childOfChild=FindVisualChild<childItem>(child); if (childOfChild != null) return childOfChild; } } return null; }
数据绑定 绑定四个组件:绑定目标对象,目标属性,绑定源,以及要使用的绑定源中的值的路径. 例如,如果要将TextBox的内容绑定到Employee对象的Name属性,则目标对象是TextBox,目标属性是Text,要使用的值是Name,源对象是Employee对象. 11.12.创建绑定 1.定义源对象 2.设置DataContext属性绑定到源对象 3.使用Binding标记扩展来绑定数据源对象的属性.
1 2 3 4 5 6 7 8 9 public class MyData { public string Title { get; set ; } } public MyData myData = new MyData { Title = "这是绑定的标题!" }; this.DataContext = myData; <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" > <TextBlock Text="{Binding Title}" Margin="12,100,0,28" FontSize="50" /> </Grid>
11.13.用元素值绑定 用元素值绑定就是将某一个控件元素作为绑定的数据源,绑定的对象是控件元素,而绑定的数据源同时也是控件元素,这种绑定的方式,可以轻松实现两个控件之间的值的交互影响,用元素值进行绑定是通过设置Binding的ElementName属性和Path属性来实现的,ElementName属性赋值为数据源控件的Name的值,Path属性赋值则赋值为数据源控件的某个属性,这个属性就是数据源控件的一个数据变化的反映 2.在C#中可以通过类似语法指定属性的子属性,例如,字句Path=ShoppingCart.Order设置与对象或属性ShoppingCart的Order属性的绑定,也就是说ShoppingCart是绑定数据源的属性,而Order则是ShoppingCart的属性,相当于是数据源的属性的属性. 3.若要绑定到附加属性,应在附加属性周围放置圆括号,例如,若要绑定到附加属性Grid.Row,则语法是Path=(Grid.Row). 4.可以使用数组的索引器来实现数据的绑定.例如,字句Path=ShoppongCart[0]将绑定设置为与数组属性的内部对应的索引的数值. 5.可以在Path字句中混合索引器和子属性,例如,Path=ShoppingCart.ShippingInfo[MailingAddress.Street]. 6.在索引器内部,可以由多个由逗号分隔的索引器参数.可以使用圆括号指定每个参数的类型.Path=”[(sys:Int32)42,(sys:Int32)24]”,其中sys映射到System命名空间. 7.如果源为集合视图,则可以用斜杠”/“指定当前项,例如,字句Path=/用于设置到视图中当前项的绑定,如果源为集合,则此语法指定默认集合视图的当前项. 8.可以结合使用属性名和斜杠来遍历作为集合的属性,例如,Path=/Offices/ManagerName指定源集合的当前项,该源集合包含作为集合的Offices属性,其当前项是一个包含ManagerName属性的对象. 9.也可以使用句点”.”路径绑定到当前源.例如,Text=”{Binding}”等效于Text=”{Binding Path=.}” 下面给出控制圆的半径的示例,圆形的半径绑定到Slider控件的值,从而实现通过即时改变Slider控件的值来改变Slider控件的值来改变圆的大小.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <StackPanel Margin="12,0,12,0" > <TextBlock FontSize="25" Text="圆形的半径会根据slider控件的值而改变" /> <Slider Name="slider" Value="50" Maximum="400" /> <TextBlock FontSize="25" Text="半径为:" /> <TextBlock Name="txtblk" Text="{Binding ElementName=slider,Path=Value}" FontSize="48" /> <Ellipse Height="{Binding ElementName=slider,Path=Value}" Width="{Binding ElementName=slider,Path=Value}" Fill="Red" Stroke="Black" StrokeThickness="1" /> </StackPanel> </PivotItem> `` 11.14.三种绑定模式 1.OneTime OneTime表示一次绑定,在绑定创建时使用数据源更新目标,适用于只显示数据而不进行数据的更新.OneTime绑定会导致源属性初始化目标属性,但不传播后续更改. 2.OneWay OneWay表示单向绑定,在绑定创建时或源数据发生变化时更新到目标,适用于显示变化的数据,这是默认模式.OneWay绑定导致对源属性的更改会自动更新目标属性,但是对目标属性的更改不会传播回源属性.此绑定类型适用于绑定的控件为隐式只读控件的情况,如果无需监视目标属性的更改,则使用OneWay的绑定模式可避免TwoWay绑定模式的系统开销 3.TwoWay TwoWay表示双向绑定,可以同时更新源数据和目标数据,TwoWay绑定导致对源属性的更改会自动更新目标属性,而对目标属性的更改也会自动更新源属性.此绑定类型适用于输入框或其他完全交互式UI方案.大多数UI元素内部实现的属性都默认为OneWay绑定,但是一些依赖项属性,通常为用户可编辑的控件的属性,如TextBox的Text属性和CheckBox的IsChecked属性,默认为TwoWay绑定. 下面演示三种绑定模式的区别 ``` bash <StackPanel Margin="12,0,12,0" > <Slider x:Name="slider" Value="50" Maximum="400" /> <!--OneTime--> <TextBlock FontSize="25" Height="41" Text="OneTime" VerticalAlignment="Top" Width="112" /> <TextBox Height="72" Text="{Binding ElementName=slider,Path=Value,Mode=OneTime}" Width="269" /> <TextBlock FontSize="25" Height="46" Text="OneWay" VerticalAlignment="Top" Width="99" /> <!--OneWay--> <TextBox Height="72" Text="{Binding ElementName=slider,Path=Value,Mode=OneWay}" Width="269" /> <TextBlock Height="40" FontSize="25" Text="TwoWay" VerticalAlignment="Top" Width="94" /> <!--TwoWay--> <TextBox Height="72" Text="{Binding ElementName=slider,Path=Value,Mode=TwoWay}" Width="268" /> <TextBlock FontSize="25" Height="43" Text="slider的值:" /> <TextBlock FontSize="25" Height="43" Text="{Binding ElementName=slider,Path=Value}" Width="185" /> </StackPanel>
11.15.更改通知
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class MyData : INotifyPropertyChanged { private string title; public string Title { get => title; set { title = value; OnPropertyChanged("Title" ); } } private void OnPropertyChanged(string name) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); } public event PropertyChangedEventHandler PropertyChanged; } private void Button_Click_19(object sender, RoutedEventArgs e) { myData.Title = "New Title" ; }
11.16.绑定数据转换 绑定数据转换需要通过Binding.Converter属性表示获取或设置转换器对象.当数据在源和目标之间传递时,绑定引擎调用该对象来修改数据.可以对任何的绑定设置一个转换器,通过创建一个类和实现IValueConverter接口来针对每个具体的应用场景自定义该转换器.所以转换器是派生自IValueConverter接口的类.它包含两种方法:Convert和ConvertBack方法.如果为绑定定义了Converter参数,则绑定引擎会调用Convert和ConvertBack方法.从源传递数据时,绑定引擎调用Convert并将返回的数据传递给目标,从目标传递数据时,绑定引擎调用ConvertBack并将返回的数据传递给源,如果只是要获取从数据源到绑定目标的单向绑定,只需要实现Convert方法.实现了转换器的逻辑之后,若要在绑定中使用转换器,首先要创建转换器类的实例.将转换器类的实例设置为程序中资源的XAML.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 public class Clock : INotifyPropertyChanged { int hour, minute, second; public Clock () { //h获取当前的时间 Tmr_Tick(null, null); //使用定时器来触发时间来改变类的时分秒属性 //每0.1秒获取一次当前的时间 DispatcherTimer tmr = new DispatcherTimer(); tmr.Interval = TimeSpan.FromSeconds(0.1); tmr.Tick += Tmr_Tick; tmr.Start(); } public int Hour { protected set { if (value != hour) { hour = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Hour" )); } } get { return hour; } } //分钟属性 public int Minute { get => minute; set { if (minute != value) { minute = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Minute" )); } } } public int Second { get => second; set { if (second != value) { second = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Second" )); } } } /// <summary> /// 时间触发器 /// </summary> /// <param name="sender" ></param> /// <param name="e" ></param> /// <exception cref="NotImplementedException" ></exception> private void Tmr_Tick(object sender, object e) { DateTime dt=DateTime.Now; Hour= dt.Hour; Minute= dt.Minute; Second= dt.Second; } /// <summary> /// 属性值改变事件 /// </summary> public event PropertyChangedEventHandler PropertyChanged; } public class HoursToDayStringConverter:IValueConverter { public object Convert(object value, Type targetType, object parameter, string language) { if (int.Parse(value.ToString())<12) { return "Dear Master,Good Morning" ; } else if (int.Parse(value.ToString())>12) { return "Dear Master,Good Afternoon" ; } else { return "Dear Master,Good " ; } } public object ConvertBack(object value, Type targetType, object parameter, string language) { return DateTime.Now.Hour; } } <Grid> <StackPanel Margin="12,0,12,0" DataContext="{StaticResource clock}" > <TextBlock FontSize="30" Text="{Binding Hour,Converter={StaticResource booleanToDayString}}" /> <TextBlock FontSize="30" Text="现在的时间是:" /> <TextBlock FontSize="20" Text="{Binding Hour}" /> <TextBlock FontSize="20" Text="小时" /> <TextBlock FontSize="20" Text="{Binding Minute}" /> <TextBlock FontSize="20" Text="分钟" /> <TextBlock FontSize="20" Text="{Binding Second}" /> <TextBlock FontSize="20" Text="秒" /> </StackPanel> </Grid> <models:Clock x:Key="clock" /> <converters:HoursToDayStringConverter x:Key="booleanToDayString" />
11.2.绑定集合 绑定目标:ItemsControl对象(ListView等列表控件)ItemsSource属性. 绑定对象:OneWay 数据源:集合对象 值 若要将列表控件绑定到集合对象,应使用列表控件自带的ItemsSource属性,而不是DataContext属性. 可以将ItemSource属性视为列表控件的内容,为列表项提供数据,还需要注意的是,绑定是OneWay模式,因为ItemSource属性默认情况下支持OneWay绑定. 11.21.数据集合 在实现列表绑定集合之前,必须要选择使用一个数据集合类或自定义实现一个数据集合类才能实例化出数据源的集合对象,不同的数据集合所实现的绑定的效果是有差异的. 1.ObservaleCollection集合. ObservableCollection类是实现了INotifyCollectionChanged接口的数据集合类,使用ObservableCollection类的实例与列表控件及进行绑定可以动态地往数据源地集合对象增加或删除数据,并且可以把这种变更通知到UI上,这就是ObservableCollection类最大地特点. 2.其他的实现了IEnumerable接口的集合,如List,Collection等. 凡是实现了IEnumerable接口的集合都可以作为列表绑定的数据集合,给列表的ItemSource属性赋值,但是没有实现INotifyCollectionChanged接口的集合就无法设置动态绑定,所以List,Collection等集合适合于绑定静态数据,也就是绑定了列表控件之后就不需要再对项目进行插入和删除操作. 3.自定义实现集合. 当win10内置集合类无法满足需求时,可以通过自定义集合来封装数据绑定的集合的逻辑,自定义集合类就需要根据所需的功能去实现IEnumerable,INotifyCollectionChanged等相关的接口.最常用的实现方案是使用IList接口,因为它提供可以按索引逐个访问的对象的非泛型集合,因而可提供最佳性能. 11.22.绑定列表控件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public class Food { public string Name { get; set ; } public string Description { get; set ; } public string IconUri { get; set ; } public string Type { get; set ; } } public List<Food> AllFood { get; set ; } public void AddFruits () { AllFood = new List<Food>(); Food item0 = new Food () { Name = "Tomato" , IconUri = "Images/Tomato.png" , Type = "Healthy" , Description = "Tomato tastes delicious. " }; Food item1 = new Food () { Name = "Beer" , IconUri = "Images/Beer.png" , Type = "NotDetermined" , Description = "Dont konw Beer is Delicious." }; Food item2 = new Food () { Name = "fries" , IconUri = "Images/fries.png" , Type = "Unhealthy" , Description = "This is Unhealthy food." }; AllFood.Add(item0); AllFood.Add(item1); AllFood.Add(item2); listviewItemtemplate.ItemsSource = AllFood; } <ListView x:Name="listviewItemtemplate" > <ListView.ItemTemplate> <!--列表的ItemTemplate属性是一个DataTemplate类型--> <!--创建一个DataTemplate的元素对象--> <DataTemplate> <StackPanel Orientation="Horizontal" Background="Gray" Margin="10" > <!--绑定Food类的IconUri属性--> <Image Source="{Binding IconUri}" Stretch="None" /> <TextBlock Text="{Binding Name}" FontSize="40" Margin="24 0 24 0" /> <TextBlock Text="{Binding Description}" FontSize="20" /> </StackPanel> </DataTemplate> </ListView.ItemTemplate> </ListView>
11.23.绑定ObservableCollection集合 在列表绑定中实现了把集合的数据通过数据模板展示到列表上,使用DataTemplate可以很灵活的去实现列表项的显示效果,但是这个列表却是一个静态的列表,也就是说列表的数据并不会增加或减少.如果要实现一个动态8绑定的列表,就需要用到ObservableCollection集合作为数据的绑定源.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 <Grid> <Grid.RowDefinitions> <RowDefinition Height="*" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <StackPanel Margin="12,60,0,28" > <Button Content="AddItem" Click="Button_Click_20" /> <Button Content="RemoveItem" Click="Button_Click_21" /> </StackPanel> <Grid Margin="12,0,12,0" Grid.Row="1" > <ListView x:Name="Obser" > <ListView.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal" Margin="10" > <TextBlock Text="{Binding OrderId}" FontSize="30" /> <TextBlock Text="{Binding OrderName}" FontSize="30" Width="280" /> </StackPanel> </DataTemplate> </ListView.ItemTemplate> </ListView> </Grid> </Grid> ObservableCollection<OrderModel> OrderModels=new ObservableCollection<OrderModel>(); /// <summary> /// Add /// </summary> /// <param name="sender" ></param> /// <param name="e" ></param> private void Button_Click_20(object sender, RoutedEventArgs e) { Random random= new Random(); OrderModels.Add(new OrderModel { OrderId = random.Next(1000), OrderName = "OrderName" + random.Next(1000) }); } private void Button_Click_21(object sender, RoutedEventArgs e) { if (Obser.SelectedItem != null) { OrderModel orderModel= Obser.SelectedItem as OrderModel; if (OrderModels.Contains(orderModel)) { OrderModels.Remove(orderModel); } } } listviewItemtemplate.ItemsSource = AllFood; Obser.ItemsSource = OrderModels;
11.24.绑定自定义集合 实现自定义的集合最常用的就是从IList接口派生实现自定义集合类. IList接口表示可按照索引单独访问的对象的非泛型集合.IList接口是从ICollection接口派生出来的,并且是所有非泛型列表的基接口,ICollection则是从IEnumerable接口派生出来的,ICollection接口是定义所有非泛型集合的大小,枚举数和同步方法,IEnumerable接口是指公开枚举数,该枚举数支持在非泛型集合上进行简单迭代.所以IList接口同时具备ICollection接口和IEnumerable接口的共性,IEnumerable只包含一个方法,GetEnumerator,返回IEnumerator,IEnumerator可以通过集合循环显示Current属性和MoveNext和Rest方法. ICollecion接口方法和属性 Count 该属性可确定集合中的元素个数,它返回的值与Length属性相同. IsSychoronized 该属性确定集合是否是纯种安全的,对于数组,这个属性总是返回false,对于同步访问,SyncRoot属性可以用于线程安全的访问. CopyTo 该方法可以将数组的元素复制到现有的数组中,它类似于表态方法Array.Copy(). IList接口方法和属性 Add 该方法用于在集合中添加元素,对于数组,该方法会抛出NotSupportedException异常. Clear 该方法会清除数组中的所有元素,值类型设置为0,引用类型设置为NULL Contains 该方法可以确定某个元素是否在数组中,其返回值是true或false.这个方法会对数组中的所有元素进行线性搜索,直到找到所需元素为止. IndexOf 该方法与Contains方法类似,也是对数组中的所有元素进行线性搜索,不同的是IndexOf方法会返回所找到的第一个元素的索引. Insert,Remove RemoveAt 对于集合,Insert方法用于插入元素,Remove和RemoveAt可删除元素.对于数组,这些方法都抛出NotSupportedException异常. IsFixedSize 数组的大小总是固定的,所以这个属性问题返回true. IsReadOnly 数组总是可读/写的,所以这个属性返回false. Item(表现显示为this[int index])该属性可以用整形索引访问数组. 只要实现了IList接口的集合类都可以与列表控件进行绑定,所以需要自定义一个集合类来实现IList接口,因为IList接口是从ICollection接口和IEnumerable接口派生出来的,所以这三个接口的方法都需要在自定义的集合类里面实现.在ICollection接口里面,Count属性表示列表的长度,IList接口的IList.this[int index]属性表示列表某个索引的数据项,在自定义集合里面可以通过Count属性设置列表的长度,通过IList.this[int index]属性返回集合数据,所以,在自定义集合里面IList.this[int index]属性和Count属性是两个必须要实现的属性接口, 可以在IList.this[int index]属性的实现里面处理集合项生成的逻辑. 下面给出自定义集合并与列表控件实现数据绑定.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 public class MyList : IList { /// <summary> /// 在这个属性里面,index表示集合项的索引,通过索引返回该索引对应的对象 /// </summary> /// <param name="index" ></param> /// <returns></returns> /// <exception cref="NotImplementedException" ></exception> public object this[int index] { get { //随机生成一个OrderModel对象并返回 Random random=new Random(); return new OrderModel { OrderId = random.Next(1000), OrderName = "OrderName" + random.Next(1000) }; } set => throw new NotImplementedException(); } public bool IsFixedSize => throw new NotImplementedException(); public bool IsReadOnly => throw new NotImplementedException(); public int Count => 10; public bool IsSynchronized => throw new NotImplementedException(); public object SyncRoot => throw new NotImplementedException(); public int Add(object value) { throw new NotImplementedException(); } public void Clear () { throw new NotImplementedException(); } public bool Contains(object value) { throw new NotImplementedException(); } public void CopyTo(Array array, int index) { throw new NotImplementedException(); } public IEnumerator GetEnumerator () { throw new NotImplementedException(); } public int IndexOf(object value) { throw new NotImplementedException(); } public void Insert(int index, object value) { throw new NotImplementedException(); } public void Remove(object value) { throw new NotImplementedException(); } public void RemoveAt(int index) { throw new NotImplementedException(); } } <ListView x:Name="Ilist" > <ListView.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal" Margin="10" > <TextBlock Text="{Binding OrderId}" FontSize="30" /> <TextBlock Text="{Binding OrderName}" FontSize="30" Width="280" /> </StackPanel> </DataTemplate> </ListView.ItemTemplate> </ListView> Ilist.ItemsSource =new MyList();
12.列表编程 12.1.列表控件的使用 在Win10中相关的列表控件有ItemsControl控件,ListBox控件,ListView控件,GridView控件和SemanticZoom控件则是更高级的列表控件.最简单展示数据:ItemsControl,实现列表的选择等功能:ListView,实现网格布局:GridView,如果要实现分组索引的列表,可以选用SemanticZoom控件. 12.11.ItemsControl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <ItemsControl x:Name="itemsControl2" > <ItemsControl.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal" > <TextBlock Text="{Binding FirstName}" FontSize="30" /> <TextBlock Text="{Binding LastName}" FontSize="30" Margin="30,0,0,0" /> </StackPanel> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> //创建一个有100个数据项的集合绑定到列表 List<Item> items= new List<Item>(); for (int i = 0; i < 100; i++) { items.Add(new Item { FirstName="Li" +i,LastName="Lei" +i}); } itemsControl2.ItemsSource = items;
2.让ItemsControl控件滚动起来 ItemsControl控件内置的模板是不支持滚动的,如果要让ItemsControl的数据滚动起来,需要自定义ItemsControl的控件模板,把ItemsControl控件的数据项面板放在ScrollViewer控件上,就可以让列表的数据滚动起来了.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <ItemsControl x:Name="ItemsControl3" > <ItemsControl.Template> <ControlTemplate TargetType="ItemsControl" > <ScrollViewer> <ItemsPresenter/> </ScrollViewer> </ControlTemplate> </ItemsControl.Template> <ItemsControl.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal" > <TextBlock Text="{Binding FirstName}" FontSize="30" /> <TextBlock Text="{Binding LastName}" FontSize="30" Margin="30,0,0,0" /> </StackPanel> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl>
3.大数据量数据的绑定 ItemsControl控件初始化2000个数据项的时候耗费了非常多的时间.ItemsControl控件本身是不支持数据的虚拟化的, 还需要另外的布局处理. 12.12.ListBox实现下拉点击刷新列表 ListBox控件是在ItemsControl控件的基础上进行封装的,把列表项选择,虚拟化等功能集成在一起. 下面用ListBox实现一个下拉点击刷新列表的交互效果.通常当一个列表包含很多数据,可以采用这种方式来分布加载列表的数据,特别是当列表的数据从网络请求时,这种分步加载的方式优势就更加明显了. 1.点击按钮放哪里 通过修改ListBox控件的ControlTemplate,把Button控件放在ScrollViewer里面,然后按钮就可以跟随列表滚动,点击按钮发出刷新的逻辑.第二个问题解决方式是使用ObservableCollection集合类型来存储集合的数据, 跟列表进行绑定,ObservableCollection表示一个动态数据集合,在添加项,移除项或刷新整个列表时,此集合将向列表提供通知从而可以刷新列表.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 <ListBox ItemsSource="{Binding Items}" SelectionChanged="ListBox_SelectionChanged" > <ListBox.Template> <ControlTemplate TargetType="ItemsControl" > <ScrollViewer> <StackPanel> <ItemsPresenter/> <Button Content="加载更多" Click="Button_Click_22" HorizontalAlignment="Center" Margin="12" /> </StackPanel> </ScrollViewer> </ControlTemplate> </ListBox.Template> <ListBox.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal" > <TextBlock Text="{Binding FirstName}" FontSize="30" /> <TextBlock Text="{Binding LastName}" FontSize="30" Margin="30,0,0,0" /> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> public ObservableCollection<Item> Items { get; set ; } Items = new ObservableCollection<Item>(); for (int i = 0; i < 5; i++) { Items.Add(new Item { FirstName="Li" +i,LastName="Lei" +i}); } this.DataContext = this; /// <summary> /// 按钮单击事件,加载更多的数据集合 /// </summary> /// <param name="sender" ></param> /// <param name="e" ></param> private void Button_Click_22(object sender, RoutedEventArgs e) { //往原来列表的集合,继续添加5个数据项,这同时触发列表UI发生变化 int count=Items.Count; for (int i = count; i < count+5; i++) { Items.Add(new Item { FirstName="Li" +i,LastName="Lei" +i}); } } /// <summary> /// 选中事件的处理程序 /// </summary> /// <param name="sender" ></param> /// <param name="e" ></param> private async void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e) { string selectedInfo = "" ; //获取选中的项目 foreach (var item in e.AddedItems) { selectedInfo += (item as Item).FirstName + (item as Item).LastName; } await new MessageDialog(selectedInfo).ShowAsync(); }
12.13.ListView实现下拉自动刷新列表 ListView比ListBox功能更加强大,在列表的外观上,ListView控件可以直接通过ContainerContentChanging事件来监控到相关的列表数据加载的情况,也就是说,可以通过ContainerContentChanging事件间接的获取到列表数据虚拟化的运行情况. 前面的章节介绍过一个使用ListBox控件判断列表滚动到底的例子.实现的原理是通过可视化树获取ListBox的ScrollViewer控件,然后根据ScrollViewer控件的垂直位移属性来判断ListBox控件什么时候滚动到底.在ListView控件里面,可以使用一种更加智能的方式来实现下拉刷新.这个例子是通过ListView控件的ContainerContentChanging事件去控制自动刷新的逻辑,因为ListView控件是对数据进行虚拟化处理的,当列表向下滚动的时候下面的数据就会不断地被实例化,当数据实例化的时候就会触发ContainerContentChanging事件.所以只需要监控当列表最后一个数据实例化的时候就可以发出数据刷新的逻辑就可以了.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 <ListView x:Name="listView" ItemsSource="{Binding Items}" > <ListView.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal" > <TextBlock Text="{Binding FirstName}" FontSize="30" /> <TextBlock Text="{Binding LastName}" FontSize="30" Margin="30,0,0,0" /> </StackPanel> </DataTemplate> </ListView.ItemTemplate> </ListView> Items = new ObservableCollection<Item>(); for (int i = 0; i < 100; i++) { Items.Add(new Item { FirstName="Li" +i,LastName="Lei" +i}); } this.DataContext = this; //订阅列表的ContainerContentChanging事件 listView.ContainerContentChanging += ListView_ContainerContentChanging; /// <summary> /// 在这里判断刷新的时机 /// </summary> /// <param name="sender" ></param> /// <param name="args" ></param> /// <exception cref="NotImplementedException" ></exception> private void ListView_ContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args) { //因为该事件会被多个线程进入,所以添加线程锁,控制下面的代码只能单个线程去执行 lock (o) { if (!isLoading) { if (args.ItemIndex==listView.Items.Count-1) { //设置IsLoading为true ,在加载数据的过程中,禁止多次进入 isLoading= true ; //模拟后台耗时任务拉取数据的场景 Task.Factory.StartNew(async () => { await Task.Delay(3000); //调用UI线程添加数据 await this.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => { int count = Items.Count; for (int i = count; i < count + 50; i++) { Items.Add(new Item { FirstName = "Li" + i, LastName = "Lei" + i }); } //修改加载的状态 isLoading = false ; }); }); } } } } //线程锁的对象 private object o = new object(); //数据加载的标识 public bool isLoading = false ; //绑定的数据集合 public ObservableCollection<Item> Items { get; set ; }
12.14.GridView实现网格列表 网格列表是指列表按照网格布局的方式进行布局 GridView控件的使用方式和ListView控件是一样的,通过ItemTemplate来设置列表项目的模板,不过在GridView控件设置ItemTemplate模板的时候要注意设置它的高度和宽度,GridView控件的布局就会按照Item实际的大小进行布局,可能导致网格布局的错乱.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <GridView x:Name="gridView" > <GridView.ItemTemplate> <DataTemplate> <StackPanel> <TextBlock Text="{Binding FirstName}" Width="80" /> <TextBlock Text="{Binding LastName}" Width="80" /> </StackPanel> </DataTemplate> </GridView.ItemTemplate> <GridView.ItemContainerStyle> <Style TargetType="GridViewItem" > <Setter Property="BorderBrush" Value="Gray" /> <Setter Property="BorderThickness" Value="1" /> <Setter Property="HorizontalContentAlignment" Value="Center" /> <Setter Property="VerticalContentAlignment" Value="Center" /> </Style> </GridView.ItemContainerStyle> </GridView> Items = new ObservableCollection<Item>(); for (int i = 0; i < 100; i++) { Items.Add(new Item { FirstName="Li" +i,LastName="Lei" +i}); } this.DataContext = this; gridView.ItemsSource= Items;
12.15.SemanticZoom实现分组列表 实现分组,同时会提供两个具有相同内容的不同视图,其中一个是主视图,另一个视图是可以让用户进行快速导航的分组视图. SemanticZoom控件支持对GridView和ListView控件的视图效果进行缩放,在SemanticZoom中包含两个列表控件(GridView或ListView):一个控件提供放大视图,另外一个提供缩小视图,放大视图提供一个详细信息视图(ZoomedInView)以让用户查看详细信息,缩小视图提供一个缩小索引视图(ZoomedOutView)让用户快速定位要查看信息的大致范围或者分组 1.SemanticZoom控件的样式设置 SemanticZoom控件实现分组列表会比实现非分组列表复杂一些,实现分组列表还需要设置两大属性的内容: ZoomedOutView内容和ZoomedInView内容.这两个属性内容含义如下所示
1 2 3 4 5 6 <SemanticZoom.ZoomedInView> 在这里放置GridView(或ListView)以表示放大视图,显示详细信息 </SemanticZoom.ZoomedInView> <SemanticZoom.ZoomOutView> 在这里放置GridView(或ListView)以表示缩小视图,一般情况下绑定Group.Title </SemanticZoom.ZoomOutView>
在赋值给ZoomInView属性的列表控件里面,一般需要设置ItemTemplate模板和GroupStyle.HeaderTemplate模板.ItemTemplate模板要设置的内容就是列表详细信息所展示的内容.G.H 模板是指分组的组头模板,如在人脉里面”a”,”b”这些就是属于列表的组头,如果同样是一个列表的集合,也是通过模板的绑定形式来进行定义. 在赋值给ZoomedOutView属性的列表控件里面,也需要设置其ItemTemplate模板,ZoomedOutView里面的ItemTemplate模板和ZoomedInView里面的模板的作用是不一样的,这里的ItemTemplate模板是指当点击组头的时候弹出的组头的索引面板项目展示,如点击人脉的a,b就会弹出一个字母的现实面板,当你点击某个字母的时候就会重新回到列表的界面并跳到列表字母所属的组项目的位置.同时还可以使用ItemsPanel来设置列表的布局,使用ItemContainerStyle来设置列表项目的容器样式,这些功能的使用和单独的GridView(或ListView)列表的使用是一样的. 2.SemanticZoom控件的数据源创建 SemanticZoom控件的数据源创建需要用到Windows.UI.Xaml.Data命名空间下的CollectionViewSource. CollectionViewSource是专为数据绑定UI视图互动而设置的,尤其是对于要实现分组的情况,更需要他.创建一个CollectionViewSource对象既可以使用Xaml的方式来进行,也可以使用C#代码来创建,在CollectionViewSource对象中通常需要设置下面几个重要的属性: 1.Source属性:设置分组后的数据源,赋值给Source属性的对象是列表嵌套列表的集合对象. 2.IsSourceGrouped属性:指示是否允许分组 3.ItemsPath属性:是分组后,组内部所包含列表的属性路径. 4.View属性:获取当前与CollectionViewSource实例关联的视图对象. 5.View.CollectionGroups属性:返回该视图关联的所有集合组. 在绑定数据时,需要把ZoomedInView里面的列表控件的ItemsSource绑定到CollectionViewSource对象的View属性,用于展示CollectionViewSource对象所关联的视图.把ZoomedOutView里面的列表控件的ItemSource绑定到CollectionViewSource对象的View.CollectionGroups属性,用于展示分组的视图.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 <Grid Margin="12,0,12,0" > <Grid.Resources> <!--创建数据源对象,注意ItemContent属性就是数据源中真正的基础数据的列表属性,必须设置该属性的值数据源才能定位到实际绑定的数据实体对象--> <CollectionViewSource x:Name="itemcollectSource" IsSourceGrouped="True" ItemsPath="ItemContent" /> </Grid.Resources> <SemanticZoom x:Name="semanticZoom" > <SemanticZoom.ZoomedInView> <!--在这里放置GridView(或ListView)以表示放大视图--> <ListView x:Name="inView" > <ListView.GroupStyle> <GroupStyle> <!--用于显示列表有的数据项的模板--> <GroupStyle.HeaderTemplate> <DataTemplate> <Border Background="Red" Height="80" > <TextBlock Text="{Binding Key}" FontSize="50" /> </Border> </DataTemplate> </GroupStyle.HeaderTemplate> </GroupStyle> </ListView.GroupStyle> <!--用于显示列表的数据项的模板--> <ListView.ItemTemplate> <DataTemplate> <StackPanel> <TextBlock Text="{Binding Title}" Height="40" FontSize="30" /> </StackPanel> </DataTemplate> </ListView.ItemTemplate> </ListView> </SemanticZoom.ZoomedInView> <SemanticZoom.ZoomedOutView> <!--在这里放置GridView(或ListView)以表示缩小视图--> <GridView x:Name="outView" > <!--用于显示弹出的分组列表视图的数据项的模板--> <GridView.ItemTemplate> <DataTemplate> <Border Height="60" > <TextBlock Text="{Binding Group.Key}" FontSize="24" /> </Border> </DataTemplate> </GridView.ItemTemplate> <!--列表布局模板--> <GridView.ItemsPanel> <ItemsPanelTemplate> <WrapGrid ItemWidth="100" ItemHeight="75" MaximumRowsOrColumns="1" VerticalChildrenAlignment="Center" /> </ItemsPanelTemplate> </GridView.ItemsPanel> <!--列表项目容器的样式设置--> <GridView.ItemContainerStyle> <Style TargetType="GridViewItem" > <Setter Property="BorderBrush" Value="Gray" /> <Setter Property="Background" Value="Red" /> <Setter Property="BorderThickness" Value="3" /> <Setter Property="HorizontalAlignment" Value="Center" /> <Setter Property="VerticalAlignment" Value="Center" /> </Style> </GridView.ItemContainerStyle> </GridView> </SemanticZoom.ZoomedOutView> </SemanticZoom> </Grid> </PivotItem> public void GetSezData () { //先创建一个普通的数据集合 List<BookItem> mainItem=new List<BookItem>(); for (int i = 0; i < 10; i++) { mainItem.Add(new BookItem { Content = "A类别" , Title = "TestA" + i }); mainItem.Add(new BookItem { Content = "B类别" , Title = "TestB" + i }); mainItem.Add(new BookItem { Content = "C类别" , Title = "TestC" + i }); } //使用LINQ语法把普通的数据集合转换为分组的数据集合. List<ItemInGroup> Items=(from item in mainItem group item by item.Content into newItems select new ItemInGroup { Key=newItems.Key,ItemContent=newItems.ToList()}).ToList(); //设置CollectionViewSource对象的数据源 this.itemcollectSource.Source= Items; //分别对两个视图进行绑定 outView.ItemsSource = itemcollectSource.View.CollectionGroups; inView.ItemsSource = itemcollectSource.View; } internal class ItemInGroup { public string Key { get; set ; } public List<BookItem> ItemContent { get; set ; } }
12.2.虚拟化技术 允许应用程序只把在屏幕当前和屏幕附近的UI元素初始化了,其他UI元素都是处于虚构的状态,还可以利用虚拟化技术来做更多的优化,如当虚拟化发生时,可以去主动的回收暂时不使用的内存,从而可以对程序暂用的内存进行优化.也可以利用虚拟化的布局控件去实现自定义的虚拟化的功能需求. 12.21.列表的虚拟化 标准布局系统可以创建项容器并为每个与列表控件关联的项计算布局.在自定义集合里面可以通过Count属性设置列表的长度,通过IList.this[int index]属性返回数据项和打印出相关的数据信息.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 public class VirtualDataList : IList { public object this[int index] { get { //当获取集合的某个数据项的时候把这个数据的索引项打印出来 Debug.WriteLine("当前加载的数据Data" +index.ToString()); return new Data { Name = "data" + index.ToString() }; } set => throw new NotImplementedException(); } public bool IsFixedSize => throw new NotImplementedException(); public bool IsReadOnly => throw new NotImplementedException(); public int Count => 1000; public bool IsSynchronized => throw new NotImplementedException(); public object SyncRoot => throw new NotImplementedException(); public int Add(object value) { throw new NotImplementedException(); } public void Clear () { throw new NotImplementedException(); } public bool Contains(object value) { throw new NotImplementedException(); } public void CopyTo(Array array, int index) { throw new NotImplementedException(); } public IEnumerator GetEnumerator () { throw new NotImplementedException(); } public int IndexOf(object value) { throw new NotImplementedException(); } public void Insert(int index, object value) { throw new NotImplementedException(); } public void Remove(object value) { throw new NotImplementedException(); } public void RemoveAt(int index) { throw new NotImplementedException(); } } <ListView ItemsSource="{Binding Data}" > <ListView.ItemTemplate> <DataTemplate> <StackPanel> <TextBlock Text="{Binding Name}" Height="50" /> </StackPanel> </DataTemplate> </ListView.ItemTemplate> </ListView>
12.22.VirtualizingStackPanel,ItemsStackPanel和ItemsWrapGrid虚拟化排列布局控件 都是虚拟化布局控件,一般情况下在界面的布局上很少会用到这些虚拟化排列的控件,大部分都是封装在列表的布局面板上使用,主要目的就是为了实现列表上大数据量的虚拟化,从而极大的提高列表的效率. 其实这3个虚拟化布局控件都是列表控件的默认布局排列的方式,其中VirtualizingStackPanel控件是ListBox的默认布局面板,ItemsStackPanel是ListView的默认布局面板,ItemsWrapGrid是GridView的默认布局面板. VirtualizingStackPanel控件和ItemsStackpanel控件都表示沿着水平方向或垂直方向将内容虚拟化的排列在一行上,所实现的排列布局效果和StackPanel控件是一样的,不同的是这些控件可以实现虚拟化的逻辑.对于数据较多的列表布局,使用VirtualizingStackPanel控件或ItemsStackPanel控件会比StackPanel高效很多,因为虚拟化控件只是把当前屏幕范围内的数据显示出来,其他的数据都通过虚拟化的技术进行处理,并没有进行UI的初始化显示,所以效率很高,ItemsWrapGrid控件实现的是网格的虚拟化布局效果,虚拟化原理和ItemsStackPanel控件类似,只不过排列的方式不一样. 如果使用ItemsControl列表控件来展示数据,要给这个列表增加虚拟化的功能,ItemsStackPanel对象元素必须包含在一个ItemsPanelTemplate中,给ItemsControl控件增加ItemsStackPanel虚拟化布局.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 public class ImageList : IList { public object this[int index] { get { //加载的图片是程序里面的图片资源,20张图片循环加载 int imageIndex = 20 - index % 20; Debug.WriteLine("加载的集合索引是:" +index); return new ImageItem { ImageName = "图片" + index, Image = new BitmapImage(new Uri($"ms-appx:///Assets/thumb/thumb_{imageIndex}.webp" , UriKind.RelativeOrAbsolute)) }; } set => throw new NotImplementedException(); } public bool IsFixedSize => throw new NotImplementedException(); public bool IsReadOnly => throw new NotImplementedException(); public int Count => 100; public bool IsSynchronized => throw new NotImplementedException(); public object SyncRoot => throw new NotImplementedException(); public int Add(object value) { throw new NotImplementedException(); } public void Clear () { throw new NotImplementedException(); } public bool Contains(object value) { throw new NotImplementedException(); } public void CopyTo(Array array, int index) { throw new NotImplementedException(); } public IEnumerator GetEnumerator () { throw new NotImplementedException(); } public int IndexOf(object value) { throw new NotImplementedException(); } public void Insert(int index, object value) { throw new NotImplementedException(); } public void Remove(object value) { throw new NotImplementedException(); } public void RemoveAt(int index) { throw new NotImplementedException(); } }
2.实现ItemsControl的横向虚拟化布局. 要实现ItemsControl的横向虚拟化布局,除了使用ItemsStackPanel控件的Horizontal布局,还需要在ItemsControl中设置ScrollViewer,HorizontalScrollBarVisibility=”Auto”,这样列表就可以水平滚动了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <ItemsControl x:Name="imageList" > <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <!--设置横向布局--> <ItemsStackPanel Orientation="Horizontal" /> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.Template> <ControlTemplate TargetType="ItemsControl" > <!--设置水平滚动--> <ScrollViewer ScrollViewer.HorizontalScrollBarVisibility="Visible" ScrollViewer.VerticalScrollBarVisibility="Disabled" > <!--<StackPanel>--> <ItemsPresenter/> <!--</StackPanel>--> </ScrollViewer> </ControlTemplate> </ItemsControl.Template> <ItemsControl.ItemTemplate> <DataTemplate> <StackPanel> <Image Source="{Binding Image}" Width="144" Height="240" Stretch="Uniform" /> <TextBlock Text="{Binding ImageName}" /> </StackPanel> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> imageList.ItemsSource = new ImageList();
12.24.大数据量网络图片列表的异步加载和内存优化 实现网络图片列表数据集合大加载:1.图片的加载比较耗时2.不断地滑动会让数据集合加载的图片占用的内存越来越高. 1.可以采用异步加载的方式来解决,这样的列表加载完之后,图片再显示出来,列表首次加载的速度会很快,我们可以通过后台线程调用网络请求下载图片,下载完图片之后再触发UI线程把图片加载出来. 2.要解决内存的问题,可以使用弱引用类型(WeakReference类)来存储图片的数据.弱引用就是不保证不被垃圾回收期回收的对象,它拥有比较短暂的生命周期,在垃圾回收器扫描它所管辖的内存区域过程中,一旦发现了只具有弱引用的对象,就会回收它的内存.不过一般情况下,垃圾回收器的线程优先级很低,也就不会很快发现那些只有弱引用的对象.当内存的使用影响程序流畅运行时,垃圾回收器就会按照优先次序把存在时间长的弱引用对象回收,而释放内存.所以弱引用特别适合在当前这种情况下,占用大量内存,但通过垃圾回收功能回收以后很容易重新创建的图片对象.图片下载完之后会存放在弱引用对象里面,当检查到数据被回收的时候,再进行异步加载,当然也可以把图片用独立存储存起来,这样就免去了再次请求网络的操作.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 <ListView x:Name="InternetImage" > <ListView.ItemTemplate> <DataTemplate> <StackPanel> <TextBlock Text="{Binding Name}" Height="80" /> <Image Source="{Binding ImageSource}" Width="200" Height="200" /> </StackPanel> </DataTemplate> </ListView.ItemTemplate> </ListView> public void GetInternetImage () { List<ImageData> Items=new List<ImageData>(); for (int i = 0; i < 1000; i++) { //在网络地址后面加上index=i是为了保证每个网络地址不一样 //这样就不会产生网络数据缓存,更加接近真实的网络图片列表 Items.Add(new ImageData { Name = "Test" + i, Page = this, ImageUri = new Uri("http://pic002.cnblogs.com/images/2012/152755/2012120917494440.png?index=" + i) }); } InternetImage.ItemsSource= Items; } public class ImageData : INotifyPropertyChanged { private Uri imageUri; public ImageData () { } public string Name { set ; get; } public Page Page { set ; get; } public Uri ImageUri { get => imageUri; set { if (imageUri == value) { return ; } imageUri = value; bitmapImage = null; } } //弱引用对象,用于存储下载好的图片对象 WeakReference bitmapImage; private ImageSource imageSource; //ImageSource属性用于绑定到列表的Image控件上 public ImageSource ImageSource { get { if (bitmapImage != null) { //如果弱引用没有回收,则取弱引用的值 if (bitmapImage.IsAlive) { return (ImageSource)bitmapImage.Target; } else { Debug.WriteLine("数据已经被回收" ); } } //弱引用已经被回收,那么则通过图片网路地址进行异步下载 if (ImageUri!=null) { Task.Factory.StartNew(() => { DownloadImage(imageUri); }); } return null; } } /// <summary> /// 下载图片的方法 /// </summary> /// <param name="imageUri" ></param> /// <exception cref="NotImplementedException" ></exception> private async void DownloadImage(Uri imageUri) { List<Byte> allBytes = new List<byte>(); Stream streamForUI; //通过网络下载图片数据 using (var response=await HttpWebRequest.Create(imageUri).GetResponseAsync()) { using (Stream responseStream=response.GetResponseStream()) { byte[] buffer = new byte[4000]; int bytesRead = 0; while ((bytesRead = await responseStream.ReadAsync(buffer,0,4000))>0) { allBytes.AddRange(buffer.Take(bytesRead)); } } } streamForUI = new MemoryStream((int)allBytes.Count); streamForUI.Write(allBytes.ToArray(),0,allBytes.Count); streamForUI.Seek(0, SeekOrigin.Begin); //触发UI线程处理位图和UI更新 await Page.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => { BitmapImage bm=new BitmapImage(); bm.SetSource(streamForUI.AsRandomAccessStream()); //把图片位图对象存放到弱引用对象里面 if (bitmapImage==null) { bitmapImage = new WeakReference(bm); } else { bitmapImage.Target = bm; //触发UI绑定属性的改变 PropertyChanged?.Invoke(this,new PropertyChangedEventArgs("ImageSource" )); } }); } /// <summary> /// 属性改变事件 /// </summary> /// <param name="proeprty" ></param> async void OnPropertyChanged(string proeprty) { var handler = PropertyChanged; await Page.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => { if (handler!=null) { handler(this,new PropertyChangedEventArgs(proeprty)); } }); } public event PropertyChangedEventHandler PropertyChanged; } }
图表编程 13.1.动态生成折线图和区域图 区域图其实就是在折线图的基础上实现了区域的显示效果,这两者非常类似. 13.11.折线图和区域图原理 折线图可以通过Polyline图形来进行创建,我们要实现的折线图就是一种特殊的Polyline图形.一般的折线图的X轴坐标都是等量递增的,Y轴的坐标是随意变化的,我们需要根据这种变化的规律来给Polyline控件库的Point属性来赋值.
1 <Polyline Stroke="LightBlue" StrokeThickness="5" Height="150" Points="0,10 50,40 100,90 150,50 200,50 250,10 300,100 350,30 400,0" />
区域图的规则和折线图是类似的,只不过区域图是一个闭合的图形,相当于把折线图和X轴组合起来形成一个区域,区域图可以通过Polygon图形去实现,Polygon图形和Polyline图形的区别在于一个是闭合的而另一个是非闭合的,这也是区域图和折线图的区别.
1 <Polygon Fill="AliceBlue" StrokeThickness="5" Height="150" Stroke="Red" Points="0,150 0,10 50,40 100,90 150,50 200,50 250,10 300,100 350,30 400,0 400,150" />
13.12.生成图形逻辑封装 创建折线图和区域图最关键的部分就是把相关的数据集合转换为X轴和Y轴的坐标,然后根据坐标生成图形. 下面实现了一个生成折线图的PointCollection的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 private PointCollection GetLineChartPointCollection(List<double> datas,double topheight,double perWidth,double topValue) { PointCollection pointCollection=new PointCollection(); double x = 0;//X坐标 foreach (var data in datas) { double y; //Y坐标 if (data > topValue) y = 0; else y = (topheight - (data * topheight) / topValue); Point point =new Point(x,y); pointCollection.Add(point); x += perWidth; } return pointCollection; }
该方法通过参数传递进行折线图表展示的数据集合,图表的最高高度,两个数据之间的X轴的间隔和图表数值的最大的值,j然后根据这些数据来产生一个坐标的点集合.图表数值的最大值是为了控制数据集合里面的数值相差太大而导致图表显示异常,所以做了一个最大值的控制.如果datas数据里面有比topValue大的数据,将会用topValue来代替,Y坐标的产生公式是y=(topHeight-(data*topHeight)/topValue),计算出点和顶部的距离就是Y坐标,X坐标就是有规律地递增. 定义好PointCollection的生成方法后,就可以在UI上生成折线图了,下面是UI上的代码,通过Button事件调用GetLineChartPointCollection方法生成一个折线图.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <Grid Margin="12,0,12,0" > <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <Grid x:Name="chartCanvas" Height="400" HorizontalAlignment="Center" VerticalAlignment="Center" > </Grid> <StackPanel Grid.Row="1" > <Button Content="折线图" Click="Button_Click_23" /> <Button Content="区域图" Click="Button_Click_24" /> </StackPanel> </Grid> /// <summary> /// 生成折线图 /// </summary> /// <param name="sender" ></param> /// <param name="e" ></param> private void Button_Click_23(object sender, RoutedEventArgs e) { chartCanvas.Children.Clear(); List<double> datas=new List<double>() { 23,23,45,26,45,36,29,30,27,38,36,52,27,35}; PointCollection pointCollection = GetLineChartPointCollection(datas,400,30,100); Polyline polyline=new Polyline () { Points=pointCollection,Stroke=new SolidColorBrush(Colors.Red)}; chartCanvas.Children.Add(polyline); }
生成区域图,如果直接使用Polygon图形来生成区域图,只需要在GetLineChartPointCollection方法里面添加上一个开始点在X轴的映射坐标和一个结束点在X轴的映射坐标就可以了.除了使用Polygon图形来实现,还可以用Path图形来实现,实现的效果也是一样的.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 private PathGeometry GetLineChartPathGeometry(List<double> datas, double topHeight, double perWidth, double topValue) { PathGeometry pathGeometry=new PathGeometry(); PathFigureCollection pathFigures=new PathFigureCollection(); //使用数据集合第一个点在X轴的投影点作为Path图形的开始点 PathFigure pathFigure=new PathFigure () { StartPoint=new Point(0,topHeight)}; //新建一个PathSegmentCollection集合来添加LineSegment线段的对象 PathSegmentCollection pathSegmentCollection=new PathSegmentCollection(); double x = 0; //x坐标 foreach (double data in datas) { double y;//Y坐标 if (data > topValue) y = 0; else y = (topHeight - (data * topHeight) / topValue); Point point=new Point(x,y); LineSegment lineSegment=new LineSegment () { Point=point}; pathSegmentCollection.Add(lineSegment); x+= perWidth; } x-=perWidth; LineSegment lineSegmentEnd = new LineSegment () { Point = new Point(x, topHeight) }; pathSegmentCollection.Add(lineSegmentEnd); pathFigure.Segments = pathSegmentCollection; pathFigures.Add(pathFigure); pathGeometry.Figures = pathFigures; return pathGeometry; } /// <summary> /// 生成区域图 /// </summary> /// <param name="sender" ></param> /// <param name="e" ></param> private void Button_Click_24(object sender, RoutedEventArgs e) { chartCanvas.Children.Clear(); List<double> datas = new List<double> { 23, 23, 45, 26, 45, 36, 29, 30, 27, 38, 36, 52, 27, 35 }; PathGeometry pathGeometry = GetLineChartPathGeometry(datas,400,30,100); Path path = new Path { Data = pathGeometry, Fill = new SolidColorBrush(Colors.Red) }; chartCanvas.Children.Add(path); }
13.2.实现饼图控件 要实现一个饼图控件,首先要实现饼图片形状,有一些重要的属性,如饼图半径Radius,内圆半径InnerRadius,旋转角度RotationAngle,片形角度WedgeAngle,点innerArcStartPoint,点innerArcEndPoint,点outerArcStartPoint和outerArcEndPoint,这4个点的坐标需要通过半径和角度相关的属性计算出来,计算出这4个点的坐标之后,通过这4个点创建一个Path图形,这个Path图形由两条直线和两条弧线组成,形成了一个饼图片形形状,通过这种方式不仅把这个饼图片形形状创建好了,连这个图形在整个饼图的位置也设置好了.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 /// <summary> /// 自定义饼图图片形形状 /// </summary> public class PiePiece:Path { /// <summary> /// 饼图半径 /// </summary> public double Radius { get { return (double)GetValue(RadiusProperty); } set { SetValue(RadiusProperty, value); } } // Using a DependencyProperty as the backing store for Radius. This enables animation, styling, binding, etc... public static readonly DependencyProperty RadiusProperty = DependencyProperty.Register("Radius" , typeof(double), typeof(PiePiece), new PropertyMetadata(0)); /// <summary> /// 距离饼图中心的距离 /// </summary> public double PushOut { get { return (double)GetValue(PushOutProperty); } set { SetValue(PushOutProperty, value); } } // Using a DependencyProperty as the backing store for PushOut. This enables animation, styling, binding, etc... public static readonly DependencyProperty PushOutProperty = DependencyProperty.Register("PushOut" , typeof(double), typeof(PiePiece), new PropertyMetadata(0)); /// <summary> /// 饼图内圆半径 /// </summary> public double InnerRadius { get { return (double)GetValue(InnerRadiusProperty); } set { SetValue(InnerRadiusProperty, value); } } // Using a DependencyProperty as the backing store for InnerRadius. This enables animation, styling, binding, etc... public static readonly DependencyProperty InnerRadiusProperty = DependencyProperty.Register("InnerRadius" , typeof(double), typeof(PiePiece), new PropertyMetadata(0)); /// <summary> /// 饼图片形的角度 /// </summary> public double WedgeAngle { get { return (double)GetValue(WedgeAngleProperty); } set { SetValue(WedgeAngleProperty, value); } } // Using a DependencyProperty as the backing store for WedgeAngle. This enables animation, styling, binding, etc... public static readonly DependencyProperty WedgeAngleProperty = DependencyProperty.Register("WedgeAngle" , typeof(double), typeof(PiePiece), new PropertyMetadata(0)); /// <summary> /// 旋转的角度 /// </summary> public double RotationAngle { get { return (double)GetValue(RotationAngleProperty); } set { SetValue(RotationAngleProperty, value); } } // Using a DependencyProperty as the backing store for RotationAngle. This enables animation, styling, binding, etc... public static readonly DependencyProperty RotationAngleProperty = DependencyProperty.Register("RotationAngle" , typeof(double), typeof(PiePiece), new PropertyMetadata(0)); /// <summary> /// 注册中心点的X坐标属性 /// </summary> public double CentreX { get { return (double)GetValue(CentreXProperty); } set { SetValue(CentreXProperty, value); } } // Using a DependencyProperty as the backing store for CentreX. This enables animation, styling, binding, etc... public static readonly DependencyProperty CentreXProperty = DependencyProperty.Register("CentreX" , typeof(double), typeof(PiePiece), new PropertyMetadata(0)); /// <summary> /// 中心点的Y坐标 /// </summary> public double CentreY { get { return (double)GetValue(CentreYProperty); } set { SetValue(CentreYProperty, value); } } // Using a DependencyProperty as the backing store for CentreY. This enables animation, styling, binding, etc... public static readonly DependencyProperty CentreYProperty = DependencyProperty.Register("CentreY" , typeof(double), typeof(PiePiece), new PropertyMetadata(0)); /// <summary> /// 饼图片形所占饼图的百分比 /// </summary> public double Percentage { get { return (double)GetValue(PercentageProperty); } set { SetValue(PercentageProperty, value); } } // Using a DependencyProperty as the backing store for Percentage. This enables animation, styling, binding, etc... public static readonly DependencyProperty PercentageProperty = DependencyProperty.Register("Percentage" , typeof(double), typeof(PiePiece), new PropertyMetadata(0)); public double PieceValue { get { return (double)GetValue(PieceValueProperty); } set { SetValue(PieceValueProperty, value); } } // Using a DependencyProperty as the backing store for PieceValue. This enables animation, styling, binding, etc... public static readonly DependencyProperty PieceValueProperty = DependencyProperty.Register("PieceValue" , typeof(double), typeof(PiePiece), new PropertyMetadata(0)); private double lastWidth=0; private double lastHeight=0; private PathFigure figure; public PiePiece () { CreatePathData(0,0); } private void CreatePathData(double width, double height) { //用于退出布局的循环逻辑 if (lastWidth == width && lastHeight == height) return ; lastWidth = width; lastHeight = height; Point startPoint = new Point(CentreX,CentreY); //计算饼图片形内圆弧的开始点 Point innerArcStartPoint = ComputeCartesianCCoordinate(RotationAngle,InnerRadius); //根据中心点来校正坐标的位置 innerArcStartPoint = Offset(innerArcStartPoint,CentreX,CentreY); //计算饼图片形内圆弧的结束点 Point innerArcEndPoint=ComputeCartesianCCoordinate(RotationAngle+WedgeAngle,InnerRadius); innerArcEndPoint = Offset(innerArcEndPoint,CentreX,CentreY); //计算饼图片形外圆弧的开始点 Point outerArcStartPoint = ComputeCartesianCCoordinate(RotationAngle,Radius); outerArcStartPoint = Offset(outerArcStartPoint,CentreX,CentreY); //计算饼图片形外圆弧的结束点 Point outerArcEndPoint=ComputeCartesianCCoordinate(RotationAngle+WedgeAngle,Radius); outerArcEndPoint = Offset(outerArcEndPoint,CentreX,CentreY); //判断饼图片形的角度是否大于180度 bool largeArc = WedgeAngle > 180.0; //把扇面饼图往偏离中心点推出一部分 if (PushOut>0) { Point offset = ComputeCartesianCCoordinate(RotationAngle+WedgeAngle/2,PushOut); //根据偏移量来重新设置圆弧的坐标 innerArcStartPoint = Offset(innerArcStartPoint,offset.X,offset.Y); innerArcEndPoint = Offset(innerArcEndPoint,offset.X,offset.Y); outerArcStartPoint = Offset(outerArcStartPoint,offset.X,offset.Y); outerArcEndPoint = Offset(outerArcEndPoint,offset.X,offset.Y); } //外圆的大小 Size outerArcSize = new Size(Radius,Radius); //内圆的大小 Size innerArcSize = new Size(InnerRadius,InnerRadius); var geometry = new PathGeometry(); figure = new PathFigure(); //从内圆开始坐标开始画一个闭合的扇形图形 figure.StartPoint = innerArcStartPoint; AddLine(outerArcStartPoint); AddArc(outerArcEndPoint,outerArcSize,largeArc,SweepDirection.Clockwise); AddLine(innerArcEndPoint); AddArc(innerArcStartPoint,innerArcSize,largeArc,SweepDirection.Counterclockwise); figure.IsClosed= true ; geometry.Figures.Add(figure); this.Data= geometry; } protected override Size MeasureOverride(Size availableSize) { return availableSize; } protected override Size ArrangeOverride(Size finalSize) { CreatePathData(finalSize.Width,finalSize.Height); return finalSize; } //把点进行偏移转换 private Point Offset(Point point,double offsetX,double offsetY) { point.X += offsetX; point.Y += offsetY; return point; } /// <summary> /// 根据角度和半径来计算出圆弧上的点的坐标 /// </summary> /// <param name="rotationAngle" ></param> /// <param name="innerRadius" ></param> /// <returns></returns> /// <exception cref="NotImplementedException" ></exception> private Point ComputeCartesianCCoordinate(double angle, double radius) { //转换成弧度单位 double angleRad = (Math.PI / 180.0) * (angle-90); double x=radius*Math.Cos(angleRad); double y=radius*Math.Sin(angleRad); return new Point(x, y); } /// <summary> /// 在图形中添加一个点 /// </summary> /// <param name="x" ></param> /// <param name="y" ></param> private void AddPoint(double x,double y) { LineSegment segment= new LineSegment(); segment.Point= new Point(x+0.5*StrokeThickness,y+0.5*StrokeThickness); } /// <summary> /// 在图形中添加一条线段 /// </summary> /// <param name="point" ></param> private void AddLine(Point point) { LineSegment segment= new LineSegment(); segment.Point= point; figure.Segments.Add(segment); } /// <summary> /// 在图形中添加一个圆弧 /// </summary> /// <param name="point" ></param> /// <param name="size" ></param> /// <param name="largeArc" ></param> /// <param name="sweepDirection" ></param> private void AddArc(Point point,Size size,bool largeArc,SweepDirection sweepDirection) { ArcSegment segment= new ArcSegment(); segment.Point= point; segment.Size= size; segment.IsLargeArc= largeArc; segment.SweepDirection= sweepDirection; figure.Segments.Add(segment); } }
13.22.封装饼图控件 创建好了PiePiece形状之后,下面开始就要开始创建利用PiePiece形状来创建饼图控件了.创建饼图控件是通过UserControl控件来实现的,UserControl控件的Xaml代码里面只有一个Grid面板,用来加载PiePiece形状来组成饼图. 在饼图控件里面需要自定义一些相关的属性,用来传递相关的参数.属性HoleSize表示饼图内圆的大小,按照比例来计算,属性pieWidth表示饼图的宽度.饼图的数据集合是通过控件数据的上下文属性DataContext属性来传递,在初始化饼图的时候需要把DataContext的数据读取出来然后再创建PiePiece图形,每个PiePiece图形都添加了Tap事件,用来实现当用户单击饼图的时候,相应的某一块回往外推出去.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 <UserControl x:Class="App1.Controls.PiePlotter" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:App1.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400" FontFamily="{StaticResource PhoneFontFamilyNormal}" > <Grid x:Name="LayoutRoot"> </Grid> </UserControl> using App1.Models; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices.WindowsRuntime; using Windows.Foundation; using Windows.Foundation.Collections; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls.Primitives; using Windows.UI.Xaml.Data; using Windows.UI.Xaml.Input; using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Navigation; //https://go.microsoft.com/fwlink/?LinkId=234236 上介绍了“用户控件”项模板 namespace App1.Controls { /// <summary> /// 饼图控件 /// </summary> public sealed partial class PiePlotter : UserControl { public PiePlotter() { this.InitializeComponent(); } #region 依赖属性 //注册内圆大小属性 /// <summary> /// 内圆的大小,按比例来计算 /// </summary> public double HoleSize { get { return (double)GetValue(HoleSizeProperty); } set { SetValue(HoleSizeProperty, value); } } // Using a DependencyProperty as the backing store for HoleSize. This enables animation, styling, binding, etc... public static readonly DependencyProperty HoleSizeProperty = DependencyProperty.Register("HoleSize", typeof(double), typeof(PiePlotter), new PropertyMetadata(0)); /// <summary> /// 饼图宽度 /// </summary> public double PieWidth { get { return (double)GetValue(PieWidthProperty); } set { SetValue(PieWidthProperty, value); } } // Using a DependencyProperty as the backing store for PieWidth. This enables animation, styling, binding, etc... public static readonly DependencyProperty PieWidthProperty = DependencyProperty.Register("PieWidth", typeof(double), typeof(PiePlotter), new PropertyMetadata(0)); #endregion //饼图的片形PiePiece的集合 private List<PiePiece> piePieces= new List<PiePiece>(); //选中的当前饼图的数据项 private PieDataItem CurrentItem; //初始化展示饼图的方法 public void ShowPie() { //获取控件的数据上下文,转换成数据集合 List<PieDataItem> myCollectionView = (List< PieDataItem >)this.DataContext; if (myCollectionView == null) return; //半径的大小 double halfWidth = PieWidth / 2; //内圆半径大小 double innerRadius = halfWidth * HoleSize; //计算图表数据的总和 double total = 0; foreach (PieDataItem item in myCollectionView) { total += item.Value; } //通过PiePiece构建饼图 LayoutRoot.Children.Clear(); piePieces.Clear(); double accumulativeAngle = 0; foreach (PieDataItem item in myCollectionView) { bool selectedItem = item == CurrentItem; double wedgeAngle = item.Value * 360 / total; //根据数据来创建饼图的每一块图形 PiePiece piePiece= new PiePiece() { Radius=halfWidth, InnerRadius=innerRadius, CentreX=halfWidth, CentreY=halfWidth, PushOut=(selectedItem?10.0:0), WedgeAngle=wedgeAngle, PieceValue=item.Value, RotationAngle=accumulativeAngle, Fill=item.Brush, Tag=item }; //添加饼图片形的点击事件 piePiece.Tapped += PiePiece_Tapped; piePieces.Add(piePiece); LayoutRoot.Children.Add(piePiece); accumulativeAngle+= wedgeAngle; } } private void PiePiece_Tapped(object sender, TappedRoutedEventArgs e) { PiePiece piePiece = sender as PiePiece; CurrentItem = piePiece.Tag as PieDataItem; ShowPie(); } } }
13.3.线性报表 一个完整的线性报表包括网格图,坐标轴,图例和线性图形这四部分.网格图是指报表背景的网格,主要目的是清晰的查看坐标点的位置.坐标轴是水平坐标体系的X轴和Y轴,图例是集中于报表一角或一侧的报表上各种符号和颜色所代表内容与指标的说明,有助于更好的认识报表.线性图形就是图表里面的主体图形. 13.31.实现图形表格和坐标轴 首先封装一个图表的基础样式的ChartStyle类,这个类主要定义了X轴,Y轴的坐标范围,网格面板的大小和把网格面板的点转换为坐标体系的点的方法.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 /// <summary> /// 图表基础样式类 /// </summary> public class ChartStyle { //X轴最小坐标 private double xmin = 0; //X轴最大坐标 private double xmax = 1; //Y轴最小坐标 private double ymin = 0; //Y轴最大坐标 private double ymax = 1; //网格面板 private Canvas chartCanvas; public ChartStyle () { } public double Xmin { get => xmin; set => xmin = value; } public double Xmax { get => xmax; set => xmax = value; } public double Ymin { get => ymin; set => ymin = value; } public double Ymax { get => ymax; set => ymax = value; } public Canvas ChartCanvas { get => chartCanvas; set => chartCanvas = value; } //定义网格面板的宽度和高度 public void ResizeCanvas(double width,double height) { ChartCanvas.Width = width; ChartCanvas.Height = height; } //把面板的点转换成为图表坐标体系的点坐标 public Point NormalizePoint(Point pt) { if (ChartCanvas.Width.ToString()=="Nan" ) ChartCanvas.Width = 400; if (ChartCanvas.Height.ToString() == "Nan" ) ChartCanvas.Height = 400; Point result = new Point(); result.X = (pt.X - Xmin) * ChartCanvas.Width / (Xmax - Xmin); result.Y=ChartCanvas.Height-(pt.Y-Ymin)*ChartCanvas.Height/ (Ymax - Ymin); return result; } }
从ChartStyle类派生出ChartStyleGridlines类表示网格线条类,通过ChartStyleGridlines类来初始化图表的网格线条和X轴,Y轴,ChartStyleGridlines类定义图表的标题,X轴和Y轴单位间距,网格颜色等属性.是用Line对象来绘制网格的图表.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 /// <summary> /// 网格线条类 /// </summary> public class ChartStyleGridlines : ChartStyle { private string title; private Canvas textCanvas1; private bool isXGrid = true ; private bool isYGrid = true ; private Brush gridlineColor = new SolidColorBrush(Colors.LightGray); private double xTick = 1; private double yTick = 0.5; private GridlinePatternEnum gridlinePattern; //图表标题 public string Title { get => title; set => title = value; } //添加X轴和Y轴的面板 public Canvas textCanvas { get => textCanvas1; set => textCanvas1 = value; } //是否创建水平线条 public bool IsXGrid { get => isXGrid; set => isXGrid = value; } //是否创建垂直线条 public bool IsYGrid { get => isYGrid; set => isYGrid = value; } /// <summary> /// 网格颜色画刷 /// </summary> public Brush GridlineColor { get => gridlineColor; set => gridlineColor = value; } //X轴单位间距 public double XTick { get => xTick; set => xTick = value; } public double YTick { get => yTick; set => yTick = value; } //线条类型 public GridlinePatternEnum GridlinePattern { get => gridlinePattern; set => gridlinePattern = value; } private double leftOffset = 20; private double rightOffset = 10; private double bottomOffset = 15; private Line gridline = new Line(); public ChartStyleGridlines () { title = "Title" ; } /// <summary> /// 添加网格图表的样式 /// </summary> /// <param name="tbTitle" ></param> public void AddChartStyle(TextBlock tbTitle) { Point pt = new Point(); Line tick=new Line(); double offset = 0; double dx, dy; TextBlock tb = new TextBlock(); //确定右边的偏移量 tb.Text = Xmax.ToString(); tb.Measure(new Size(Double.PositiveInfinity,double.PositiveInfinity)); Size size = tb.DesiredSize; rightOffset = size.Width / 2 + 2; //确定左边的偏移量 for (dy =Ymin; dy <=Ymax; dy+=YTick) { pt = NormalizePoint(new Point(Xmin,dy)); tb=new TextBlock(); tb.Text = dy.ToString(); tb.TextAlignment = TextAlignment.Right; tb.Measure(new Size(double.PositiveInfinity,double.PositiveInfinity)); size= tb.DesiredSize; if (offset < size.Width) offset = size.Width; } leftOffset = offset + 5; ChartCanvas.Width=textCanvas.Width-leftOffset-rightOffset; ChartCanvas.Height = textCanvas.Height - bottomOffset - size.Height / 2; Canvas.SetLeft(ChartCanvas, leftOffset); Canvas.SetTop(ChartCanvas,bottomOffset); //创建报表的边框 Rectangle chartRect= new Rectangle(); chartRect.Stroke = new SolidColorBrush(Colors.Black); chartRect.Width=ChartCanvas.Width; chartRect.Height=ChartCanvas.Height; ChartCanvas.Children.Add(chartRect); //创建垂直线条 if (IsYGrid == true ) { for (dx = Xmin + XTick; dx < Xmax; dx += XTick) { gridline = new Line(); AddLinePattern(); gridline.X1 = NormalizePoint(new Point(dx,Ymin)).X; gridline.Y1 = NormalizePoint(new Point(dx, Ymin)).Y; gridline.X2 = NormalizePoint(new Point(dx,Ymax)).X; gridline.Y2 = NormalizePoint(new Point(dx, Ymax)).Y; ChartCanvas.Children.Add(gridline); } } //创建水平线条 if (IsXGrid==true ) { for (dy=Ymin+YTick;dy<Ymax;dy+=YTick) { gridline = new Line(); AddLinePattern(); gridline.X1 = NormalizePoint(new Point(Xmin, dy)).X; gridline.Y1 = NormalizePoint(new Point(Xmin,dy)).Y; gridline.X2= NormalizePoint(new Point(Xmax,dy)).X; gridline.Y2= NormalizePoint(new Point(Xmax,dy)).Y; ChartCanvas.Children.Add(gridline); } } //创建X轴 for (dx=Xmin;dx<=Xmax;dx+=XTick) { pt = NormalizePoint(new Point(dx,Ymin)); tick = new Line(); tick.Stroke = new SolidColorBrush(Colors.Black); tick.X1 = pt.X; tick.Y1=pt.Y; tick.X2= pt.X;tick.Y2= pt.Y-5; ChartCanvas.Children.Add(tick); tb=new TextBlock(); tb.Text = dx.ToString(); tb.Measure(new Size(double.PositiveInfinity,double.PositiveInfinity)); size = tb.DesiredSize; textCanvas.Children.Add(tb); Canvas.SetLeft(tb,leftOffset+pt.X-size.Width/2); Canvas.SetTop(tb,pt.Y+10+size.Height/2); } //创建Y轴 for (dy=Ymin;dy<=Ymax;dy+=YTick) { pt=NormalizePoint(new Point(Xmin,dy)); tick=new Line(); tick.Stroke = new SolidColorBrush(Colors.Black); tick.X1 = pt.X; tick.Y1=pt.Y; tick.X2= pt.X+5; tick.Y2= pt.Y; ChartCanvas.Children.Add(tick); tb.Text = dy.ToString(); tb.Measure(new Size(double.PositiveInfinity,double.PositiveInfinity)); size= tb.DesiredSize; textCanvas.Children.Add(tb); Canvas.SetLeft(tb,-30); Canvas.SetTop(tb,pt.Y); } //图表标题 tbTitle.Text = Title; } /// <summary> /// 添加线条的样式 /// </summary> /// <exception cref="NotImplementedException" ></exception> private void AddLinePattern () { gridline.Stroke = gridlineColor; gridline.StrokeThickness = 1; switch (gridlinePattern) { case GridlinePatternEnum.Solid: break ; case GridlinePatternEnum.Dash: DoubleCollection doubleCollection= new DoubleCollection(); doubleCollection.Add(4); doubleCollection.Add(3); gridline.StrokeDashArray= doubleCollection; break ; case GridlinePatternEnum.Dot: doubleCollection = new DoubleCollection(); doubleCollection.Add(1); doubleCollection.Add(2); gridline.StrokeDashArray= doubleCollection; break ; case GridlinePatternEnum.DashDot: doubleCollection = new DoubleCollection(); doubleCollection.Add(4); doubleCollection.Add(2); doubleCollection.Add(1); doubleCollection.Add(2); gridline.StrokeDashArray= doubleCollection; break ; default: break ; } } }
13.32.定义线性数据图形类 线性数据图是在报表展现数据走势的图形,定义一个线性数据图形类DataSeries.在这个类里面定义了一个多边形Polyline类的属性LineSeries来表示绘制线性图形,除此之外,还有线条颜色属性LineColor,线条大小属性LineThickness.线条类型属性LinePattern和图形名称属性SeriesName.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 /// <summary> /// 线性数据图形类 /// </summary> public class DataSeries { //线性图表 private Polyline lineSeries = new Polyline(); private Brush lineColor; private double lineThickness = 1; private string seriesName="Default Name" ; private LinePatternEnum linePattern; public DataSeries () { LineColor = new SolidColorBrush(Colors.Black); } public Polyline LineSeries { get => lineSeries; set => lineSeries = value; } /// <summary> /// 线条颜色 /// </summary> public Brush LineColor { get => lineColor; set => lineColor = value; } //线条大小 public double LineThickness { get => lineThickness; set => lineThickness = value; } //线条类型 public LinePatternEnum LinePattern { get => linePattern; set => linePattern = value; } /// <summary> /// 图形名称 /// </summary> public string SeriesName { get => seriesName; set => seriesName = value; } /// <summary> /// 添加线条的样式 /// </summary> public void AddLinePattern () { // } } public enum LinePatternEnum { Solid=1, Dash=2, Dot=3, DashDot=4, None=5 }
上面的DataSeries类只是封装了单个线性图形的基本属性和形状,下面再定义一个DataCollection类表示图形数据集合类,用于实现把线性图形按照定义的坐标体系添加到界面的面板上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 /// <summary> /// 图形数据集合类 /// </summary> public class DataCollection { private List<DataSeries> dataList; //线性数据图形类集合 public List<DataSeries> DataList { get => dataList; set => dataList = value; } public DataCollection () { dataList= new List<DataSeries>(); } /// <summary> /// 往Canvas面板上按照坐标系的坐标来添加线性图形 /// </summary> /// <param name="canvas" ></param> /// <param name="cs" ></param> public void AddLines(Canvas canvas,ChartStyle cs) { int j = 0; foreach (DataSeries ds in dataList) { if (ds.SeriesName=="Default Name" ) { ds.SeriesName = "DataSeries" + j.ToString(); } ds.AddLinePattern(); for (int i = 0; i < ds.LineSeries.Points.Count; i++) { ds.LineSeries.Points[i] = cs.NormalizePoint(ds.LineSeries.Points[i]); } canvas.Children.Add(ds.LineSeries); j++; } } }
13.33.实现图例 图例的实现其实就是把整个报表用到的图形用简单的线段画出来并标出名称,图例类Legend类里面定义了一个AddLegend方法,通过使用报表中的ChartStyleGridlines对象和DataCollection对象作为参数,然后取出报表中图形的样本和名称,添加到Canvas面板上,这样就实现了一个图例的模块.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 public class Legend { public bool IsLegend { get; private set ; } public Canvas legendCanvas { get; private set ; } public bool IsBorder { get; private set ; } /// <summary> /// 添加图例 /// </summary> /// <param name="cs" >报表的网格图形对象</param> /// <param name="dc" >报表的图形数据集合对象</param> public void AddLegend(ChartStyleGridlines cs,DataCollection dc) { TextBlock tb= new TextBlock(); if (dc.DataList.Count < 1 || !IsLegend) return ; int n = 0; //取出每个图形的名称 string[] legendLabels = new string[dc.DataList.Count]; foreach (DataSeries ds in dc.DataList) { legendLabels[n] = ds.SeriesName; n++; } double legendWidth = 0; Size size = new Size(0,0); //创建每个图形名称的TextBlock控件 for (int i = 0; i < legendLabels.Length; i++) { tb = new TextBlock(); tb.Text= legendLabels[i]; tb.Measure(new Size(double.PositiveInfinity,double.PositiveInfinity)); size = tb.DesiredSize; if (legendWidth < size.Width) legendWidth = size.Width; } //80是预留给线条示例的长度位置 legendWidth += 80; legendCanvas.Width = legendWidth + 5; //30是分配给每个图形示例的高度 double legendHeight=30*dc.DataList.Count; double sx = 6; double sy = 15; double textheight = size.Height; double lineLength = 34; //创建图例的边框 Rectangle legendRect= new Rectangle(); legendRect.Stroke = new SolidColorBrush(Colors.Black); legendRect.Width= legendWidth; legendRect.Height= legendHeight; if (IsLegend && IsBorder) legendCanvas.Children.Add(legendRect); n = 1; //创建每个图形的线段 foreach (DataSeries ds in dc.DataList) { double xSymbol = sx + lineLength / 2; double xText = 2 * sx + lineLength; double yText = n * sy + (2 * n - 1) * textheight / 2; Line line=new Line(); line.X1 = sx; line.Y1=sy; line.X2 = sx + lineLength; line.Y2 = yText; legendCanvas.Children.Add(line); tb=new TextBlock(); tb.FontSize = 15; tb.Text = ds.SeriesName; legendCanvas.Children.Add(tb); Canvas.SetTop(tb,yText-15); Canvas.SetLeft(tb,xText+10); n++; } } }
13.34.实现线性报表 上面已经把图形表格,坐标轴,线性数据图形和图例的相关逻辑都封装好了,下面就要利用这些封装好的模板来创建一个线性报表,首先需要再Xaml页面上定义3个Canvas面板分别表示图例面板,坐标轴面板和线性图形面板,然后再利用上面封装的类来初始化这些面板生成线性报表.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 <Page x:Class="App1.BlankPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local ="using:App1" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:controls="using:App1.Controls" xmlns:models="using:App1.Models" xmlns:converters="using:App1.Converters" mc:Ignorable="d" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" > <Page.Resources> <DataTemplate x:Key="PersonNameDataTemplate" > <StackPanel Orientation="Horizontal" > <TextBlock Text="{Binding LastName}" /> <TextBlock Text="," /> <TextBlock Text="{Binding FirstName}" /> </StackPanel> </DataTemplate> <Style x:Name="ItemsControlStyle" TargetType="ItemsControl" > <Setter Property="Template" > <Setter.Value> <ControlTemplate TargetType="ItemsControl" > <ScrollViewer BorderBrush="Red" BorderThickness="6" > <StackPanel Orientation="Horizontal" Background="Blue" > <Border BorderBrush="Yellow" BorderThickness="3" > <ItemsPresenter/> </Border> </StackPanel> </ScrollViewer> </ControlTemplate> </Setter.Value> </Setter> </Style> <!--选中数据项的样式--> <DataTemplate x:Key="dataTemplateSelectKey" x:Name="dataTemplateSelectName" > <Grid Tapped="StackPanel_Tapped" Background="Red" > <TextBlock Text="{Binding LastName}" FontSize="50" /> </Grid> </DataTemplate> <!--默认数据项的样式,注意默认的数据项样式不能在C <DataTemplate x:Key="dataTemplateDefaultKey" x:Name="dataTemplateDefaultName" > <StackPanel Orientation="Horizontal" Tapped="StackPanel_Tapped" > <TextBlock Text="{Binding LastName}" /> <TextBlock Text="," /> <TextBlock Text="{Binding FirstName}" /> </StackPanel> </DataTemplate> <!--非选中数据项的样式--> <DataTemplate x:Key="dataTemplateNoSelectKey" x:Name="dataTemplateNoSelectName" > <StackPanel Orientation="Horizontal" Tapped="StackPanel_Tapped" > <TextBlock Text="{Binding LastName}" /> <TextBlock Text="," /> <TextBlock Text="{Binding FirstName}" /> </StackPanel> </DataTemplate> <models:Clock x:Key="clock" /> <converters:HoursToDayStringConverter x:Key="booleanToDayString" /> </Page.Resources> <Grid> <Grid.RowDefinitions> <RowDefinition Height="auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <Grid> <Button x:Name="backButton" Margin="24,24,24,24" Click="backButton_Click" Style="{StaticResource NavigationBackButtonNormalStyle}" /> </Grid> <Pivot Title="控件" Grid.Row="1" > <PivotItem Header="Border" > <StackPanel> <Border Background="Coral" Padding="10" CornerRadius="30,38,150,29" BorderThickness="8 15 10 2" BorderBrush="Azure" /> <Border BorderThickness="1,3,5,7" BorderBrush="Blue" CornerRadius="10" Width="200" > <TextBlock Text="蓝色的Border" ToolTipService.ToolTip="这是蓝色的Border吗?" FontSize="30" TextAlignment="Center" /> </Border> <!--单击后显示边框--> <Border x:Name="TextBorder" BorderThickness="10" > <Border.BorderBrush> <SolidColorBrush Color="Red" Opacity="0" /> </Border.BorderBrush> <TextBlock Text="请单击我一下!" PointerPressed="TextBlock_PointerPressed" FontSize="20" /> </Border> <!--颜色渐变的边框--> <Border x:Name="brdTest" BorderThickness="4" Width="200" Height="150" > <Border.BorderBrush> <LinearGradientBrush x:Name="borderLinearGradientBrush" MappingMode="RelativeToBoundingBox" StartPoint="0.5,0" EndPoint="0.5,1" > <LinearGradientBrush.GradientStops> <GradientStop Color="Yellow" Offset="0" /> <GradientStop Color="Blue" Offset="1" /> </LinearGradientBrush.GradientStops> </LinearGradientBrush> </Border.BorderBrush> </Border> </StackPanel> </PivotItem> <PivotItem Header="HyperlinkButton" > <StackPanel> <HyperlinkButton Width="200" Content="链接按钮" Background="Blue" Foreground="Orange" FontWeight="Bold" Margin="0,0,0,30" /> <HyperlinkButton Content="Google" NavigateUri="http://goole.com" /> </StackPanel> </PivotItem> <PivotItem Header="Button" > <ScrollViewer> <StackPanel Orientation="Vertical" > <TextBlock Text="你好" FontSize="30" HorizontalAlignment="Left" VerticalAlignment="Center" /> <StackPanel x:Name="sp_Show" > <Button x:Name="bt_addXaml" Content="加载Xaml按钮" Click="bt_addXaml_Click" /> </StackPanel> <StackPanel x:Name="stackPanel" Margin="12,0,12,0" > <TextBox> </TextBox> <Button Content="遍历" Click="Button_Click1" > </Button> </StackPanel> <Button HorizontalAlignment="Center" VerticalAlignment="Center" Content="Hello,App" Click="Button_Click" /> <Button Content="按钮1" Height="80" Name="button1" VerticalAlignment="Top" Width="300" Click="button1_Click" /> <Button Content="按钮2" FontSize="48" FontStyle="Italic" Foreground="Red" Background="Blue" BorderThickness="10" BorderBrush="Yellow" Padding="20" /> <Button Width="165" > <StackPanel> <Image Source="Assets/StoreLogo.png" Stretch="None" Height="61" Width="94" /> </StackPanel> </Button> <Button> <SymbolIcon Symbol="Emoji2" /> </Button> <StackPanel> <TextBox InputScope="TelephoneNumber" > <TextBox.Header> 请输入电话号码: </TextBox.Header> </TextBox> <TextBox x:Name="TextBox1" TextWrapping="Wrap" AcceptsReturn="True" Header="输入信息:" SelectionHighlightColor="Red" TextChanged="TextBox1_TextChanged" SelectionChanged="TextBox1_SelectionChanged" Paste="TextBox1_Paste" /> <TextBlock x:Name="textblock2" Text="操作信息:" FontSize="20" /> <TextBlock x:Name="textBlock1" TextWrapping="Wrap" FontSize="20" /> </StackPanel> </StackPanel> </ScrollViewer> </PivotItem> <PivotItem Header="ScrollViewer" > <StackPanel Orientation="Vertical" > <ScrollViewer Height="200" Width="200" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Visible" > <ScrollViewer.Content> <StackPanel> <Image Source="ms-appx:///Assets/StoreLogo.png" /> </StackPanel> </ScrollViewer.Content> </ScrollViewer> <ScrollViewer Name="scrollViewer1" VerticalScrollBarVisibility="Hidden" Height="300" > <StackPanel Name="stkpnImage" /> </ScrollViewer> <Button Content="往上" FontSize="30" Click="btnUp_Click" /> <Button Content="往下" FontSize="30" Click="btnDown_Click" /> <Button Content="停止" FontSize="30" Click="stop_Click" /> </StackPanel> </PivotItem> <PivotItem Header="ProgressBar" > <StackPanel> <TextBlock Text="选择ProgressBar的类型:" /> <RadioButton Content="Determinate类型" Height="71" Name="radioButton1" GroupName="Type" /> <RadioButton Content="Indeterminate类型" Height="71" Name="radioButton2" GroupName="Type" IsChecked="True" /> <Button Content="启动ProgressBar" Height="72" x:Name="begin" Click="begin_Click" /> <Button Content="取消ProgressBar" Height="72" x:Name="cancel" Click="cancel_Click" /> <ProgressBar x:Name="progressBar1" IsIndeterminate="True" /> </StackPanel> </PivotItem> <PivotItem Header="Slider" > <StackPanel Grid.Row="1" > <Grid Name="controlGrid" > <Grid.RowDefinitions> <RowDefinition Height="*" /> <RowDefinition Height="*" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="*" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <!--设置红色--> <TextBlock Text="红色" Foreground="Red" FontSize="20" /> <Slider x:Name="redSlider" Grid.Row="1" Foreground="Red" Minimum="0" Maximum="255" ValueChanged="redSlider_ValueChanged" /> <TextBlock x:Name="redText" Grid.Row="2" Text="0" Foreground="Red" FontSize="20" /> <!--设置绿色--> <TextBlock Grid.Column="1" Text="绿色" Foreground="Green" FontSize="20" /> <Slider x:Name="greenSlider" Foreground="Green" FontSize="20" Minimum="0" Maximum="255" ValueChanged="redSlider_ValueChanged" Grid.Column="1" Grid.Row="1" ></Slider> <TextBlock x:Name="greenText" Grid.Column="1" Grid.Row="2" Foreground="Green" FontSize="20" /> <!--设置蓝色--> <TextBlock Grid.Column="2" Grid.Row="0" Text="蓝色" Foreground="Blue" FontSize="20" /> <Slider x:Name="blueSlider" Grid.Column="2" Grid.Row="1" Foreground="Blue" Minimum="0" Maximum="255" ValueChanged="redSlider_ValueChanged" /> <TextBlock x:Name="blueText" Grid.Column="2" Grid.Row="2" Text="0" Foreground="Blue" FontSize="20" /> <!--设置--> </Grid> <Ellipse Height="100" x:Name="ellipse2" Stroke="Black" StrokeThickness="1" Width="224" /> <TextBlock x:Name="textBlockColor" Text="颜色" FontSize="26" /> </StackPanel> </PivotItem> <PivotItem Header="TimePicker DatePicker" > <StackPanel Grid.Row="2" > <TimePicker x:Name="time" Header="请选择时间:" TimeChanged="time_TimeChanged" /> <DatePicker x:Name="date" Header="请选择日期:" DateChanged="date_DateChanged" /> <TextBlock x:Name="infoTime" FontSize="20" TextWrapping="Wrap" /> </StackPanel> </PivotItem> <PivotItem Header="Hub" > <!--在大标题上添加程序的图标--> <Hub> <Hub.Header> <StackPanel Orientation="Horizontal" > <Image Source="Assets/StoreLogo.scale-100.png" Height="100" /> <TextBlock Text="My App" /> </StackPanel> </Hub.Header> <HubSection Header="first item" > <HubSection.ContentTemplate> <DataTemplate> <StackPanel> <TextBlock Text="第一个Item" FontSize="50" /> <TextBlock Text="这是第一个item" FontSize="50" /> </StackPanel> </DataTemplate> </HubSection.ContentTemplate> </HubSection> <HubSection Header="second item" > <HubSection.ContentTemplate> <DataTemplate> <StackPanel> <TextBlock Text="第二个Item" FontSize="50" /> <TextBlock Text="这是第二个item" FontSize="50" /> </StackPanel> </DataTemplate> </HubSection.ContentTemplate> </HubSection> <HubSection Header="Third item" > <HubSection.ContentTemplate> <DataTemplate> <StackPanel> <TextBlock Text="第三个Item" FontSize="50" /> <TextBlock Text="这是第三个item" FontSize="50" /> </StackPanel> </DataTemplate> </HubSection.ContentTemplate> </HubSection> </Hub> </PivotItem> <PivotItem Header="Flyout" > <StackPanel> <Button Content="Show Flyout" > <Button.Flyout> <Flyout> <StackPanel> <TextBox PlaceholderText="Please Enter Name." /> <Button HorizontalAlignment="Right" Content="确定" /> </StackPanel> </Flyout> </Button.Flyout> </Button> <!--浮出上下文菜单,点击菜单后改变当前按钮上的文本内容--> <Button x:Name="menuFlyoutButton" Content="Show MenuFlyout" > <Button.Flyout> <MenuFlyout> <MenuFlyoutItem Text="Option 1" Click="MenuFlyoutItem_Click" /> <MenuFlyoutItem Text="Option 2" Click="MenuFlyoutItem_Click" /> <MenuFlyoutItem Text="Option 3" Click="MenuFlyoutItem_Click" /> </MenuFlyout> </Button.Flyout> </Button> <!--浮出选择日期弹窗,点击确认后触发DatePicked事件,然后可以获取选中的日期--> <Button Content="Show DatePicker" > <Button.Flyout> <DatePickerFlyout DatePicked="DatePickerFlyout_DatePicked" /> </Button.Flyout> </Button> <!--浮出选择时间弹窗,点击确认后触发DatePicked事件,然后可以获取选中的时间--> <Button Content="Show TimePicker" > <Button.Flyout> <TimePickerFlyout TimePicked="TimePickerFlyout_TimePicked" /> </Button.Flyout> </Button> <!--浮出选择弹窗,显示底下的确认取消菜单栏并且处理其确认事件Confirmed--> <Button Content="Show Picker" > <Button.Flyout> <PickerFlyout Confirmed="PickerFlyout_Confirmed" ConfirmationButtonsVisible="True" > <TextBlock Text="你确定吗?????" FontSize="30" Margin="0 100 0 0" /> </PickerFlyout> </Button.Flyout> </Button> <!--浮出选择列表弹窗,绑定集合的数据,处理选中的事件ItemsPicked--> <Button Content="Show DataPicker" > <Button.Flyout> <ListPickerFlyout x:Name="listPickerFlyout" Title="Select Mobile Tag:" ItemsPicked="listPickerFlyout_ItemsPicked" > <ListPickerFlyout.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding}" FontSize="30" /> </DataTemplate> </ListPickerFlyout.ItemTemplate> </ListPickerFlyout> </Button.Flyout> </Button> <!--使用附加属性FlyoutBase.AttachedFlyout来实现Flyout控件--> <TextBlock Text="Please Click Me!" Tapped="TextBlock_Tapped" FontSize="20" > <FlyoutBase.AttachedFlyout> <Flyout> <TextBox Text="你好!" > </TextBox> </Flyout> </FlyoutBase.AttachedFlyout> </TextBlock> </StackPanel> </PivotItem> <PivotItem Header="ComboBox" > <StackPanel> <!--纯文本的下拉框--> <ComboBox Header="Colors" PlaceholderText="Pick a Color" > <x:String> Blue </x:String> <x:String> Green </x:String> <x:String> Red </x:String> <x:String> Yellow </x:String> </ComboBox> <!--数据绑定的下拉框--> <ComboBox x:Name="comboBox2" DropDownClosed="comboBox2_DropDownClosed" > <ComboBox.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal" > <TextBlock Text="{Binding Name}" FontSize="30" /> <TextBlock Text="{Binding Age}" Margin="50 10 0 0" /> </StackPanel> </DataTemplate> </ComboBox.ItemTemplate> </ComboBox> <!--数据绑定的下拉框关闭后,这里显示选中的选项的信息--> <TextBlock x:Name="Info" FontSize="20" /> </StackPanel> </PivotItem> <PivotItem Header="SplitView" > <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" > <ToggleButton Click="ToggleButton_Click" VerticalAlignment="Top" Foreground="Green" > <ToggleButton.Content> <Border Background="Transparent" Width="40" Height="40" > <FontIcon x:Name="hamburger" FontFamily="Segoe MDL2 Assets" Glyph="" /> </Border> </ToggleButton.Content> </ToggleButton> <SplitView x:Name="Splitter" IsPaneOpen="True" > <SplitView.Pane> <StackPanel VerticalAlignment="Center" > <Button Content="菜单1" Click="Button_Click_1" /> <Button Content="菜单2" Click="Button_Click_1" /> <Button Content="菜单3" Click="Button_Click_1" /> <Button Content="菜单4" Click="Button_Click_1" /> </StackPanel> </SplitView.Pane> <Grid> <TextBlock x:Name="tb" Text="" VerticalAlignment="Center" HorizontalAlignment="Center" /> </Grid> </SplitView> </Grid> </PivotItem> <PivotItem Header="ScrollableTextBlock" > <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" > <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0" > <controls:ScrollableTextBlock x:Name="scrollableTextBlock1" FontSize="30" /> </Grid> </Grid> </PivotItem> <PivotItem Header="Canvas" > <StackPanel> <Canvas> <Button Content="按钮1" Height="200" Width="200" /> </Canvas> <Canvas> <Button Content="按钮2" Canvas.Left="30" Canvas.Top="30" Height="200" Width="200" /> </Canvas> <Canvas> <Button Content="按钮1" Canvas.Left="30" Canvas.Top="30" Background="Blue" Height="200" Width="200" /> <Button Content="按钮2" Canvas.Left="100" Canvas.Top="100" Background="Red" Height="200" Width="200" /> <Button Content="按钮3" Canvas.Left="170" Canvas.Top="170" Background="Yellow" Height="200" Width="200" /> </Canvas> <Canvas Width="200" Height="200" Background="LimeGreen" > <Button Content="按钮1" Canvas.Left="30" Canvas.Top="30" Background="Blue" Height="200" Width="200" /> </Canvas> <Canvas Width="200" Height="200" Background="White" > <Canvas Height="50" Width="50" Canvas.Left="30" Canvas.Top="30" Background="Blue" /> <Canvas Height="50" Width="50" Canvas.Left="130" Canvas.Top="30" Background="Red" /> </Canvas> </StackPanel> </PivotItem> <PivotItem Header="渐变矩形" > <Canvas Background="White" > <Canvas Height="400" Width="400" Canvas.Left="0" Canvas.Top="50" Background="Gray" Opacity="0.1" /> <Canvas Height="360" Width="360" Canvas.Left="20" Canvas.Top="70" Background="Gray" Opacity="0.2" /> <Canvas Height="320" Width="320" Canvas.Left="40" Canvas.Top="90" Background="Gray" Opacity="0.3" /> <Canvas Height="280" Width="280" Canvas.Left="60" Canvas.Top="110" Background="Gray" Opacity="0.4" /> <Canvas Height="240" Width="240" Canvas.Left="80" Canvas.Top="130" Background="Gray" Opacity="0.5" /> <Canvas Height="200" Width="200" Canvas.Left="100" Canvas.Top="150" Background="Gray" Opacity="0.6" /> <Canvas Height="160" Width="160" Canvas.Left="120" Canvas.Top="170" Background="Black" Opacity="0.3" /> <Canvas Height="120" Width="120" Canvas.Left="140" Canvas.Top="190" Background="Black" Opacity="0.4" /> <Canvas Height="80" Width="80" Canvas.Left="160" Canvas.Top="210" Background="Black" Opacity="0.5" /> <Canvas Height="40" Width="40" Canvas.Left="180" Canvas.Top="230" Background="Black" Opacity="0.6" /> </Canvas> </PivotItem> <PivotItem Header="RelativePanel" > <RelativePanel VerticalAlignment="Center" HorizontalAlignment="Center" > <Button x:Name="bt1" Content="按钮1" /> <Button x:Name="bt2" Content="按钮2" RelativePanel.RightOf="bt1" Height="100" /> <Button x:Name="bt3" Content="按钮3... ..." RelativePanel.AlignLeftWith="bt2" RelativePanel.AlignBottomWithPanel="True" /> </RelativePanel> </PivotItem> <PivotItem Header="Grid" > <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" > <Grid.RowDefinitions> <RowDefinition Height="*" /> <RowDefinition Height="3*" /> <RowDefinition Height="*" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <Ellipse Grid.Row="1" Fill="Red" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MaxWidth="500" MaxHeight="500" /> <Button Grid.Row="3" Content="按钮1" HorizontalAlignment="Stretch" Margin="0 0 0 2" MaxHeight="500" /> <Button Grid.Row="4" Content="按钮2" HorizontalAlignment="Stretch" MaxWidth="500" /> </Grid> </PivotItem> <PivotItem Header="AdaptiveTrigger" > <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" > <VisualStateManager.VisualStateGroups> <VisualStateGroup> <VisualState x:Name="SideBySideState" > <VisualState.StateTriggers> <AdaptiveTrigger MinWindowWidth="600" /> </VisualState.StateTriggers> <VisualState.Setters> <Setter Target="theStackPanel.Orientation" Value="Horizontal" /> </VisualState.Setters> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <StackPanel x:Name="theStackPanel" Orientation="Vertical" HorizontalAlignment="Left" VerticalAlignment="Top" > <Button Content="按钮1" MinWidth="150" Margin="2" /> <Button Content="按钮2" MinWidth="150" Margin="2" /> <Button Content="按钮3" MinWidth="150" Margin="2" /> </StackPanel> </Grid> </PivotItem> <PivotItem Header="MeasureArrange" > <StackPanel> <Button Content="改变高度" Click="Button_Click_2" /> <controls:TestPanel x:Name="element1" Width="60" Height="60" Background="Red" Margin="10" /> <controls:TestUIElement x:Name="element2" Width="60" Height="60" Background="Red" /> </StackPanel> </PivotItem> <PivotItem Header="CustomPanel" > <Grid x:Name="ContentPanel1" Margin="12,0,12,0" > <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <Slider Grid.Row="0" Value="5" ValueChanged="Slider_ValueChanged" /> <controls:CirclePanel x:Name="circlePanel" Radius="50" Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center" > <TextBlock>Start here</TextBlock> <TextBlock>TextBlock 2</TextBlock> <TextBlock>TextBlock 3</TextBlock> <TextBlock>TextBlock 4</TextBlock> <TextBlock>TextBlock 5</TextBlock> <TextBlock>TextBlock 6</TextBlock> <TextBlock>TextBlock 7</TextBlock> </controls:CirclePanel> </Grid> </PivotItem> <PivotItem Header="ApplicationDataContainerSettings" > <StackPanel> <!--输入应用设置的键--> <StackPanel Orientation="Horizontal" > <TextBlock x:Name="textBlock2" Text="Key:" Width="150" /> <TextBox x:Name="txtKey" Text="" Width="200" /> </StackPanel> <!--输入应用设置的值--> <StackPanel Orientation="Horizontal" Margin="0 20 0 0" > <TextBlock Text="Value:" Width="150" /> <TextBox x:Name="txtValue" Text="" Width="200" /> </StackPanel> <Button Content="保存" x:Name="btnSave" Click="btnSave_Click" /> <Button Content="删除" x:Name="btnDelete" Click="btnDelete_Click" /> <Button Content="清空所有" x:Name="deleteall" Click="deleteall_Click" /> <!--显示容器内所有的键的列表,点击选中可以在上面的输入框里面查看和修改它对应的值--> <TextBlock Text="Keys列表:" /> <ListBox Height="168" x:Name="lstKeys" SelectionChanged="lstKeys_SelectionChanged" /> </StackPanel> </PivotItem> <PivotItem Header="ApplicationDataContainer" > <StackPanel> <Button Content="创建Container" Click="CreateContainer_Click" Margin="2" /> <Button Content="添加信息" Click="WriteSetting_Click" Margin="2" /> <Button Content="删除信息" Click="DeleteSetting_Click" Margin="2" /> <Button Content="删除Container" Click="DeleteContainer_Click" Margin="2" /> <TextBlock x:Name="OutputTextBlock" TextWrapping="Wrap" /> </StackPanel> </PivotItem> <PivotItem Header="ApplicationDataCompositeValue" > <StackPanel> <Button Content="创建" Click="WriteCompositeSetting_Click" /> <Button Content="删除" Click="DeleteCompositeSetting_Click" /> <TextBlock x:Name="OutputTextBlock1" TextWrapping="Wrap" /> </StackPanel> </PivotItem> <PivotItem Header="StorageFolderFile" > <StackPanel> <TextBox Header="文件信息:" x:Name="infoStorage" TextWrapping="Wrap" /> <Button x:Name="bt_save" Content="保存" Margin="2" Click="bt_save_Click" /> <Button x:Name="bt_read" Content="读取保存的文件" Margin="2" Click="bt_read_Click" /> <Button x:Name="bt_delete" Content="删除文件" Margin="2" Click="bt_delete_Click" /> </StackPanel> </PivotItem> <PivotItem Header="StreamBuffer" > <StackPanel> <Button x:Name="bt_crreate" Content="创建一个测试文件" Margin="2" Click="bt_crreate_Click" /> <Button x:Name="bt_writebuffer" Content="写入buffer" Margin="2" Click="bt_writebuffer_Click" /> <Button x:Name="bt_readbuffer" Content="读取buffer" Margin="2" Click="bt_readbuffer_Click" /> <Button x:Name="bt_writestream" Content="写入stream" Margin="2" Click="bt_writestream_Click" /> <Button x:Name="bt_readstream" Content="读取stream" Margin="2" Click="bt_readstream_Click" /> <TextBlock x:Name="StreambufferTextBlock" TextWrapping="Wrap" FontSize="20" /> </StackPanel> </PivotItem> <PivotItem Header="GetFolderByUri" > <StackPanel> <TextBox Header="文件信息:" x:Name="UriInfo" TextWrapping="Wrap" /> <Button x:Name="bt_saveuri" Content="创建文件" Click="bt_save_Click_1" /> <Button x:Name="bt_readuri" Content="通过URI读取文件" Margin="2" Click="bt_read_Click_1" /> </StackPanel> </PivotItem> <PivotItem Header="JsonObject" > <StackPanel> <TextBlock Text="Write your message" FontSize="20" /> <TextBox x:Name="userName" Header="Name:" /> <TextBox x:Name="userAge" Header="Age:" InputScope="Number" /> <TextBlock Text="Your study school:" FontSize="20" /> <CheckBox Content="Haerbing School" x:Name="school1" /> <CheckBox Content="LanXiang School" x:Name="school2" /> <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" > <Button Content="保存" x:Name="save" Click="save_Click" /> <Button Content="获取保存的信息" x:Name="get" Click="get_Click" /> </StackPanel> <TextBlock x:Name="JsonObjectInfo" TextWrapping="Wrap" FontSize="20" /> </StackPanel> </PivotItem> <PivotItem Header="Xml" > <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" > <Grid x:Name="ContentPanelShop" Margin="12,0,12,0" > <!--展示购物清单的列表--> <ListBox FontSize="48" x:Name="Files" > </ListBox> </Grid> </Grid> </PivotItem> <PivotItem Header="ApplicationModelPackage" > <ScrollViewer> <StackPanel> <Button Content="获取安装包的根目录" x:Name="btnGetFile" Click="btnGetFile_Click" Margin="2" /> <TextBlock Text="文件夹列表:" /> <ListBox x:Name="lbFolder" /> <Button Content="打开选中的文件夹" x:Name="open" Click="open_Click" Margin="2" /> <TextBlock Text="文件列表:" /> <ListBox x:Name="lbFile" > </ListBox> <Button Content="在选中文件夹下创建测试文件" x:Name="create" Click="create_Click" Margin="2" /> <Button Content="删除选中的文件" x:Name="delete" Click="delete_Click" Margin="2" /> </StackPanel> </ScrollViewer> </PivotItem> <PivotItem Header="AppUri" > <StackPanel> <TextBlock Text="安装包的图片展示:" /> <Image x:Name="packageImage" Height="200" /> <TextBlock Text="应用存储的图片展示:" /> <Image x:Name="appImage" Height="200" /> </StackPanel> </PivotItem> <PivotItem Header="Line" > <Canvas x:Name="ContentPanelCanvas" Background="Transparent" > </Canvas> </PivotItem> <PivotItem Header="Geometry" > <StackPanel> <!--直线--> <Path Stroke="Red" StrokeThickness="2" > <Path.Data> <LineGeometry StartPoint="0,0" EndPoint="400,20" /> </Path.Data> </Path> <!--矩形路径--> <Path Fill="Red" > <Path.Data> <RectangleGeometry Rect="20,20,400,50" /> </Path.Data> </Path> <!--椭圆路径--> <Path Fill="Red" > <Path.Data> <!--<GeometryGroup组合>--> <GeometryGroup FillRule="EvenOdd" > <RectangleGeometry Rect="80,50,200,100" /> <EllipseGeometry Center="300,100" RadiusX="80" RadiusY="60" > </EllipseGeometry> </GeometryGroup> </Path.Data> </Path> <Path Fill="Red" StrokeThickness="3" > <Path.Data> <!--<GeometryGroup组合>--> <GeometryGroup FillRule="Nonzero" > <RectangleGeometry Rect="80,50,200,100" /> <EllipseGeometry Center="300,100" RadiusX="80" RadiusY="60" /> </GeometryGroup> </Path.Data> </Path> </StackPanel> </PivotItem> <PivotItem Header="Segment" > <Path Stroke="Red" StrokeThickness="5" > <Path.Data> <PathGeometry> <PathFigure StartPoint="10,20" > <PathFigure.Segments> <LineSegment Point="100,130" /> </PathFigure.Segments> </PathFigure> </PathGeometry> </Path.Data> </Path> </PivotItem> <PivotItem Header="ArcSegment" > <Path Stroke="Red" StrokeThickness="5" > <Path.Data> <PathGeometry> <PathGeometry.Figures> <PathFigure StartPoint="10,50" > <PathFigure.Segments> <ArcSegment Size="50,50" RotationAngle="45" IsLargeArc="True" SweepDirection="Clockwise" Point="200,100" > </ArcSegment> </PathFigure.Segments> </PathFigure> </PathGeometry.Figures> </PathGeometry> </Path.Data> </Path> </PivotItem> <PivotItem Header="BezierSegment" > <ScrollViewer> <StackPanel Orientation="Vertical" > <Path Stroke="Red" StrokeThickness="5" > <Path.Data> <PathGeometry> <PathGeometry.Figures> <PathFigure StartPoint="10,20" > <PathFigure.Segments> <BezierSegment Point1="100,0" Point2="200,200" Point3="300,100" /> </PathFigure.Segments> </PathFigure> </PathGeometry.Figures> </PathGeometry> </Path.Data> </Path> <Path Stroke="Red" StrokeThickness="5" > <Path.Data> <PathGeometry> <PathGeometry.Figures> <PathFigure StartPoint="10,20" > <PathFigure.Segments> <!--<PolyBezierSegment>--> <PolyBezierSegment Points="0,0 100,0 150,100 150,0 200,0 300,100" /> </PathFigure.Segments> </PathFigure> </PathGeometry.Figures> </PathGeometry> </Path.Data> </Path> <Path Stroke="Red" StrokeThickness="5" > <Path.Data> <PathGeometry> <PathGeometry.Figures> <PathFigure StartPoint="10,20" > <PathFigure.Segments> <QuadraticBezierSegment Point1="200,200" Point2="300,100" /> </PathFigure.Segments> </PathFigure> </PathGeometry.Figures> </PathGeometry> </Path.Data> </Path> <Path Stroke="Red" StrokeThickness="5" > <Path.Data> <PathGeometry> <PathGeometry.Figures> <PathFigure StartPoint="10,20" > <PathFigure.Segments> <PolyQuadraticBezierSegment Points="200,200 300,100 0,200 30,400" /> </PathFigure.Segments> </PathFigure> </PathGeometry.Figures> </PathGeometry> </Path.Data> </Path> <Path Stroke="Red" StrokeThickness="3" Data="M 100,200 C 100,25 400,350 400,175 H 280" /> </StackPanel> </ScrollViewer> </PivotItem> <PivotItem Header="Hexagon" > <Grid x:Name="HexagonContentPanel" Margin="12,0,12,0" > <controls:Hexagon Height="300" Width="300" Stroke="Yellow" StrokeThickness="8" Fill="Red" /> </Grid> </PivotItem> <PivotItem Header="LinearGradientBrush" > <Path Canvas.Left="15" Canvas.Top="50" Stroke="Black" Data="M 0,0 A 15,5 180 1 1 200,0 L 100,100 L 0,100 Z" > <Path.Fill> <LinearGradientBrush StartPoint="0,0" EndPoint="1,0" > <GradientStop Offset="0" Color="DarkBlue" /> <GradientStop Offset="1" Color="LightBlue" /> </LinearGradientBrush> </Path.Fill> </Path> </PivotItem> <PivotItem Header="ImageBrush" > <StackPanel> <Ellipse Height="180" Width="180" Margin="50,0,0,0" > <Ellipse.Fill> <ImageBrush ImageSource="Assets/ApplicationIcon.png" Stretch="Fill" /> </Ellipse.Fill> </Ellipse> <TextBlock Text="你好" FontSize="100" FontWeight="Bold" > <TextBlock.Foreground> <!--使用图像画刷填充TextBlock的Foreground--> <ImageBrush ImageSource="Assets/AlignmentGrid.png" /> </TextBlock.Foreground> </TextBlock> </StackPanel> </PivotItem> <PivotItem Header="Clip" > <Grid x:Name="ClipContentPanel" Margin="12,0,12,0" > <Canvas Background="White" Margin="20" controls:Clip.ToBounds="True" > <Ellipse Fill="Red" Canvas.Top="-10" Canvas.Left="-10" Width="200" Height="200" /> </Canvas> </Grid> </PivotItem> <PivotItem Header="RenderTargetBitmap" > <!--注册PointerReleased事件用于捕获屏幕的单击操作,并在时间处理程序中生成图片.--> <Grid x:Name="root" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" PointerReleased="root_PointerReleased" > <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <StackPanel x:Name="TitlePanel" Margin="12,35,0,28" > <TextBlock Text="我的应用程序" FontSize="20" /> <TextBlock Text="点击截屏" FontSize="60" /> </StackPanel> <Grid x:Name="RenderTargetBitmapContentPanel" Grid.Row="1" Margin="12,0,12,0" > <!--该图片控件用于展示截图图片效果--> <Image x:Name="img" /> </Grid> </Grid> </PivotItem> <PivotItem Header="SaveBitmapEncoder" > <Grid x:Name="root1" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" > <Grid x:Name="BitmapEncoderContentPanel" Margin="12,0,12,0" > <StackPanel> <Button x:Name="bt_save1" Content="存储生成的图片" Click="bt_save_Click_2" /> <Button x:Name="bt_show" Content="展示存储的图片" Click="bt_show_Click" /> <ScrollViewer BorderBrush="Red" BorderThickness="2" Height="350" > <Image x:Name="img1" /> </ScrollViewer> </StackPanel> </Grid> </Grid> </PivotItem> <PivotItem Header="TranslateTransform" > <Rectangle Height="50" Width="50" Fill="#CCCCCCFF" Stroke="Blue" StrokeThickness="2" Canvas.Left="100" Canvas.Top="100" > <Rectangle.RenderTransform> <TranslateTransform X="50" Y="50" /> </Rectangle.RenderTransform> </Rectangle> </PivotItem> <PivotItem Header="RotateTransform" > <StackPanel> <Canvas Height="200" Width="200" > <!--以Polyline对象的左上角0,0为旋转点将其旋转45°--> <Polyline Points="25,25,0,50,25,75,50,50,25,25,25,0" Stroke="Blue" StrokeThickness="10" Canvas.Left="75" Canvas.Top="50" > <Polyline.RenderTransform> <RotateTransform CenterX="0" CenterY="0" Angle="45" /> </Polyline.RenderTransform> </Polyline> </Canvas> <Canvas Height="200" Width="200" > <Polyline Points="25,25,0,50,25,75,50,50,25,25,25,0" Stroke="Blue" StrokeThickness="10" Canvas.Left="75" Canvas.Top="50" > <Polyline.RenderTransform> <RotateTransform CenterX="25" CenterY="50" Angle="45" /> </Polyline.RenderTransform> </Polyline> </Canvas> <Canvas Height="200" Width="200" > <Rectangle Height="50" Width="50" Fill="#CCCCCCFF" Stroke="Blue" StrokeThickness="2" Canvas.Left="100" Canvas.Top="100" > <Rectangle.RenderTransform> <ScaleTransform CenterX="0" CenterY="0" ScaleX="2" ScaleY="2" /> </Rectangle.RenderTransform> </Rectangle> <Rectangle Height="50" Width="50" Fill="#CCCCCCFF" Canvas.Left="100" Canvas.Top="100" Stroke="Blue" StrokeThickness="2" > <Rectangle.RenderTransform> <ScaleTransform CenterX="25" CenterY="25" ScaleX="2" ScaleY="2" /> </Rectangle.RenderTransform> </Rectangle> </Canvas> </StackPanel> </PivotItem> <PivotItem Header="SkewTransform" > <Canvas Width="200" Height="200" > <Rectangle Height="50" Width="50" Fill="#CCCCCCFF" Stroke="Blue" StrokeThickness="2" Canvas.Left="100" Canvas.Top="100" > <Rectangle.RenderTransform> <SkewTransform CenterX="0" CenterY="0" AngleX="45" AngleY="0" /> </Rectangle.RenderTransform> </Rectangle> </Canvas> </PivotItem> <PivotItem Header="TransformGroup" > <Button RenderTransformOrigin="0.5,0.5" HorizontalAlignment="Center" >Click <Button.RenderTransform> <TransformGroup> <ScaleTransform ScaleY="3" /> <RotateTransform Angle="45" /> </TransformGroup> </Button.RenderTransform> </Button> </PivotItem> <PivotItem Header="MatrixTransform" > <!--<Button MinWidth="100" >Click <Button.RenderTransform> <MatrixTransform x:Name="myMatrixTransform" > <MatrixTransform.Matrix> <Matrix/> </MatrixTransform.Matrix> </MatrixTransform> </Button.RenderTransform> </Button>--> </PivotItem> <PivotItem Header="3D" > <Canvas Background="Black" > <Rectangle Width="200" Height="200" Fill="Red" > <Rectangle.RenderTransform> <MatrixTransform Matrix="1,-0.5,0,1.0,60,100" /> </Rectangle.RenderTransform> </Rectangle> <Rectangle Width="200" Height="200" Fill="FloralWhite" > <Rectangle.RenderTransform> <MatrixTransform Matrix="1.0,0.5,0,1.0,260,0" /> </Rectangle.RenderTransform> </Rectangle> <Rectangle Width="200" Height="200" Fill="Green" > <Rectangle.RenderTransform> <MatrixTransform Matrix="1,0.5,-1,0.5,260,200" /> </Rectangle.RenderTransform> </Rectangle> </Canvas> </PivotItem> <PivotItem Header="SanWeiXuanzhuan" > <Grid x:Name="SanWeiContentPanel" Margin="12,0,12,0" > <Grid.RowDefinitions> <RowDefinition Height="*" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <!--旋转的对象--> <TextBlock Grid.Row="0" Text="3D" FontSize="120" Foreground="Red" HorizontalAlignment="Center" VerticalAlignment="Center" > <TextBlock.Projection> <PlaneProjection x:Name="planeProjection" /> </TextBlock.Projection> </TextBlock> <StackPanel Grid.Row="1" > <StackPanel Orientation="Horizontal" > <RadioButton x:Name="rotationRadioButton" Content="Rotation" Checked="rotationRadioButton_Checked" /> <RadioButton x:Name="centerOfRotationRadioButton" Content="CenterOfRotation" Checked="centerOfRotationRadioButton_Checked" /> </StackPanel> <TextBlock x:Name="infoTextBlock" TextWrapping="Wrap" /> <TextBlock x:Name="xTextBlock" Text="沿着X轴旋转" /> <Slider x:Name="xSlider" Minimum="0" Maximum="100" ValueChanged="xSlider_ValueChanged" /> <TextBlock x:Name="yTextBlock" Text="沿着Y轴旋转" /> <Slider x:Name="ySlider" Minimum="0" Maximum="100" ValueChanged="ySlider_ValueChanged" /> <TextBlock x:Name="zTextBlock" Text="沿着Z轴旋转" /> <Slider x:Name="zSlider" Minimum="0" Maximum="100" ValueChanged="zSlider_ValueChanged" /> </StackPanel> </Grid> </PivotItem> <PivotItem Header="SanweiPingyi" > <Grid x:Name="SanweiContentPanel" Margin="12,0,12,0" > <Grid.RowDefinitions> <RowDefinition Height="*" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <!--平移的对象--> <TextBlock Grid.Row="0" Text="3D" FontSize="120" Foreground="red" HorizontalAlignment="Center" VerticalAlignment="Center" > <TextBlock.Projection> <PlaneProjection x:Name="pingyiProjection" RotationX="45" /> </TextBlock.Projection> </TextBlock> <StackPanel Grid.Row="1" > <StackPanel Orientation="Horizontal" > <RadioButton x:Name="globalRadioButton" Content="GlobalOffset" Checked="rotationRadioButton_Checked" /> <RadioButton x:Name="localRadioButton" Content="LocalOffset" Checked="centerOfRotationRadioButton_Checked" /> </StackPanel> <TextBlock x:Name="infoTextBlock1" TextWrapping="Wrap" /> <TextBlock x:Name="xTextBlock1" Text="沿着X轴旋转" /> <Slider x:Name="xSlider1" Minimum="0" Maximum="100" ValueChanged="xSlider_ValueChanged1" /> <TextBlock x:Name="yTextBlock1" Text="沿着Y轴旋转" /> <Slider x:Name="ySlider1" Minimum="0" Maximum="100" ValueChanged="ySlider_ValueChanged1" /> <TextBlock x:Name="zTextBlock1" Text="沿着Z轴旋转" /> <Slider x:Name="zSlider1" Minimum="0" Maximum="100" ValueChanged="zSlider_ValueChanged1" /> </StackPanel> </Grid> </PivotItem> <PivotItem Header="Storyboard" > <StackPanel> <Ellipse x:Name="ellipse1" Width="150" Height="150" > </Ellipse> <StackPanel.Resources> <Storyboard x:Name="storyboard1" > <DoubleAnimation EnableDependentAnimation="True" Storyboard.TargetName="ellipse1" Storyboard.TargetProperty="Width" From="150" To="300" Duration="0:0:3" /> </Storyboard> </StackPanel.Resources> </StackPanel> </PivotItem> <PivotItem Header="myStoryboard" > <StackPanel Margin="12,0,12,0" > <StackPanel.Resources> <Storyboard x:Name="myStoryboard" > <DoubleAnimation From="0" To="300" AutoReverse="True" RepeatBehavior="Forever" Duration="0:0:3" Storyboard.TargetName="rect" Storyboard.TargetProperty="Width" EnableDependentAnimation="True" /> </Storyboard> </StackPanel.Resources> <Rectangle x:Name="rect" Width="0" Fill="Red" Height="100" /> <Button Content="启动动画" Click="Button_Click_3" /> </StackPanel> </PivotItem> <PivotItem Header="DoubleAnimation" > <Canvas Loaded="Page_Loaded" > <Canvas.Resources> <Storyboard x:Name="storyBoard" > <!--对ScaleTransform对象的ScaleY属性应用动画,表示沿着Y轴缩放倍数变化的动画--> <DoubleAnimation Storyboard.TargetName="scacleTransform" Storyboard.TargetProperty="ScaleY" From="1" To="2" Duration="0:0:3" RepeatBehavior="Forever" AutoReverse="True" > </DoubleAnimation> </Storyboard> </Canvas.Resources> <Rectangle x:Name="rectangle" Height="50" Width="50" Canvas.Left="75" Canvas.Top="75" Fill="Blue" > <Rectangle.RenderTransform> <!--<注意需要对ScaleTransform命名,否则无法定义动画的目标对象>--> <ScaleTransform x:Name="scacleTransform" > </ScaleTransform> </Rectangle.RenderTransform> </Rectangle> </Canvas> </PivotItem> <PivotItem Header="ColorAnimation" > <StackPanel> <StackPanel.Resources> <Storyboard x:Name="storybord1" > <ColorAnimation From="Red" To="Yellow" Storyboard.TargetName="button" Storyboard.TargetProperty="(Button.Background).(SolidColorBrush.Color)" Duration="0:0:5" > </ColorAnimation> </Storyboard> </StackPanel.Resources> <Button Content="开始动画" Margin="12" Click="Button_Click_4" HorizontalAlignment="Stretch" /> <Button Content="Test" x:Name="button" Margin="12" Click="button_Click_5" HorizontalAlignment="Stretch" /> </StackPanel> </PivotItem> <PivotItem Header="PointAnimation" > <StackPanel> <StackPanel.Resources> <Storyboard x:Name="storyboard2" > <PointAnimation From="50,0" To="100,50" Duration="0:0:3" Storyboard.TargetName="bezierSegment" Storyboard.TargetProperty="Point3" RepeatBehavior="5" EnableDependentAnimation="True" /> </Storyboard> </StackPanel.Resources> <Button Content="运行动画" Click="Button_Click_6" Margin="12" HorizontalAlignment="Stretch" /> <!--<Data属性由4个BezierSegment曲线和1条直线LineSegment组成>--> <Path Fill="#FF4080FF" HorizontalAlignment="Left" Height="100" Margin="162,164,0,0" Stretch="Fill" VerticalAlignment="Top" Width="100" > <Path.Data> <PathGeometry> <PathFigure StartPoint="100,50" > <BezierSegment Point1="100,77.6142" Point2="77.6142,100" Point3="50,100" /> <BezierSegment Point1="22.3858,100" Point2="0,77.6142" Point3="0,50" /> <BezierSegment Point1="0,22.3858" Point2="22.3858,0" Point3="50,0" /> <!--BezierSegment表示是圆右上的弧线,对其终点Point3进行动画处理--> <BezierSegment x:Name="bezierSegment" Point1="77.6142,0" Point2="100,22.3858" Point3="100,50" /> <LineSegment Point="50,50" /> </PathFigure> </PathGeometry> </Path.Data> </Path> </StackPanel> </PivotItem> <PivotItem Header="SplineDoubleKeyFrame" > <Grid x:Name="LayoutRoot" Background="Transparent" > <Grid.Resources> <Storyboard x:Name="SplineKeyStoryBoard" > <!--对第一个矩形的Canvas.Top属性使用样条关键帧动画--> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="srect" Storyboard.TargetProperty="(Canvas.Top)" Duration="0:0:10" RepeatBehavior="Forever" > <SplineDoubleKeyFrame Value="0" KeyTime="0:0:10" KeySpline="0.0,1.0 1.0,0.0" /> </DoubleAnimationUsingKeyFrames> <!--对第一个矩形的Canvas.Left属性使用线性关键帧动画--> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="srect" Storyboard.TargetProperty="(Canvas.Left)" Duration="0:0:10" RepeatBehavior="Forever" > <LinearDoubleKeyFrame Value="400" KeyTime="0:0:10" /> </DoubleAnimationUsingKeyFrames> <!--对第二个矩形的Canvas.Top属性使用线性关键帧动画--> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="srect2" Storyboard.TargetProperty="(Canvas.Top)" Duration="0:0:10" RepeatBehavior="Forever" > <LinearDoubleKeyFrame Value="0" KeyTime="0:0:10" /> </DoubleAnimationUsingKeyFrames> <!--对第二个矩形的Canvas.Left属性使用线性关键帧动画--> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="srect2" Storyboard.TargetProperty="(Canvas.Left)" Duration="0:0:10" RepeatBehavior="Forever" > <LinearDoubleKeyFrame Value="400" KeyTime="0:0:10" /> </DoubleAnimationUsingKeyFrames> </Storyboard> </Grid.Resources> <Canvas Margin="12,0,12,0" > <!--第一个矩形的运动轨迹时采用样条关键帧的方式从左下角向右上角用变化的加速度运动--> <Rectangle x:Name="srect" Width="50" Height="50" Fill="Purple" Canvas.Top="400" Canvas.Left="0" /> <!--第二个矩形的运动轨迹是采用线性关键帧的方式从左下角向右上角匀速运动--> <Rectangle x:Name="srect2" Width="50" Height="50" Fill="Red" Canvas.Top="400" Canvas.Left="0" /> <Button Content="运行动画" Canvas.Top="500" Click="Button_Click_7" /> </Canvas> </Grid> </PivotItem> <PivotItem Header="Discrete" > <Grid> <Grid.Resources> <Storyboard x:Name="Discretestoryboard" > <PointAnimationUsingKeyFrames Storyboard.TargetName="myLinearGradientBrush" Storyboard.TargetProperty="StartPoint" EnableDependentAnimation="True" RepeatBehavior="Forever" > <DiscretePointKeyFrame Value="0.1,0.3" KeyTime="0:0:0" /> <DiscretePointKeyFrame Value="0.2,0.4" KeyTime="0:0:1" /> <DiscretePointKeyFrame Value="0.3,0.5" KeyTime="0:0:2" /> <DiscretePointKeyFrame Value="0.4,0.6" KeyTime="0:0:3" /> <DiscretePointKeyFrame Value="0.5,0.7" KeyTime="0:0:4" /> <DiscretePointKeyFrame Value="0.6,0.8" KeyTime="0:0:5" /> <DiscretePointKeyFrame Value="0.7,0.9" KeyTime="0:0:6" /> </PointAnimationUsingKeyFrames> </Storyboard> </Grid.Resources> <StackPanel> <Ellipse x:Name="ellipse" Width="200" Height="200" > <Ellipse.Fill> <LinearGradientBrush x:Name="myLinearGradientBrush" StartPoint="0,0" EndPoint="1,0" > <LinearGradientBrush.GradientStops> <GradientStop Color="White" Offset="0.001" /> <GradientStop Color="Blue" Offset="1" /> </LinearGradientBrush.GradientStops> </LinearGradientBrush> </Ellipse.Fill> </Ellipse> <Button Content="启动动画" Height="100" Click="Button_Click_8" /> </StackPanel> </Grid> </PivotItem> <PivotItem Header="ChangeBackGroundDiscrete" > <Grid x:Name="LayoutRoot1" Background="Transparent" > <Grid.Resources> <Storyboard x:Name="ChangeBackGroundstoryboard" > <ObjectAnimationUsingKeyFrames Storyboard.TargetName="LayoutRoot1" Storyboard.TargetProperty="Background" Duration="0:0:4" RepeatBehavior="Forever" > <!--在1秒钟时间点上设置背景的画刷为LinearGradientBrush--> <DiscreteObjectKeyFrame KeyTime="0:0:1" > <DiscreteObjectKeyFrame.Value> <LinearGradientBrush> <LinearGradientBrush.GradientStops> <GradientStop Color="Yellow" Offset="0.0" /> <GradientStop Color="Orange" Offset="0.5" /> <GradientStop Color="Red" Offset="1.0" /> </LinearGradientBrush.GradientStops> </LinearGradientBrush> </DiscreteObjectKeyFrame.Value> </DiscreteObjectKeyFrame> <!--在2秒钟的时间点上设置背景的画刷为另外一个LinearGradientBrush--> <DiscreteObjectKeyFrame KeyTime="0:0:2" > <DiscreteObjectKeyFrame.Value> <LinearGradientBrush StartPoint="0,0" EndPoint="1,0" > <LinearGradientBrush.GradientStops> <GradientStop Color="White" Offset="0.0" /> <GradientStop Color="MediumBlue" Offset="0.5" /> <GradientStop Color="Black" Offset="1.0" /> </LinearGradientBrush.GradientStops> </LinearGradientBrush> </DiscreteObjectKeyFrame.Value> </DiscreteObjectKeyFrame> </ObjectAnimationUsingKeyFrames> </Storyboard> </Grid.Resources> <Grid x:Name="ChangeBackGroundContentPanel" Margin="12,0,12,0" > <Button Content="启动动画" Height="100" Click="Button_Click_9" /> </Grid> </Grid> </PivotItem> <PivotItem Header="BackEase" > <Grid Background="Transparent" > <Grid.Resources> <Storyboard x:Name="Easestoryboard" > <!--第一个椭圆的动画--> <DoubleAnimation From="1" To="2" Duration="0:0:3" Storyboard.TargetName="ellipselScaleTransform" Storyboard.TargetProperty="ScaleX" > <DoubleAnimation.EasingFunction> <BackEase Amplitude="0.3" EasingMode="EaseInOut" /> </DoubleAnimation.EasingFunction> </DoubleAnimation> <DoubleAnimation From="1" To="2" Duration="0:0:3" Storyboard.TargetName="ellipselScaleTransform" Storyboard.TargetProperty="ScaleY" > <DoubleAnimation.EasingFunction> <BackEase Amplitude="0.3" EasingMode="EaseInOut" /> </DoubleAnimation.EasingFunction> </DoubleAnimation> <!--第二个椭圆的动画--> <DoubleAnimation From="0" To="400" Duration="0:0:3" Storyboard.TargetName="ellipse4" Storyboard.TargetProperty="(Canvas.Left)" > </DoubleAnimation> <DoubleAnimation From="400" To="0" Duration="0:0:3" Storyboard.TargetName="ellipse4" Storyboard.TargetProperty="(Canvas.Top)" > <DoubleAnimation.EasingFunction> <BackEase Amplitude="0.3" EasingMode="EaseInOut" /> </DoubleAnimation.EasingFunction> </DoubleAnimation> </Storyboard> </Grid.Resources> <Canvas Margin="12,0,12,0" > <!--第一个椭圆展示了BackEase的放大动画效果--> <Ellipse Name="ellipse3" Width="80" Height="80" Fill="Blue" > <Ellipse.RenderTransform> <ScaleTransform x:Name="ellipselScaleTransform" > </ScaleTransform> </Ellipse.RenderTransform> </Ellipse> <!--第二个椭圆展示了BackEase的运动轨迹--> <Ellipse x:Name="ellipse4" Fill="Red" Width="80" Height="80" Canvas.Left="0" Canvas.Top="400" /> <Button Margin="0,500,0,0" Content="启动动画" Height="80" Click="Button_Click_10" /> </Canvas> </Grid> </PivotItem> <PivotItem Header="BounceEase" > <Grid Background="Transparent" > <Grid.Resources> <Storyboard x:Name="Bouncestoryboard" > <!--第一个椭圆的动画--> <DoubleAnimation From="0" To="200" Duration="0:0:3" EnableDependentAnimation="True" Storyboard.TargetName="ellipse5" Storyboard.TargetProperty="Width" > <DoubleAnimation.EasingFunction> <BounceEase Bounces="2" EasingMode="EaseOut" Bounciness="2" /> </DoubleAnimation.EasingFunction> </DoubleAnimation> <DoubleAnimation From="80" To="200" Duration="0:0:3" EnableDependentAnimation="True" Storyboard.TargetName="ellipse5" Storyboard.TargetProperty="Height" > <DoubleAnimation.EasingFunction> <BounceEase Bounces="2" EasingMode="EaseOut" Bounciness="2" /> </DoubleAnimation.EasingFunction> </DoubleAnimation> <!--第二个椭圆的动画--> <DoubleAnimation From="0" To="400" Duration="0:0:3" Storyboard.TargetName="ellipse6" Storyboard.TargetProperty="(Canvas.Left)" > </DoubleAnimation> <DoubleAnimation From="400" To="0" Duration="0:0:3" Storyboard.TargetName="ellipse6" Storyboard.TargetProperty="(Canvas.Top)" > <DoubleAnimation.EasingFunction> <BounceEase Bounces="2" EasingMode="EaseOut" Bounciness="2" /> </DoubleAnimation.EasingFunction> </DoubleAnimation> </Storyboard> </Grid.Resources> <Canvas Margin="12,0,12,0" > <!--第一个椭圆展示了BounceEase的动画效果--> <Ellipse Name="ellipse5" Width="80" Height="80" Fill="Blue" /> <!--第二个椭圆展示了BounceEase的运动轨迹--> <Ellipse x:Name="ellipse6" Fill="Red" Width="80" Height="80" Canvas.Left="0" Canvas.Top="400" /> <Button Margin="0,500,0,0" Content="启动动画" Height="80" Click="Button_Click_11" /> </Canvas> </Grid> </PivotItem> <PivotItem Header="CircleEase" > <Grid Background="Transparent" > <Grid.Resources> <Storyboard x:Name="CircleEasestoryboard" > <!--第一个椭圆的动画--> <DoubleAnimation From="80" To="400" Duration="0:0:3" EnableDependentAnimation="True" Storyboard.TargetName="ellipse7" Storyboard.TargetProperty="Width" > <DoubleAnimation.EasingFunction> <CircleEase EasingMode="EaseOut" /> </DoubleAnimation.EasingFunction> </DoubleAnimation> <!--第二个椭圆的动画--> <DoubleAnimation From="0" To="400" Duration="0:0:3" Storyboard.TargetName="ellipse8" Storyboard.TargetProperty="(Canvas.Left)" > </DoubleAnimation> <DoubleAnimation From="400" To="0" Duration="0:0:3" Storyboard.TargetName="ellipse8" Storyboard.TargetProperty="(Canvas.Top)" > <DoubleAnimation.EasingFunction> <CircleEase EasingMode="EaseOut" /> </DoubleAnimation.EasingFunction> </DoubleAnimation> </Storyboard> </Grid.Resources> <Canvas Margin="12,0,12,0" > <!--第一个椭圆展示了CircleEase的动画效果--> <Ellipse x:Name="ellipse7" Width="80" Height="80" Fill="Blue" /> <!--第二个椭圆展示了CircleEase的运动轨迹--> <Ellipse x:Name="ellipse8" Fill="Red" Width="80" Height="80" Canvas.Left="0" Canvas.Top="400" /> <Button Margin="0,500,0,0" Content="启动动画" Height="80" Click="Button_Click_12" /> </Canvas> </Grid> </PivotItem> <PivotItem Header="CubicEase" > <Grid Background="Transparent" > <Grid.Resources> <Storyboard x:Name="Cubicstoryboard" > <!--第一个椭圆的动画--> <DoubleAnimation From="0" To="180" Duration="0:0:3" Storyboard.TargetName="ellipselPlaneProjection" Storyboard.TargetProperty="RotationX" > <DoubleAnimation.EasingFunction> <CubicEase EasingMode="EaseInOut" /> </DoubleAnimation.EasingFunction> </DoubleAnimation> <!--第二个椭圆的动画--> <DoubleAnimation From="0" To="400" Duration="0:0:3" Storyboard.TargetName="ellipse10" Storyboard.TargetProperty="(Canvas.Left)" > </DoubleAnimation> <DoubleAnimation From="400" To="200" Duration="0:0:3" Storyboard.TargetName="ellipse10" Storyboard.TargetProperty="(Canvas.Top)" > <DoubleAnimation.EasingFunction> <CubicEase EasingMode="EaseInOut" /> </DoubleAnimation.EasingFunction> </DoubleAnimation> </Storyboard> </Grid.Resources> <Canvas Margin="12,0,12,0" > <!--第一个椭圆展示了CubicEase的动画效果--> <Ellipse x:Name="ellipse9" Width="200" Height="200" Fill="Blue" > <Ellipse.Projection> <PlaneProjection RotationX="0" x:Name="ellipselPlaneProjection" > </PlaneProjection> </Ellipse.Projection> </Ellipse> <!--第二个椭圆展示了CubicEase的运动轨迹--> <Ellipse x:Name="ellipse10" Fill="Red" Width="80" Height="80" Canvas.Left="0" Canvas.Top="400" /> <Button Margin="0,500,0,0" Content="启动动画" Height="80" Click="Button_Click_13" /> </Canvas> </Grid> </PivotItem> <PivotItem Header="ElasticEase" > <Grid Background="Transparent" > <Grid.Resources> <Storyboard x:Name="Elasticstoryboard" > <!--线条的动画--> <DoubleAnimation From="0" To="400" Duration="0:0:3" EnableDependentAnimation="True" Storyboard.TargetName="line1" Storyboard.TargetProperty="Y2" > <DoubleAnimation.EasingFunction> <ElasticEase EasingMode="EaseOut" Oscillations="7" /> </DoubleAnimation.EasingFunction> </DoubleAnimation> <!--第一个椭圆的动画--> <DoubleAnimation From="0" To="400" Duration="0:0:3" Storyboard.TargetName="ellipse11" Storyboard.TargetProperty="(Canvas.Top)" > <DoubleAnimation.EasingFunction> <ElasticEase EasingMode="EaseOut" Oscillations="7" /> </DoubleAnimation.EasingFunction> </DoubleAnimation> <!--第二个椭圆的动画--> <DoubleAnimation From="0" To="400" Duration="0:0:3" Storyboard.TargetName="ellipse12" Storyboard.TargetProperty="(Canvas.Left)" > </DoubleAnimation> <DoubleAnimation From="400" To="200" Duration="0:0:3" Storyboard.TargetName="ellipse12" Storyboard.TargetProperty="(Canvas.Top)" > <DoubleAnimation.EasingFunction> <ElasticEase EasingMode="EaseOut" Oscillations="7" /> </DoubleAnimation.EasingFunction> </DoubleAnimation> </Storyboard> </Grid.Resources> <Canvas Margin="12,0,12,0" > <!--线条展示了ElasticEase的动画效果--> <Line x:Name="line1" X1="50" Y1="0" X2="50" Y2="50" Stroke="Blue" StrokeThickness="10" Fill="Blue" /> <!--第一个椭圆展示了ElasticEase的动画效果--> <Ellipse Name="ellipse11" Fill="Blue" Width="100" Height="100" /> <!--第二个椭圆展示了ElasticEase的运动轨迹--> <Ellipse x:Name="ellipse12" Fill="Red" Width="80" Height="80" Canvas.Left="0" Canvas.Top="400" /> <Button Margin="0,500,0,0" Content="启动动画" Height="80" Click="Button_Click_14" /> </Canvas> </Grid> </PivotItem> <PivotItem Header="ExponentialEase" > <Grid Background="Transparent" > <Grid.Resources> <Storyboard x:Name="ExponentialStoryboard" > <!--第一个椭圆的动画--> <DoubleAnimation From="0" To="400" Duration="0:0:3" Storyboard.TargetName="ellipse13" Storyboard.TargetProperty="(Canvas.Top)" > <DoubleAnimation.EasingFunction> <ExponentialEase Exponent="10" EasingMode="EaseIn" /> </DoubleAnimation.EasingFunction> </DoubleAnimation> <!--第二个椭圆的动画--> <DoubleAnimation From="0" To="400" Duration="0:0:3" Storyboard.TargetName="ellipse14" Storyboard.TargetProperty="(Canvas.Left)" > </DoubleAnimation> <DoubleAnimation From="400" To="200" Duration="0:0:3" Storyboard.TargetName="ellipse14" Storyboard.TargetProperty="(Canvas.Top)" > <DoubleAnimation.EasingFunction> <ExponentialEase Exponent="10" EasingMode="EaseIn" /> </DoubleAnimation.EasingFunction> </DoubleAnimation> </Storyboard> </Grid.Resources> <Canvas Margin="12,0,12,0" > <!--第一个椭圆展示了ExponentialEase的动画效果--> <Ellipse x:Name="ellipse13" Width="50" Height="50" Fill="Blue" /> <!--第二个椭圆展示了ExponentialEase的运动轨迹--> <Ellipse x:Name="ellipse14" Fill="Red" Width="80" Height="80" Canvas.Left="0" Canvas.Top="400" /> <Button Margin="0,500,0,0" Content="启动动画" Height="80" Click="Button_Click_15" /> </Canvas> </Grid> </PivotItem> <PivotItem Header="PowerEase" > <Grid Background="Transparent" > <Grid.Resources> <Storyboard x:Name="powerStoryboard" > <!--第一个椭圆的动画--> <DoubleAnimation From="0" To="400" Duration="0:0:3" Storyboard.TargetName="ellipse15" Storyboard.TargetProperty="(Canvas.Top)" > <DoubleAnimation.EasingFunction> <PowerEase EasingMode="EaseIn" x:Name="powerEase1" /> </DoubleAnimation.EasingFunction> </DoubleAnimation> <!--第二个椭圆的动画--> <DoubleAnimation From="0" To="400" Duration="0:0:3" Storyboard.TargetName="ellipse16" Storyboard.TargetProperty="(Canvas.Left)" > </DoubleAnimation> <DoubleAnimation From="400" To="200" Duration="0:0:3" Storyboard.TargetName="ellipse16" Storyboard.TargetProperty="(Canvas.Top)" > <DoubleAnimation.EasingFunction> <PowerEase EasingMode="EaseIn" x:Name="powerEase2" /> </DoubleAnimation.EasingFunction> </DoubleAnimation> </Storyboard> </Grid.Resources> <Canvas Margin="12,0,12,0" > <Ellipse Name="ellipse15" Width="50" Height="50" Fill="Blue" /> <Ellipse x:Name="ellipse16" Fill="Red" Width="80" Height="80" Canvas.Left="0" Canvas.Top="400" /> <Button Margin="0,500,0,0" Content="启动动画" Height="80" Click="Button_Click_16" /> <Slider x:Name="powerSlider" Margin="200,500,0,0" Width="200" Background="Red" Value="50" Maximum="100" Minimum="0" /> </Canvas> </Grid> </PivotItem> <PivotItem Header="SineEase" > <Grid Background="Transparent" > <Grid.Resources> <Storyboard x:Name="SineStoryboard" > <DoubleAnimation From="0" To="400" Duration="0:0:3" Storyboard.TargetName="ellipse17" Storyboard.TargetProperty="(Canvas.Top)" > <DoubleAnimation.EasingFunction> <SineEase EasingMode="EaseIn" /> </DoubleAnimation.EasingFunction> </DoubleAnimation> <DoubleAnimation From="0" To="400" Duration="0:0:3" Storyboard.TargetName="ellipse18" Storyboard.TargetProperty="(Canvas.Left)" > </DoubleAnimation> <DoubleAnimation From="400" To="200" Duration="0:0:3" Storyboard.TargetName="ellipse18" Storyboard.TargetProperty="(Canvas.Top)" > <DoubleAnimation.EasingFunction> <SineEase EasingMode="EaseIn" /> </DoubleAnimation.EasingFunction> </DoubleAnimation> </Storyboard> </Grid.Resources> <Canvas Margin="12,0,12,0" > <Ellipse Name="ellipse17" Width="50" Height="50" Fill="Blue" /> <Ellipse x:Name="ellipse18" Fill="Red" Width="80" Height="80" Canvas.Left="0" Canvas.Top="400" /> <Button Margin="0,500,0,0" Content="启动动画" Height="80" Click="Button_Click_17" /> </Canvas> </Grid> </PivotItem> <PivotItem Header="JyzEase" > <Canvas Background="Gray" PointerMoved="Canvas_PointerMoved" > <Rectangle x:Name="prectangle" Height="50" Width="100" RadiusX="12.5" RadiusY="12.5" > <Rectangle.Fill> <LinearGradientBrush> <GradientStop Color="Black" Offset="0" /> <GradientStop Color="White" Offset="0.5" /> <GradientStop Color="Black" Offset="1" /> </LinearGradientBrush> </Rectangle.Fill> </Rectangle> </Canvas> </PivotItem> <PivotItem Header="TwoAnimationCompare" > <StackPanel> <StackPanel.Resources> <Storyboard x:Name="heightStoryboard" > <!--针对Height属性的动画--> <DoubleAnimation Storyboard.TargetName="rectangle1" Storyboard.TargetProperty="Height" RepeatBehavior="Forever" EnableDependentAnimation="True" From="100" To="200" Duration="0:0:2" > </DoubleAnimation> </Storyboard> <Storyboard x:Name="scaleTransformStoryboard" > <!--针对ScaleTransform的ScaleY属性的动画--> <DoubleAnimation Storyboard.TargetName="scaleTransform1" Storyboard.TargetProperty="ScaleY" RepeatBehavior="Forever" From="1" To="2" Duration="0:0:2" > </DoubleAnimation> </Storyboard> </StackPanel.Resources> <Button Content="阻塞UI线程" Click="Button_Click_18" /> <Button x:Name="heightAnimationButton" Content="Height属性动画" Click="heightAnimationButton_Click" /> <Button x:Name="scaleTransformAnimationButton" Content="ScaleTransform属性动画" Click="scaleTransformAnimationButton_Click" /> <Rectangle Height="100" Fill="Blue" x:Name="rectangle1" > <Rectangle.RenderTransform> <ScaleTransform x:Name="scaleTransform1" /> </Rectangle.RenderTransform> </Rectangle> </StackPanel> </PivotItem> <PivotItem Header="StarDown" > <Grid> <Canvas x:Name="myCanvas" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" > </Canvas> <Button x:Name="btn" VerticalAlignment="Bottom" Content="开始星星飘落" Click="btn_Click" /> </Grid> </PivotItem> <PivotItem Header="Style" > <StackPanel> <StackPanel.Resources> <Style x:Key="commonStyle" TargetType="Button" > <Setter Property="Width" Value="200" /> <Setter Property="Height" Value="100" /> <Setter Property="FontSize" Value="20" /> <Setter Property="Foreground" Value="Green" /> <Setter Property="Background" Value="Red" /> <Setter Property="FontFamily" Value="Arial" /> </Style> <Style x:Key="commonstyle2" TargetType="FrameworkElement" > <Setter Property="Width" Value="200" /> <Setter Property="Height" Value="100" /> </Style> </StackPanel.Resources> <Button Content="按钮1" Style="{StaticResource commonStyle}" /> <Button Content="按钮2" Style="{StaticResource commonStyle}" /> <Button Content="按钮3" Style="{StaticResource commonStyle}" /> <TextBlock Text="AAA" Style="{StaticResource commonstyle2}" /> </StackPanel> </PivotItem> <PivotItem Header="ControlTemplate" > <Button Content="Hello World" > <Button.Template> <ControlTemplate> <Grid> <Ellipse Width="{TemplateBinding Button.Width}" Height="{TemplateBinding Height}" Fill="{TemplateBinding Button.Background}" Stroke="Red" /> <TextBlock Margin="5,0,0,0" FontSize="50" VerticalAlignment="Center" HorizontalAlignment="Center" Text="{TemplateBinding Button.Content}" /> <TextBlock FontSize="50" Foreground="Red" VerticalAlignment="Center" HorizontalAlignment="Center" Text="{TemplateBinding Button.Content}" /> </Grid> </ControlTemplate> </Button.Template> </Button> </PivotItem> <PivotItem Header="ContentPresenter" > <Button> <Button.Template> <ControlTemplate> <Grid> <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalAlignment}" VerticalAlignment="{TemplateBinding VerticalAlignment}" /> </Grid> </ControlTemplate> </Button.Template> <Button.Content> <Rectangle Fill="Red" Height="50" Width="50" /> </Button.Content> </Button> </PivotItem> <PivotItem Header="VisualStateManager" > <Button Content="Hello World" LostFocus="Button_LostFocus" Tapped="Button_Tapped" > <Button.Template> <ControlTemplate TargetType="Button" > <Border> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CommonStates" > <VisualStateGroup.Transitions> <VisualTransition From="Test1" To="Test2" GeneratedDuration="0:0:1.5" > <Storyboard> </Storyboard> </VisualTransition> </VisualStateGroup.Transitions> <!--创建状态Test1把Border背景的颜色改成红色--> <VisualState x:Name="Test1" > <Storyboard> <ColorAnimation Storyboard.TargetName="BorderBrush" Storyboard.TargetProperty="Color" To="Red" /> </Storyboard> </VisualState> <!--创建状态Test2把Border背景的颜色改成蓝色--> <VisualState x:Name="Test2" > <Storyboard> <ColorAnimation Storyboard.TargetName="BorderBrush" Storyboard.TargetProperty="Color" To="Blue" /> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <Border.Background> <!--定义Border背景的颜色,用于测试不同状态的显示效果--> <SolidColorBrush x:Name="BorderBrush" Color="Black" /> </Border.Background> <Grid> <Ellipse x:Name="ellipse19" Width="{TemplateBinding Button.Width}" Height="{TemplateBinding Height}" Fill="{TemplateBinding Button.Background}" Stroke="Red" /> <TextBlock Margin="5,0,0,0" FontSize="50" VerticalAlignment="Center" HorizontalAlignment="Center" Text="{TemplateBinding Button.Content}" /> <TextBlock FontSize="50" Foreground="Red" VerticalAlignment="Center" HorizontalAlignment="Center" Text="{TemplateBinding Button.Content}" /> </Grid> </Border> </ControlTemplate> </Button.Template> </Button> </PivotItem> <PivotItem Header="DataTemplate" > <Button Content="Hello World" Width="100" LostFocus="Button_LostFocus" Tapped="Button_Tapped" > <Button.Template> <ControlTemplate TargetType="Button" > <Border> <VisualStateManager.VisualStateGroups> <VisualStateGroup> <VisualStateGroup.Transitions> <!--状态Test1转换为状态Test2的颜色变化动画--> <VisualTransition From="Test1" To="Test2" GeneratedDuration="0:0:1.5" > <Storyboard> </Storyboard> </VisualTransition> </VisualStateGroup.Transitions> <!--创建状态Test1把Border背景的颜色改成红色--> <VisualState x:Name="Test3" > <Storyboard> <ColorAnimation Storyboard.TargetName="BorderBrush1" Storyboard.TargetProperty="Color" To="Red" /> </Storyboard> </VisualState> <!--创建状态Test2把Border背景的颜色改成蓝色--> <VisualState x:Name="Test4" > <Storyboard> <ColorAnimation Storyboard.TargetName="BorderBrush1" Storyboard.TargetProperty="Color" To="Blue" /> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <Border.Background> <SolidColorBrush x:Name="BorderBrush1" Color="Black" /> </Border.Background> <Grid> <Ellipse x:Name="ellipse20" Width="{Binding Width}" Height="{Binding Height}" Fill="{Binding Background}" Stroke="Red" /> <TextBlock Margin="5,0,0,0" FontSize="50" VerticalAlignment="Center" HorizontalAlignment="Center" Text="{Binding}" /> <TextBlock FontSize="50" Foreground="Red" VerticalAlignment="Center" HorizontalAlignment="Center" Text="{Binding}" /> </Grid> </Border> </ControlTemplate> </Button.Template> </Button> </PivotItem> <PivotItem Header="DataTemplate1" > <StackPanel> <Button x:Name="singlePersonButton" Click="singlePersonButton_Click" ContentTemplate="{StaticResource PersonNameDataTemplate}" /> </StackPanel> </PivotItem> <PivotItem Header="DataTemplate2" > <ItemsControl x:Name="itemsControl" ItemTemplate="{StaticResource PersonNameDataTemplate}" /> </PivotItem> <PivotItem Header="DataTemplate3" > <ItemsControl x:Name="itemsControl1" ItemTemplate="{StaticResource PersonNameDataTemplate}" Style="{StaticResource ItemsControlStyle}" /> </PivotItem> <PivotItem Header="DataTemplate4" > <ListBox x:Name="listbox" ItemTemplate="{StaticResource dataTemplateDefaultKey}" /> </PivotItem> <PivotItem Header=" Binding" > <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" > <TextBlock Text="{Binding Title}" Margin="12,100,0,28" FontSize="50" /> </Grid> </PivotItem> <PivotItem Header="ElementNameBinding" > <StackPanel Margin="12,0,12,0" > <TextBlock FontSize="25" Text="圆形的半径会根据slider控件的值而改变" /> <Slider Name="slider1" Value="50" Maximum="400" /> <TextBlock FontSize="25" Text="半径为:" /> <TextBlock Name="txtblk" Text="{Binding ElementName=slider1,Path=Value}" FontSize="48" /> <Ellipse Height="{Binding ElementName=slider,Path=Value}" Width="{Binding ElementName=slider,Path=Value}" Fill="Red" Stroke="Black" StrokeThickness="1" /> </StackPanel> </PivotItem> <PivotItem Header="BindingMode" > <StackPanel Margin="12,0,12,0" > <Slider x:Name="slider" Value="50" Maximum="400" /> <!--OneTime--> <TextBlock FontSize="25" Height="41" Text="OneTime" VerticalAlignment="Top" Width="112" /> <TextBox Height="72" Text="{Binding ElementName=slider,Path=Value,Mode=OneTime}" Width="269" /> <TextBlock FontSize="25" Height="46" Text="OneWay" VerticalAlignment="Top" Width="99" /> <!--OneWay--> <TextBox Height="72" Text="{Binding ElementName=slider,Path=Value,Mode=OneWay}" Width="269" /> <TextBlock Height="40" FontSize="25" Text="TwoWay" VerticalAlignment="Top" Width="94" /> <!--TwoWay--> <TextBox Height="72" Text="{Binding ElementName=slider,Path=Value,Mode=TwoWay}" Width="268" /> <TextBlock FontSize="25" Height="43" Text="slider的值:" /> <TextBlock FontSize="25" Height="43" Text="{Binding ElementName=slider,Path=Value}" Width="185" /> </StackPanel> </PivotItem> <PivotItem Header="INotifyPropertyChanged" > <StackPanel Margin="12,0,12,0" > <TextBlock Text="{Binding Title}" FontSize="50" /> <Button Content="改变数据源的数据" Click="Button_Click_19" /> </StackPanel> </PivotItem> <PivotItem Header="IValueConverter" > <Grid> <StackPanel Margin="12,0,12,0" DataContext="{StaticResource clock}" > <TextBlock FontSize="30" Text="{Binding Hour,Converter={StaticResource booleanToDayString}}" /> <TextBlock FontSize="30" Text="现在的时间是:" /> <TextBlock FontSize="20" Text="{Binding Hour}" /> <TextBlock FontSize="20" Text="小时" /> <TextBlock FontSize="20" Text="{Binding Minute}" /> <TextBlock FontSize="20" Text="分钟" /> <TextBlock FontSize="20" Text="{Binding Second}" /> <TextBlock FontSize="20" Text="秒" /> </StackPanel> </Grid> </PivotItem> <PivotItem Header="BindingItemTemplate" > <ListView x:Name="listviewItemtemplate" > <ListView.ItemTemplate> <!--列表的ItemTemplate属性是一个DataTemplate类型--> <!--创建一个DataTemplate的元素对象--> <DataTemplate> <StackPanel Orientation="Horizontal" Background="Gray" Margin="10" > <!--绑定Food类的IconUri属性--> <Image Source="{Binding IconUri}" Stretch="None" /> <TextBlock Text="{Binding Name}" FontSize="40" Margin="24 0 24 0" /> <TextBlock Text="{Binding Description}" FontSize="20" /> </StackPanel> </DataTemplate> </ListView.ItemTemplate> </ListView> </PivotItem> <PivotItem Header="ObservableCollection" > <Grid> <Grid.RowDefinitions> <RowDefinition Height="*" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <StackPanel Margin="12,60,0,28" > <Button Content="AddItem" Click="Button_Click_20" /> <Button Content="RemoveItem" Click="Button_Click_21" /> </StackPanel> <Grid Margin="12,0,12,0" Grid.Row="1" > <ListView x:Name="Obser" > <ListView.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal" Margin="10" > <TextBlock Text="{Binding OrderId}" FontSize="30" /> <TextBlock Text="{Binding OrderName}" FontSize="30" Width="280" /> </StackPanel> </DataTemplate> </ListView.ItemTemplate> </ListView> </Grid> </Grid> </PivotItem> <PivotItem Header="IList" > <ListView x:Name="Ilist" > <ListView.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal" Margin="10" > <TextBlock Text="{Binding OrderId}" FontSize="30" /> <TextBlock Text="{Binding OrderName}" FontSize="30" Width="280" /> </StackPanel> </DataTemplate> </ListView.ItemTemplate> </ListView> </PivotItem> <PivotItem Header="ItemsControl" > <ItemsControl x:Name="itemsControl2" > <ItemsControl.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal" > <TextBlock Text="{Binding FirstName}" FontSize="30" /> <TextBlock Text="{Binding LastName}" FontSize="30" Margin="30,0,0,0" /> </StackPanel> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </PivotItem> <PivotItem Header="ScrollItemsControl" > <ItemsControl x:Name="ItemsControl3" > <ItemsControl.Template> <ControlTemplate TargetType="ItemsControl" > <ScrollViewer> <ItemsPresenter/> </ScrollViewer> </ControlTemplate> </ItemsControl.Template> <ItemsControl.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal" > <TextBlock Text="{Binding FirstName}" FontSize="30" /> <TextBlock Text="{Binding LastName}" FontSize="30" Margin="30,0,0,0" /> </StackPanel> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </PivotItem> <PivotItem Header="ListBox" > <ListBox ItemsSource="{Binding Items}" SelectionChanged="ListBox_SelectionChanged" > <ListBox.Template> <ControlTemplate TargetType="ItemsControl" > <ScrollViewer> <StackPanel> <ItemsPresenter/> <Button Content="加载更多" Click="Button_Click_22" HorizontalAlignment="Center" Margin="12" /> </StackPanel> </ScrollViewer> </ControlTemplate> </ListBox.Template> <ListBox.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal" > <TextBlock Text="{Binding FirstName}" FontSize="30" /> <TextBlock Text="{Binding LastName}" FontSize="30" Margin="30,0,0,0" /> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </PivotItem> <PivotItem Header="ListView" > <ListView x:Name="listView" ItemsSource="{Binding Items}" > <ListView.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal" > <TextBlock Text="{Binding FirstName}" FontSize="30" /> <TextBlock Text="{Binding LastName}" FontSize="30" Margin="30,0,0,0" /> </StackPanel> </DataTemplate> </ListView.ItemTemplate> </ListView> </PivotItem> <PivotItem Header="GridView" > <GridView x:Name="gridView" > <GridView.ItemTemplate> <DataTemplate> <StackPanel> <TextBlock Text="{Binding FirstName}" Width="80" /> <TextBlock Text="{Binding LastName}" Width="80" /> </StackPanel> </DataTemplate> </GridView.ItemTemplate> <GridView.ItemContainerStyle> <Style TargetType="GridViewItem" > <Setter Property="BorderBrush" Value="Gray" /> <Setter Property="BorderThickness" Value="1" /> <Setter Property="HorizontalContentAlignment" Value="Center" /> <Setter Property="VerticalContentAlignment" Value="Center" /> </Style> </GridView.ItemContainerStyle> </GridView> </PivotItem> <PivotItem Header="SemanticZoom" > <Grid Margin="12,0,12,0" > <Grid.Resources> <!--创建数据源对象,注意ItemContent属性就是数据源中真正的基础数据的列表属性,必须设置该属性的值数据源才能定位到实际绑定的数据实体对象--> <CollectionViewSource x:Name="itemcollectSource" IsSourceGrouped="True" ItemsPath="ItemContent" /> </Grid.Resources> <SemanticZoom x:Name="semanticZoom" > <SemanticZoom.ZoomedInView> <!--在这里放置GridView(或ListView)以表示放大视图--> <ListView x:Name="inView" > <ListView.GroupStyle> <GroupStyle> <!--用于显示列表有的数据项的模板--> <GroupStyle.HeaderTemplate> <DataTemplate> <Border Background="Red" Height="80" > <TextBlock Text="{Binding Key}" FontSize="50" /> </Border> </DataTemplate> </GroupStyle.HeaderTemplate> </GroupStyle> </ListView.GroupStyle> <!--用于显示列表的数据项的模板--> <ListView.ItemTemplate> <DataTemplate> <StackPanel> <TextBlock Text="{Binding Title}" Height="40" FontSize="30" /> </StackPanel> </DataTemplate> </ListView.ItemTemplate> </ListView> </SemanticZoom.ZoomedInView> <SemanticZoom.ZoomedOutView> <!--在这里放置GridView(或ListView)以表示缩小视图--> <GridView x:Name="outView" > <!--用于显示弹出的分组列表视图的数据项的模板--> <GridView.ItemTemplate> <DataTemplate> <Border Height="60" > <TextBlock Text="{Binding Group.Key}" FontSize="24" /> </Border> </DataTemplate> </GridView.ItemTemplate> <!--列表布局模板--> <GridView.ItemsPanel> <ItemsPanelTemplate> <WrapGrid ItemWidth="100" ItemHeight="75" MaximumRowsOrColumns="1" VerticalChildrenAlignment="Center" /> </ItemsPanelTemplate> </GridView.ItemsPanel> <!--列表项目容器的样式设置--> <GridView.ItemContainerStyle> <Style TargetType="GridViewItem" > <Setter Property="BorderBrush" Value="Gray" /> <Setter Property="Background" Value="Red" /> <Setter Property="BorderThickness" Value="3" /> <Setter Property="HorizontalAlignment" Value="Center" /> <Setter Property="VerticalAlignment" Value="Center" /> </Style> </GridView.ItemContainerStyle> </GridView> </SemanticZoom.ZoomedOutView> </SemanticZoom> </Grid> </PivotItem> <PivotItem Header="VirtualDataList" > <ListView ItemsSource="{Binding Data}" > <ListView.ItemTemplate> <DataTemplate> <StackPanel> <TextBlock Text="{Binding Name}" Height="50" /> </StackPanel> </DataTemplate> </ListView.ItemTemplate> </ListView> </PivotItem> <PivotItem Header="ItemsStackPanel" > <ItemsControl x:Name="imageList" > <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <!--设置横向布局--> <ItemsStackPanel Orientation="Horizontal" /> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.Template> <ControlTemplate TargetType="ItemsControl" > <!--设置水平滚动--> <ScrollViewer ScrollViewer.HorizontalScrollBarVisibility="Visible" ScrollViewer.VerticalScrollBarVisibility="Disabled" > <!--<StackPanel>--> <ItemsPresenter/> <!--</StackPanel>--> </ScrollViewer> </ControlTemplate> </ItemsControl.Template> <ItemsControl.ItemTemplate> <DataTemplate> <StackPanel> <Image Source="{Binding Image}" Width="144" Height="240" Stretch="Uniform" /> <TextBlock Text="{Binding ImageName}" /> </StackPanel> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </PivotItem> <PivotItem Header="ImageShow" > <ListView x:Name="InternetImage" > <ListView.ItemTemplate> <DataTemplate> <StackPanel> <TextBlock Text="{Binding Name}" Height="80" /> <Image Source="{Binding ImageSource}" Width="200" Height="200" /> </StackPanel> </DataTemplate> </ListView.ItemTemplate> </ListView> </PivotItem> <PivotItem Header="Polyline" > <Polyline Stroke="LightBlue" StrokeThickness="5" Height="150" Points="0,10 50,40 100,90 150,50 200,50 250,10 300,100 350,30 400,0" /> </PivotItem> <PivotItem Header="Polygon" > <Polygon Fill="AliceBlue" StrokeThickness="5" Height="150" Stroke="Red" Points="0,150 0,10 50,40 100,90 150,50 200,50 250,10 300,100 350,30 400,0 400,150" /> </PivotItem> <PivotItem Header="GetLineChartPointCollection" > <Grid Margin="12,0,12,0" > <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <Grid x:Name="chartCanvas" Height="400" HorizontalAlignment="Center" VerticalAlignment="Center" > </Grid> <StackPanel Grid.Row="1" > <Button Content="折线图" Click="Button_Click_23" /> <Button Content="区域图" Click="Button_Click_24" /> </StackPanel> </Grid> </PivotItem> <PivotItem Header="PiePlotter" > <StackPanel> <controls:PiePlotter x:Name="piePlotter" Width="400" Height="400" PieWidth="400" HoleSize="0.2" /> </StackPanel> </PivotItem> <PivotItem Header="ChartCanvas" > <Grid> <Grid.RowDefinitions> <RowDefinition Height="80" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <!--图例面板--> <Canvas x:Name="legendCanvas" Height="80" Width="200" /> <!--坐标轴面板--> <Canvas x:Name="textCanvas" Width="300" Height="300" Grid.Row="1" > <!--线性图形面板--> <Canvas x:Name="linechartCanvas" Width="300" Height="300" /> </Canvas> </Grid> </PivotItem> </Pivot> </Grid> <Page.BottomAppBar> <!--菜单栏新增按钮跳转到添加购物清单页面--> <CommandBar Opened="CommandBar_Opened" Closed="CommandBar_Closed" > <AppBarButton Label="新增" Icon="Add" Click="AppBarButton_Click_1" /> <AppBarButton Label="buy" Icon="Shop" /> <!--BitmapIcon图标按钮--> <AppBarButton Label="BitmapIcon" Click="AppBarButton_Click" > <AppBarButton.Icon> <BitmapIcon UriSource="ms-appx:///Assets/questionmark.png" /> </AppBarButton.Icon> </AppBarButton> <!--FontIcon图标按钮--> <AppBarButton Label="FontIcon" Click="AppBarButton_Click" > <AppBarButton.Icon> <FontIcon FontFamily="Candara" Glyph="Σ" /> </AppBarButton.Icon> </AppBarButton> <!--PathIcon图标按钮--> <AppBarButton Label="PathIcon" Click="AppBarButton_Click" > <AppBarButton.Icon> <PathIcon Data="F1 M 20,20L 24,10L 24,24L 5,24" /> </AppBarButton.Icon> </AppBarButton> <!--文本菜单按钮--> <CommandBar.SecondaryCommands> <AppBarButton Label="about" Click="AppBarButton_Click" /> </CommandBar.SecondaryCommands> </CommandBar> </Page.BottomAppBar> </Page> /// <summary> /// 添加图表 /// </summary> /// <exception cref="NotImplementedException" ></exception> private void AddChart () { //添加报表的网格图形 cs=new ChartStyleGridlines(); cs.ChartCanvas = linechartCanvas; cs.TextCanvas = textCanvas; cs.Title = "Sine and Cosine Chart" ; cs.Xmin = 0; cs.Xmax = 7; cs.Ymin = -1.5; cs.Ymax = 1.5; cs.YTick = 0.5; cs.GridlinePattern = GridlinePatternEnum.Dot; cs.GridlineColor = new SolidColorBrush(Colors.Black); cs.AddChartStyle(new TextBlock()); //画Sine曲线图形 //ds = new DataSeries(); ds.LineColor=new SolidColorBrush(Colors.Black); ds.LineThickness = 1; ds.SeriesName = "Sine" ; //计算出图形中的一系列点,然后用线段连接起来 for (int i = 0; i < 36; i++) { double x = i / 5.0; double y = Math.Sin(x); ds.LineSeries.Points.Add(new Point(x, y)); } dc.DataList.Add(ds); //画cosine曲线图形 ds=new DataSeries(); ds.LineColor=new SolidColorBrush(Colors.Red); ds.SeriesName = "Cosine" ; ds.LinePattern = GridlinePatternEnum.DashDot; ds.LineThickness = 2; for (int i = 0; i < 36; i++) { double x = i / 5.0; double y = Math.Cos(x); ds.LineSeries.Points.Add(new Point(x,y)); } dc.DataList.Add(ds); //画sine^2曲线图形 ds = new DataSeries(); ds.LineColor = new SolidColorBrush(Colors.Green); ds.SeriesName = "Sine^2" ; ds.LinePattern = GridlinePatternEnum.Dot; ds.LineThickness = 2; for (int i = 0; i< 36; i++) { double x = i / 5.0; double y = Math.Sin(x)*Math.Sin(x); ds.LineSeries.Points.Add((Point)new Point(x,y)); } dc.DataList.Add(ds); dc.AddLines(linechartCanvas,cs); //添加图例 lg.legendCanvas = legendCanvas; lg.IsLegend = true ; lg.IsBorder = true ; lg.AddLegend(cs,dc); } //网格图形 private ChartStyleGridlines cs; //图例 private Legend lg = new Legend(); //图形数据集合 private DataCollection dc = new DataCollection(); //线性数据图形 private DataSeries ds=new DataSeries();
13.4.QuickCharts图表控件库解析 QuickCharts图表控件是Amcharts公司提供的一个开源图表控件库,这个控件库支持WPF,Silverlight和Windows平台,源码可以从Github网站(http://github.com/ailon/amCharts-Qucik-Charts)下载 . QuickCharts图表控件封装了一些常用的图表控件如饼图,柱形图,折线图,区域图等,可以直接再项目中进行其提供的图表控件来创建图表. 13.41.QuickCharts项目结构分析 QuickCharts控件库包含了两类图表,一种是饼图图表PieChart,另外一种是连续图表SerialChart.连续图包含了线形,柱形,区域图等图形. ![1.png] ![2.png] 13.42.饼图图表PieChart的实现逻辑 饼图PieChart是由多个饼图切片Slice控件,一个图例控件Legend控件和一个标注Balloon控件组成.