Windows通用应用程序开发(三)


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+M21Y+M31Z+OffsetX.
Y=M12*X+M22*Y+M32*Z+OffsetY. Z=M13
X+M23Y+M33Z+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-tasin(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
{
#region MyRegion

/// <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(); }
#endregion
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
{
#region 依赖属性

/// <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;



#endregion
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="&#xE700;"/>

</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="&#x03a3;"/>
</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控件组成.

深入浅出Windows应用程序开发(二)


应用数据

6.1 应用设置简介
1.拥有容器的层次结构
2.有本地和漫游两种设置类型
Win10支持本地和漫游两种类型,本地是指数据只会存在于当前的客户端应用程序里面,漫游则是指数据会同步到其他设备的相同账户的客户端里.本地应用设置是在根容器ApplicationData.Current.LocalSettings下,而漫游应用设置是在根容器ApplicationData.Current.RoamingSetting下,只是存储的根目录不一样,其他Api操作完全一致.
3.应用设置支持大多数Windows运行时数据类型
应用设置所存储的数据是单个的数据类型对象,它并不是所有的类型都会支持,例如,集合对象不支持,如果要将List对象存储到应用设置里会引起异常信息.
数值类型:UInt8-64 Int16-64 Single Double
布尔类型:Boolean
字符类型:Char16,String
时间类型:DateTime,TimeSpan
结构类型:GUID,Point,Size,Rect
组合类型:ApplicationDataCompositeValue
对于不支持的类型,使用应用文件存储,另一种是将数据序列化为一种受支持的数据类型
6.1.2.应用设置操作
应用设置操作支持增删改查,在开始对应用设置操作之前,首先要获取到应用设置的容器对象(ApplicationDataContainer),所有的操作都会从一个容器对象开始.获取应用程序的根容器可以通过ApplicationData.Current属性来获取单例对象
1.添加和修改应用设置
在进行应用设置相关操作前,需要先获取应用的设置

1
ApplicationDataContainer localSettings=Windows.Storage.ApplicationData.Current.LocalSettings;

获取容器之后,将数据添加到应用设置,如果该应用设置已存在,则对其进行修改,使用ApplicationDataContainer.Values属性可以访问在上一步中获取的LocalSettings容器中的设置,然后通过键/值对方式来操作应用设置.

1
localSettings.Values["testSetting"]="Hello Windows10";

如果容器里面没有”testSetting”这个键则新增一个,如果已有,则对其进行修改.
使用ApplicationDataContainer.Values属性可以访问在上一步中获取的LocalSetting容器中的设置,然后通过键/值对的方式来操作应用设置.

1
bool isoKeyExist=localSettings.Values.ContainKey("testSetting");

2.读取应用设置
从设置中读取数据,也是使用ApplicationDataContainer.Values属性来获取应用设置的值

1
String value=localSettings.Values["testSetting"].ToString();

3.删除应用设置
如果需要删除应用程序里面的设置数据,可以用ApplicationDataContainerSettings.Remove方法来实现.

1
localSettings.Values.Remove("testSetting")
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
<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>

///获取当前应用程序的本地设置根容器
_appSettings = ApplicationData.Current.LocalSettings;
//把容器的键列表绑定到list控件
BindKeyList();
/// <summary>
/// 将当前程序中所有的key值绑定到List上
/// </summary>
private void BindKeyList()
{
//先清空List控件的绑定值
lstKeys.Items.Clear();
//获取当前应用程序的所有Key
foreach (var item in _appSettings.Values.Keys)
{
//添加到List控件上
lstKeys.Items.Add(item);
}
txtKey.Text = "";
txtValue.Text = "";
}

//声明容器实例
private ApplicationDataContainer _appSettings;
/// <summary>
/// 保存应用设置的值
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void btnSave_Click(object sender, RoutedEventArgs e)
{
//检查key输入框不为空
if (!string.IsNullOrEmpty(txtKey.Text))
{
_appSettings.Values[txtKey.Text] = txtValue.Text;
BindKeyList();
}
else
{
await new MessageDialog("请输入key值").ShowAsync();
}
}
/// <summary>
/// 删除在List中选中的应用设置
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnDelete_Click(object sender, RoutedEventArgs e)
{
//如果选中了List中的某项
if (lstKeys.SelectedIndex > -1)
{
//移除这个键的独立存储设置
_appSettings.Values.Remove(lstKeys.SelectedItem.ToString());
BindKeyList();
}
}
/// <summary>
/// 清空容器所有的设置
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void deleteall_Click(object sender, RoutedEventArgs e)
{
_appSettings.Values.Clear();
BindKeyList();
}
//List控件选中项的事件,将选中的键和值显示在上面的文本框中.
private void lstKeys_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Count > 0)
{
//获取在List中选择的Key
string key = e.AddedItems[0].ToString();
//检查设置是否存在这个key
if (_appSettings.Values.ContainsKey(key)) {
txtKey.Text = key;
//获取key的值并显示在文本框上
txtValue.Text = _appSettings.Values[key].ToString();
}
}
}

6.13.存储容器设置
1.容器的创建
容器的创建必须依赖容器的对象,也就是说必须要在容器下面创建容器,所以容器里是可以嵌套着容器的,
但是这个嵌套的层次不能超过32层.首先需要获取根容器,然后调用ApplicationDataContainer.CreateContainer方法,可创建设置容器.该方法有两个参数,第一个是容器的名字,注意同一个容器里面不能有相同名字的两个容器,第二个是ApplicationDataCreateDisposition枚举,通常会设置为枚举中的Always值,表示如果容器不存在则新建一个再返回容器对象.

1
ApplicationDataContainer container=localSettings.CreateContainer("exampleContainer",ApplicationDataCreateDisposition.Always);

2.容器的删除
容器的删除可以调用ApplicationDataContainer.DeleteContainer方法,通过传入容器的名称可以删除当前容器下的该名称的容器,注意不是删除容器对象的这个容器.删除容器后,容器下面的应用设置信息也会全部删除掉.所以在做容器删除时一定要确认该容器下面的应用设置信息是否已经完全不需要了,否则会造成信息的丢失.

1
localSettings.DeleteContainer("exampleContainer");
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
<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>

///获取根容器
localSettings=ApplicationData.Current.LocalSettings;
//输出容器相关信息
DisplayOutput();
/// <summary>
/// 展示创建的容器和应用设置的信息
/// </summary>
private void DisplayOutput()
{
//判断容器是否存在
bool hasContainer=localSettings.Containers.ContainsKey(containerName);
//判断容器里面的键值是否存在
bool hasSetting = hasContainer ? localSettings.Containers[containerName].Values.ContainsKey(settingName) : false;
string output = string.Format(
$"Container Exists:{(hasContainer?true:false)}\n Setting Exists:{(hasSetting?true:false)}");
OutputTextBlock.Text = output;
}

/// <summary>
/// 创建一个容器
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void CreateContainer_Click(object sender, RoutedEventArgs e)
{
//如果容器不存在则新建
ApplicationDataContainer container = localSettings.CreateContainer(containerName, ApplicationDataCreateDisposition.Always);
DisplayOutput();
}
//在新建的容器上添加应用设置信息
private void WriteSetting_Click(object sender, RoutedEventArgs e)
{
if (localSettings.Containers.ContainsKey(containerName))
{
localSettings.Containers[containerName].Values[settingName] = "Hello World";
}
DisplayOutput();

}
/// <summary>
/// 删除容器上的应用设置信息
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void DeleteSetting_Click(object sender, RoutedEventArgs e)
{
if (localSettings.Containers.ContainsKey(containerName))
{
localSettings.Containers[containerName].Values.Remove(settingName);
}
DisplayOutput();
}
/// <summary>
/// 删除所创建的容器
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void DeleteContainer_Click(object sender, RoutedEventArgs e)
{
localSettings.DeleteContainer(containerName);
DisplayOutput();
}

6.14.复合设置数据
应用设置还支持一个特殊的类型 ApplicationDataCompositeValue,就是复合设置.
ApplicationDataCompositeValue类表示必须进行自动序列化和反序列化的相关应用程序的设置.
复合设置通过将其插入设置映射而序列化,通过从映射查找该设置而反序列化.针对少量数据进行了优化,用于大型数据集,性能可能很差.所以复合设置使用的场景通常是将一组互相依赖的数据捆绑在一起,保证在任何情况下,他们都是作为一个整体进行操作.复合设置是应用设置中的一种,所以其相关的操作和应用设置一致.ApplicationDataCompositeValue对象也是一个键/值对的对象类型.

1
2
3
ApplicationDataCompositeValue composite=new ApplicationDataCompositeValue();
composite["intVal"]=1;
composite["strVal"]="string";
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
 <PivotItem Header="ApplicationDataCompositeValue">
<StackPanel>
<Button Content="创建" Click="WriteCompositeSetting_Click"/>
<Button Content="删除" Click="DeleteCompositeSetting_Click"/>
<TextBlock x:Name="OutputTextBlock1" TextWrapping="Wrap"/>
</StackPanel>
</PivotItem>

///ApplicationDataCompositiveValue
roamingSettings=ApplicationData.Current.LocalSettings;
//展示信息
DisplayOutputAdcv();

/// <summary>
/// 展示复合设置的信息
/// </summary>
private void DisplayOutputAdcv()
{
ApplicationDataCompositeValue composite = (ApplicationDataCompositeValue)roamingSettings.Values[settingNameA];
string output;
if (composite==null)
{
output = "复合设置信息为空";
}
else
{
output =$"复合设置:{settingNameB}={composite[settingNameB]},{settingNameC}={composite[settingNameC]}";
}
OutputTextBlock1.Text = output;
}

ApplicationDataContainer roamingSettings = null;
//复合设置的名称
const string settingNameA = "exampleCompositeSetting";
//复合设置里面的键One
const string settingNameB = "one";
//复合设置里面的键two
const string settingNameC = "two";
/// <summary>
/// 写入复合设置信息
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void WriteCompositeSetting_Click(object sender, RoutedEventArgs e)
{
ApplicationDataCompositeValue composite=new ApplicationDataCompositeValue();
composite[settingNameB] = 1;
composite[settingNameC] = "world";
roamingSettings.Values[settingNameA]=composite;
DisplayOutputAdcv();
}
/// <summary>
/// 删除复合设置信息
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void DeleteCompositeSetting_Click(object sender, RoutedEventArgs e)
{
roamingSettings.Values.Remove(settingNameA);
DisplayOutputAdcv();
}

6.2 应用文件存储
在每个应用的应用数据存储中,该应用拥有系统定义的根目录,一个用于本地文件,一个用于漫游文件,还有一个用于临时文件,应用可向根目录添加新文件和新目录,创建新目录可组织文件,分本地和漫游
6.21.三种类型的应用文件
1.本地应用文件
存储在客户端的存储数据,存储区域属于程序的沙箱里,应用程序自己才可以访问,保障了数据的安全性.应用程序卸载后无法恢复.本地应用文件的根目录文件夹可以通过ApplicationData对象的LocalFolder属性来访问,即ApplicationData.Current.LocalFolder,类型是文件夹(StorageFolder)对象.
2.漫游应用文件
漫游应用文件是指对同一个账号登录的设备共享的数据,用户可以轻松在多个设备保持应用数据同步.漫游应用文件的根目录文件夹可以通过ApplicationData对象的RoamingFolder属性来访问.也就是ApplicationData.Current.RoamingFolder.
1.数据大小有限制.
大小由ApplicationData类的RoamingStorageQuota属性决定.RoamingStorageQuota属性表示获取可从漫游应用程序数据存储区同步到云的数据的最大值,如果漫游数据在漫游应用程序数据存储区中的当前大小超过RoamingStorageQuota指定的最大值,则系统会挂起并将包中所有应用程序的数据复制到云,直到当前大小不再超过最大值.出于此原因,最好的做法是仅为用户首选项,链接和小型数据文件使用漫游数据.
2.数据改变时机的不确定性
如果需要监控漫游数据的变化,应用应该注册处理ApplicationData类的DataChanged事件,处理操作在漫游应用程序更改时执行.
3.数据版本的一致性
更新新版本之前,不会将应用数据更新到用户安装了该应用的其他设备.
4.漫游数据不是永久的,有时间限制.
漫游数据同步并不是无期限同步的,它有一个30天的时间间隔限制,用户可在此时间间隔访问漫游数据.超过 云删除.卸载 漫游数据保留 在改时间间隔重新安装,会从云中同步漫游数据.
5.漫游数据同步时机依赖于网络和设备
随机 漫游数据是应用设置,可以通过特殊的设置键来设置一个高优先级别的漫游设置数据,可以更快频繁和快速同步到云端.这个高优先级别的key为HighPriority,系统会以最快的速度在多个设备间同步HighPriority所对应的数据.它支持ApplicationDataCompositeValue数据,但总大小限于8KB,限值不是强制的,超过视为常规漫游。
3.临时应用文件
类似缓存,不会漫游,随时删除.磁盘清理清除 用于存储应用会话期间的临时信息,无法保证超出应用会话结束时间后仍将保留此数据.临时应用文件的根目录文件夹可以通过ApplicationData对象的TemporaryFolder属性来访问,也就是ApplicationData.Current.TemporaryFolder.
6.22.应用文件和文件夹操作
win10对应用数据所进行的操作,就是对应用文件夹和文件的操作.
StorageFolder类表示操作文件夹及其内容,并提供有关它们的信息,用于向本地文件夹内的某个文件读取和写入数据.在设备存储里文件夹的根目录分别为本地文件夹(ApplicationData.Current.LocalFolder),漫游文件夹(ApplicationData.Current.RoamingFolder)和临时文件夹(ApplicationData.Current.TemporaryFolder).
Storage类表示文件,提供有关文件及其内容和操作信息.StorageFolder类和StorageFile类是两个关系非常密切的类
StorageFolder类的主要成员
DateCreated 获取创建文件夹的日期和时间
Name 获取存储文件夹的名称
Path 获取存储文件夹的路径
CreteFileAsync(string desiredName) 在文件夹或文件组中创建一个新文件,desiredName:要创建的文件的所需名称,返回表示新文件的StorageFile
CreateFolderAsync(string desiredName) 在当前文件夹中创建新的文件夹.desiredName:要创建的文件夹所需名称.返回表示新文件夹的StorageFolder.
DeleteAsync(); 删除当前文件夹或文件组.
GetFileAsync(string name) 从当前文件夹获取指定文件.name:要检索的文件的名称.返回表示文件的StorageFile.
GetFilesAsync() 在当前文件夹中获取文件.返回文件夹中的文件列表(类型IReadOnlyList).列表中的每个文件均由一个StorageFile对象表示.
GetFolderAsync(string name) 从当前文件夹获取指定文件夹.name:要检索的文件夹的名称.返回表示子文件夹的StorageFolder.
RenameAsync(string desiredName) 重命名当前文件夹.desiredName:当前文件夹所需的新名称.
StorageFile类的主要成员
DateCreated 获取创建文件夹的日期和时间
Name 获取存储文件夹的名称
Path 获取存储文件夹的路径
CopyAndReplaceAsync(IStorageFile fileToReplace) 将指定文件替换为当前文件的副本.fileToReplace:要替换的文件
CopyAsync(IStorageFolder destinationFolder):在指定文件夹中创建文件的副本.destinationFolder:从中创建副本的目标文件夹.返回表示副本的StorageFile
CopyAsync(IStorageFolder destinationFolder,string desiredNewName) 使用所需的名称,在指定文件夹中创建文件的副本.destinationFolder:从中创建副本的目标文件夹.desiredNewName:副本的所需名称.如果在已经指定desiredNewName的目标文件夹中存在现有文件,则为副本生成唯一的名称.返回表示副本的StorageFile
DeleteAsync() 删除当前文件
GetFileFromPathAsync(string path) 获取StorageFIle对象以代表指定路径中的文件.path:表示获取StorageFile的文件路径.返回表示文件的StorageFile
RenameAsync(string desiredName) 重命名当前文件.desiredName:当前项所需的新名称.
1.创建文件夹和文件
对于根目录里的StorageFolder对象,可以直接通过ApplicationData类的单例来获取,在文件夹里面再创建文件夹,可以调用StorageFolder.CreateFolderAsync方法在本地文件夹中创建一个文件夹目录,以及调用StorageFolder.CreateFileAsync方法在本地文件夹中创建一个文件.

1
2
3
4
5
6
7
8
9
10
11
12
13
/// <summary>
/// 创建文件夹和文件
/// </summary>
private async void GetLocalFolder()
{
//获取本地文件夹根目录
StorageFolder local = Windows.Storage.ApplicationData.Current.LocalFolder;
//创建文件夹,如果文件夹存在则打开它
var dataFolder =await local.CreateFolderAsync("DataFolder",CreationCollisionOption.OpenIfExists);
//创建一个命名为DataFile.txt的文件,如果文件存在则替换掉
var file = await dataFolder.CreateFileAsync("DataFile.txt",CreationCollisionOption.ReplaceExisting);

}

2.文件的读写
可以使用StreamReader,StreamWriter类,FileIO类
使用S可以读取/写入标准文本文件的各行信息.StreamReader/StreamWriter的默认编码为UTF-8.UTF-8可以正确处理Unicode字符并提供一致的结果.FileIO类是专门为IStorageFile类型的对象表示读取/写入文件提供帮助的方法,FileIO类是一个静态类,直接调用其静态的文件读写方法来进行操作.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/// <summary>
/// 2.文件的读写
/// </summary>
private async void ReadWrite(StorageFile file) {
//读取文件的文本信息,使用FileIO类实现
string fileContent = await FileIO.ReadTextAsync(file);
//读取文件的文本信息,使用StreamReader类实现
using (StreamReader streamReader = new StreamReader(file.Path))
{
fileContent = streamReader.ReadToEnd();

}
//写入文件的文本信息,使用FileIO类实现
await FileIO.WriteTextAsync(file, "Windows 10");
//写入文件的文本信息,使用StreamWriter类实现.
using (StreamWriter swNew=new StreamWriter(file.Path))
{
swNew.WriteLine("Windows 10");
}

}

还可以使用WindowsRuntimeStorageExtensions类提供的实现IStorageFile和IStorageFolder接口类使用的方法来进行文件和文件夹的读写操作,这些扩展的方法分别有OpenStreamForReadAsync和OpenStreamForWriteAsync.

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
 //使用OpenStreamForWriteAsync方法来实现写入文件信息
byte[] fileBytes=Encoding.UTF8.GetBytes("Windows 10".ToCharArray());
using (var s=await file.OpenStreamForWriteAsync())
{
s.Write(fileBytes,0,fileBytes.Length);
}
///文件删除,复制,重命名和移动操作
//await file.DeleteAsync();
var newfile =await storageFolder.CreateFileAsync("sample - Copy.txt", CreationCollisionOption.ReplaceExisting);
//复制文件
StorageFile fileCopy = await file.CopyAsync(storageFolder,"sample - Copy.txt",NameCollisionOption.ReplaceExisting);
//重命名文件
await file.RenameAsync("sampleRe.txt");
var newStorageFolder = await storageFolder.CreateFolderAsync("newDataFolder", CreationCollisionOption.OpenIfExists);
//移动文件
await file.MoveAsync(newStorageFolder,"newDataFile");
//文件名
private string fileName = "testfile.txt";
//保存按钮事件处理程序
private async void bt_save_Click(object sender, RoutedEventArgs e)
{
if (infoStorage.Text!="")
{
//写入文件信息
await WriteFile(fileName,infoStorage.Text);
await new MessageDialog("保存成功").ShowAsync();
}
else
{
await new MessageDialog("内容不能为空").ShowAsync();
}
}
/// <summary>
/// 写入本地文件夹根目录的文件
/// </summary>
/// <param name="fileName"></param>
/// <param name="text"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
private async Task WriteFile(string fileName, string text)
{
//获取本地文件夹根目录文件夹
IStorageFolder applicationFolder=ApplicationData.Current.LocalFolder;
//在文件夹里面创建文件,如果文件存在则替换掉.
IStorageFile storageFile = await applicationFolder.CreateFileAsync(fileName,CreationCollisionOption.OpenIfExists);
//使用FileIO类把字符串信息写入文件
await FileIO.WriteTextAsync(storageFile, text);
}
/// <summary>
/// 删除文件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void bt_delete_Click(object sender, RoutedEventArgs e)
{
string text;
try
{
IStorageFolder applicationFolder = ApplicationData.Current.LocalFolder;
//获取文件
IStorageFile storageFile=await applicationFolder.GetFileAsync(fileName);
//删除当前的文件
await storageFile.DeleteAsync();
text = "删除成功";
}
catch (Exception ex)
{

text = "文件删除错误:" + ex.Message;
}
await new MessageDialog(text).ShowAsync();
}
/// <summary>
/// 读取文件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void bt_read_Click(object sender, RoutedEventArgs e)
{
//读取文件的文本信息
string content = await ReadFile(fileName);
await new MessageDialog(content).ShowAsync();
}
/// <summary>
/// 读取本地文件夹根目录的文件
/// </summary>
/// <param name="fileName"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
private async Task<string> ReadFile(string fileName)
{
string text;
try
{
//获取本地文件夹根目录文件夹
IStorageFolder applicaitonFolder=ApplicationData.Current.LocalFolder;
//根据文件名获取文件夹里面的文件
IStorageFile storageFile=await applicaitonFolder.GetFileAsync(fileName);
//打开文件获取文件的数据流
IRandomAccessStream accessStream=await storageFile.OpenReadAsync();
//使用StreamReader读取文件的内容,需要将IRandomAccessStream对象转换为Stream对象来初始化StreamReader对象.
using (StreamReader streamReader=new StreamReader(accessStream.AsStreamForRead((int)accessStream.Size)))
{
text = streamReader.ReadToEnd();
}
}
catch (Exception e)
{
text = "文件读取错误:" + e.Message;

}
return text;
}

6.23.文件Stream和Buffer读写操作
操作二进制文件,需要用到DataWriter类和DataReader类,DataWriter类用于写入文件的信息.当然这个信息不仅仅是文本信息
1.Buffer的写入操作
win10里,文件的Buffer操作使用的是IBuffer对象,所以要使用DataWriter类写入相关的信息之后再转换为IBuffer对象,然后保存到文件中.

1
2
3
4
5
6
7
8
9
using(InMemoryRandomAccessStream memoryStream=new InMemoryRandomAccessStream()){
using(DataWriter dataWriter=new DataWriter(memoryStream)){
//文件相关的信息,可以根据文件的规则来进行写入
dataWriter.WriterInt32(size);
dataWriter.WriteString(userContent);
buffer=dataWriter.DetachBuffer();
}
}
await FileIO.WriteBufferAsync(file,buffer);

2.Buffer的读取操作
读取的操作其实就是获取文件的IBuffer对象之后,再使用IBuffer对象初始化一个DataReader对象,就可以对文件进行读取操作了.

1
2
3
4
5
6
IBuffer buffer=await FileIO.ReadBufferAsync(file);
using(DataReader dataReader=DataReader.FromBuffer(buffer)){
//读取文件相关的信息,读取的规则要与文件的规则一致.
Int32 stringSize=dataReader.ReadInt32();
string fileContent=dataReader.ReadString((uint)stringSize);
}

3.Stream的写入操作
文件的Stream其实就是文件内的信息,所以在用Stream来写入文件的数据时,直接保存Stream的信息就可以,并不需要再调用文件的对象进行保存.

1
2
3
4
5
6
7
8
9
10
11
using(StorageStreamTransaction transaction=await file.OpenTransactedWriteAsync()){
using(DataWriter dataWriter=new DataWriter(transaction.Stream)){
//文件相关的信息,可以根据文件的规则进行写入
dataWriter.WriteInt32(size);
dataWriter.WriteString(userContent);
... ...
transaction.Stream.Size=await dataWriter.StoreAsync();
//保存Stream数据
await transaction.CommitAsync();
}
}

4.Stream的读取操作
使用Stream读取文件的内容,需要先调用DataReader类的LoadAsync方法,把数据加载进来,再调用相关的Read方法来读取文件的内容;Buffer的操作不用调用LoadAsync方法,那是因为其已经一次性把数据都读取出来了.

1
2
3
4
5
6
7
8
using(IRandomAccessStream readStream=await file.OpenAsync(FileAccessMode.Read)){
//读取文件相关的信息,读取的规则要与文件的规则一致
await dataReader.LoadAsync(sizeof(Int32));
Int32 stringSize=dataReader.ReadInt32();
await dataReader.LoadAsync((UInt32)stringSize);
string fileContent=dataReader.ReadString((uint)stringSize);
... ...
}

文件Stream和Buffer读写操作的示例,演示了文件格式是先写入字符串内容的长度,Int32类型占用4个字节,然后再写入字符串的内容;读取文件的时候再按照这样的方式逆着来.按文件格式读取正确数据.

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
<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>

/// <summary>
/// 创建的测试文件对象
/// </summary>
private StorageFile sampleFile;
/// <summary>
/// 文件名
/// </summary>
private string filename = "sampleFile.dat";
/// <summary>
/// 创建一个文件事件处理程序
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void bt_crreate_Click(object sender, RoutedEventArgs e)
{
StorageFolder storageFolder= ApplicationData.Current.LocalFolder;
sampleFile=await storageFolder.CreateFileAsync(filename,CreationCollisionOption.ReplaceExisting);
StreambufferTextBlock.Text = $"文件{sampleFile.Name}已经创建好.";

}
/// <summary>
/// 写入IBuffer按钮事件处理程序
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void bt_writebuffer_Click(object sender, RoutedEventArgs e)
{
StorageFile file = sampleFile;
if (file!=null)
{
try
{
string userContent = "测试的文本消息";
IBuffer buffer;
//使用一个内存的可访问的数据流创建一个DataWriter对象,写入String 再转换为IBuffer对象.
using (InMemoryRandomAccessStream memoryStream=new InMemoryRandomAccessStream())
{
//把String信息转换为IBuffer对象.
using (DataWriter dataWriter=new DataWriter(memoryStream))
{
//先写入字符串的长度信息
dataWriter.WriteInt32(Encoding.UTF8.GetByteCount(userContent));
//写入字符串信息
dataWriter.WriteString(userContent);
buffer=dataWriter.DetachBuffer();
}
}
await FileIO.WriteBufferAsync(file,buffer);
StreambufferTextBlock.Text = $"长度为:{buffer.Length} bytes的文本信息写入到了文件{file.Name}:{Environment.NewLine+Environment.NewLine+userContent}";
}
catch (Exception ex)
{
StreambufferTextBlock.Text =$"异常:{ex.Message}";
}
}
else
{
StreambufferTextBlock.Text = "请先创建文件";
}
}


/// <summary>
/// 读取IBuffer按钮事件处理程序
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void bt_readbuffer_Click(object sender, RoutedEventArgs e)
{
StorageFile file= sampleFile;
if (file != null)
{
try
{
IBuffer buffer = await FileIO.ReadBufferAsync(file);
using (DataReader dataReader = DataReader.FromBuffer(buffer))
{
//先读取字符串的长度信息
Int32 stringSize = dataReader.ReadInt32(); //读取字符串信息
string fileContent = dataReader.ReadString((uint)stringSize);
StreambufferTextBlock.Text = $"长度为:{buffer.Length} bytes的文本信息从文件{file.Name}读取出来,其中字符串的长度为{stringSize}bytes:{Environment.NewLine + fileContent}";
}
}
catch (Exception ex)
{

StreambufferTextBlock.Text = "异常:" + ex.Message;
}
}
else {
StreambufferTextBlock.Text = "请先创建文件";
}
}
//写入Stream按钮事件处理程序.
private async void bt_writestream_Click(object sender, RoutedEventArgs e)
{
StorageFile file= sampleFile;
if (file!=null)
{
try
{
string userContent = "测试的文本信息";
//使用StorageStreamTransaction对象来创建DataWriter对象写入数据
using (StorageStreamTransaction transaction=await file.OpenTransactedWriteAsync())
{
using (DataWriter dataWriter=new DataWriter(transaction.Stream))
{
//先写入信息的长度
dataWriter.WriteInt32(Encoding.UTF8.GetByteCount(userContent));
//写入字符串信息
dataWriter.WriteString(userContent);
//提交dataWriter的数据同时重设Stream的大小
transaction.Stream.Size=await dataWriter.StoreAsync();
//保存Stream数据
await transaction.CommitAsync();
StreambufferTextBlock.Text = $"使用stream把信息写入了文件{file.Name}:{Environment.NewLine+userContent}";
}
}
}
catch (Exception ex)
{

StreambufferTextBlock.Text = $"异常:" + ex.Message;
}
}
else
{
StreambufferTextBlock.Text = "请先创建文件";
}
}
/// <summary>
/// 读取Stream按钮事件处理程序
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void bt_readstream_Click(object sender, RoutedEventArgs e)
{
StorageFile file=sampleFile;
if (file!=null)
{
try
{
//使用文件流创建DataReader对象
using (IRandomAccessStream readStream=await file.OpenAsync(FileAccessMode.Read))
{
using (DataReader dataReader=new DataReader(readStream))
{
UInt64 size = readStream.Size;
if (size<=UInt32.MaxValue)
{
//先读取字符串的长度信息
await dataReader.LoadAsync(sizeof(Int32));
Int32 stringSize=dataReader.ReadInt32();
//读取字符串的内容信息
await dataReader.LoadAsync((UInt32)stringSize);
string fileContent = dataReader.ReadString((UInt32)stringSize);
StreambufferTextBlock.Text =$"使用stream把信息从文件{file.Name}读取出来,其中字符串的长度为:{stringSize}bytes:{Environment.NewLine+fileContent}";
}
else
{
StreambufferTextBlock.Text = $"文件{file.Name}太大,不能再单个数据块中读取";
}
}
}
}
catch (Exception ex)
{

StreambufferTextBlock.Text = $"异常:{ex.Message}";
}

}
else
{
StreambufferTextBlock.Text = "请先创建文件";
}
}

6.24.应用文件的URI方案
3个根目录文件夹所对应的保存路径的格式分别如下:
1.LocalFolder文件夹保存路径格式:%USERPROFILE%\APPDATA\Local\Packages{PackageId}\LocalState.
2.RoamingFolder文件夹的保存路径格式:%USERPROFILE%\APPDATA\Local\Packages{PackageId}\RoamingState
3.TemporaryFolder文件夹的保存路径格式:%USERPROFILE%\APPDATA\Local\Packages{PackageId}\TempState
获取到的路径并不能作为访问文件的路径来使用,访问文件的路径需要使用本地文件夹的ms-appdata的URI方案.LocalFolder文件夹对应的是“ms-appdata:///local/”,RoamingFolder “ms-appdata:///roaming/“
TemporaryFolder ms-appdata:///temp/ 可以根据StorageFile类的静态方法GetFileFromApplicationUriAsync来根据URI读取文件.下面示例使用了ms-appdata的URI方法来获取在LocalFolder文件夹里面的AppConfigSettings.xml文件.

1
var file=await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appdata:///local/AppConfigSettings.xml"));

在这种通过URI访问文件的方案里还需要注意,新文件和文件夹的路径在URI方案名称的最后一个斜杠后面不能超过185个字符.

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
<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>
//文件名
private string UrifileName = "testfile.txt";



/// <summary>
/// 读取文件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void bt_read_Click_1(object sender, RoutedEventArgs e)
{
//读取文件的文本信息
string text;
try
{
//通过URI获取本地文件
var storageFile = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appdata:///local/"+UrifileName));
//打开文件获取文件的数据流
IRandomAccessStream accessStream = await storageFile.OpenReadAsync();
//使用StreamReader读取文件的内容,需要将IRandomAccessStream对象转换为Stream对象来初始化StreamReader对象.
using (StreamReader streamReader=new StreamReader(accessStream.AsStreamForRead((int)accessStream.Size)))
{
text = streamReader.ReadToEnd();
}
}
catch (Exception ex)
{
text = $"文件读取错误:"+ex.Message;

}
await new MessageDialog(text).ShowAsync();
}
//创建文件
private async void bt_save_Click_1(object sender, RoutedEventArgs e)
{
if (UriInfo.Text != "") {
//获取本地文件夹根目录文件夹
IStorageFolder applicationFolder=ApplicationData.Current.LocalFolder;
//在文件夹里面创建文件,如果文件存在则替换掉
IStorageFile storageFile = await applicationFolder.CreateFileAsync(UrifileName,CreationCollisionOption.OpenIfExists);
//使用FileIO类把字符串信息写入文件
await FileIO.WriteTextAsync(storageFile, UriInfo.Text);
await new MessageDialog($"保存成功,文件的路径:{storageFile.Path}").ShowAsync();
}
else
{
await new MessageDialog("内容不能为空").ShowAsync();

}
}

6.3.常用的存储数据格式
6.31.JSON数据序列化存储
JSON用于描述数据结构,有以下形式存在.
对象(object):一个对象以”{“开始”,”并以”}”退出.一个对象包含一系列非排序的名称/值对,每个名称/值对之间使用”,”分隔.
名称/值(collection):名称和值之间使用”:”隔开,一般的形式是{name;value}.一个名称是一个字符串,一个值可以是一个字符串,一个数值,一个对象,一个布尔值,一个有串行表或者一个null值.值的串行表(Array):一个或者多个值用”,”分区后,使用”[“,”]”括起来就形成了这样的列表.例如,[collection,collection]
在win10里如果要使用JSON数据格式来存储相关的信息会有两种编程方式;
1.使用DataContractJsonSerializer类对JSON数据进行序列化和反序列化
DataContractJsonSerializer类对Json数据进行序列化和反序列化,是最简洁的Json数据操作方式.序列化的过程是把实体类对象转换为Json字符串对象

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
/// <summary>
/// 对实体类进行序列化的方法
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
public string ToJsonData(object item) {
DataContractJsonSerializer serializer=new DataContractJsonSerializer(item.GetType());
string result = string.Empty;
using (MemoryStream ms=new MemoryStream())
{
serializer.WriteObject(ms,item);
ms.Position = 0;
using (StreamReader reader=new StreamReader(ms))
{
result = reader.ReadToEnd();
}
}
return result;
}
/// <summary>
/// 把Json字符串反序列化成实体类对象
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="jsonString"></param>
/// <returns></returns>
public T DataContractJsonDeSerializer<T>(string jsonString) {
var ds=new DataContractJsonSerializer(typeof(T));
var ms=new MemoryStream(Encoding.UTF8.GetBytes(jsonString));
T obj=(T)ds.ReadObject(ms);
ms.Dispose();
return obj;
}

2.使用JsonObject对象来自定义Json对象
使用DataContractJsonSerializer类对json数据进行序列化和反序列化的操作很方便,但是却有一个弊端,它的灵活性很差.例如,序列化成的Json字符串的名称/值对的名称必须和类的属性完全一致.若要实现灵活复杂的Json数据进行序列化和反序列化的操作可以使用JsonObject类来进行自定义.
通过JsonObject类来创建一个如下的Json对象.
{“city”:”Beijing”,”street”:”Chaoyang Road”,”postcode”:100025}
创建其对应的JsonObject类实现的语法如下:

1
2
3
4
JsonObject jsonObject=new JsonObject();
JsonObject.SetNamedValue("city",JsonValue.CreateStringValue("Beijing"));
JsonObject.SetNamedValue("street",JsonValue.CreateStringValue("Chaoyang Road"));
jsonObject.setNamedValue("postcode",JsonValue,CreateNumberValue(10025));

获取JsonObject类对象”city”所对应的数值如下:
jsonObject.GetNamedString(“city”,”默认值”);

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
<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>
/// <summary>
/// 对实体类进行序列化的方法
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
public string ToJsonData(object item) {
DataContractJsonSerializer serializer=new DataContractJsonSerializer(item.GetType());
string result = string.Empty;
using (MemoryStream ms=new MemoryStream())
{
serializer.WriteObject(ms,item);
ms.Position = 0;
using (StreamReader reader=new StreamReader(ms))
{
result = reader.ReadToEnd();
}
}
return result;
}
/// <summary>
/// 把Json字符串反序列化成实体类对象
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="jsonString"></param>
/// <returns></returns>
public T DataContractJsonDeSerializer<T>(string jsonString) {
var ds=new DataContractJsonSerializer(typeof(T));
var ms=new MemoryStream(Encoding.UTF8.GetBytes(jsonString));
T obj=(T)ds.ReadObject(ms);
ms.Dispose();
return obj;
}

private const string UserDataKey = "UserDataKey";
private async void save_Click(object sender, RoutedEventArgs e)
{
if (userName.Text=="" || userAge.Text=="")
{
await new MessageDialog("请输入完整的信息").ShowAsync();
return;
}
ObservableCollection<School> education = new ObservableCollection<School>();
if (school1.IsChecked == true) {
education.Add(new School { Id="id001",Name=school1.Content.ToString()});


}
if (school2.IsChecked == true)
{
education.Add(new School { Id = "id002", Name = school2.Content.ToString() });
}

User user = new User { Education=education,Id=Guid.NewGuid().ToString(),Name=userName.Text,Age=Int32.Parse(userAge.Text),Verified=false};
//使用DataContractJsonDeSerializer的实现方式
//string json=ToJsonData(user);
//使用JsonObject的实现方式
string json = user.Stringify();
JsonObjectInfo.Text = json;
_appSettings.Values[UserDataKey] = json;
await new MessageDialog("保存成功").ShowAsync();
}
// 把应用设置存储的Json数据转换为实体类对象.
private async void get_Click(object sender, RoutedEventArgs e)
{
if (!_appSettings.Values.ContainsKey(UserDataKey)) {
await new MessageDialog("未保存信息").ShowAsync();
return;
}
string json = _appSettings.Values[UserDataKey].ToString();
//使用DataContractJsonDeSerializer的实现方式
//User user = DataContractJsonDeSerializer<User>(json);
//使用JsonObject的实现方式
User user = new User(json);
string userInfo = "";
userInfo = $"Id:{user.Id} Name:{user.Name} Age:{user.Age}";
foreach (var item in user.Education)
{
userInfo += $"Education:Id:{item.Id} Name:{item.Name}";
}
await new MessageDialog(userInfo).ShowAsync();
}

6.32.XML文件存储
Xml是可扩展标记语言的缩写,用于创建内容并使用限定标记,从而使每个单词,短语或块称为可识别,可分类的信息.Xml是一种易于使用和扩展的标记语言,它比JSON使用得更加广泛,它也是一种简单的数据格式,是纯100%的ASCII文本,而ASCII的抗破坏能力是很强的 下面是一个非常简单的XML文档的格式

1
2
3
4
5
<?xml version="1.0" enconding="UTF-8">
<test>
Hello Windows 10
</test>

简写:
1.使用DataContractSerializer类对Xml文件进行序列化和反序列化
DataContractSerializer类和DataContractJsonSerializer类是两个很相似的类,前者是针对Xml格式的数据,后者是针对Json的数据.
Person对象Name=”terry”,Age=41,通常上面的序列化方法存储到应用文件里.

1
2
3
<Person xmlns:i="http://www.w3.org/2001/XMLSchema - instance" xmlns="http://schemas.datacontract.org/2004/07/DataContractSerializerDeno"><Age>41</Age>
<Name>terry</Name>
</Person>

2.使用XmlDocument类对Xml文件进行序列化和反序列化

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
/// <summary>
/// 把实体类对象序列化成xml格式存储到文件里面
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="data"></param>
/// <param name="fileName"></param>
/// <returns></returns>
public async Task SaveAsync<T>(T data, string fileName) {
StorageFile file = await ApplicationData.Current.LocalFolder.CreateFileAsync(fileName,CreationCollisionOption.ReplaceExisting);
//获取文件的数据流来进行操作.
using (IRandomAccessStream raStream=await file.OpenAsync(FileAccessMode.ReadWrite))
{
using (IOutputStream outputStream=raStream.GetOutputStreamAt(0))
{
//创建序列化对象写入数据
DataContractSerializer serializer = new DataContractSerializer(typeof(T));
serializer.WriteObject(outputStream.AsStreamForWrite(),data);
await outputStream.FlushAsync();
}
}
}
/// <summary>
/// 反序列化XML文件
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="filename"></param>
/// <returns></returns>
public async Task<T> ReadAsync<T>(string filename) {
//获取实体类类型实例化一个对象
T sessionState_ = default(T);
StorageFile file = await ApplicationData.Current.LocalFolder.GetFileAsync(filename);
if (file == null) return sessionState_;
using (IInputStream inStream=await file.OpenSequentialReadAsync())
{
//反序列化XML数据
DataContractSerializer serializer = new DataContractSerializer(typeof(T));
sessionState_ = (T)serializer.ReadObject(inStream.AsStreamForRead());
}
return sessionState_;
}
/// <summary>
///
/// </summary>
public void SaveAsXml() {
//创建一个XmlDocument对象代表着是一个Xml的文档对象.
XmlDocument dom = new XmlDocument();
//创建和添加一条XML的评论
XmlComment dec = dom.CreateComment("THis is Students Message.");
dom.AppendChild(dec);

//添加一个students的元素为Xml文档的根元素
XmlElement x = dom.CreateElement("studetns");
dom.AppendChild(x);
//创建一个Student元素后面再添加到students元素的节点上
XmlElement x1 = dom.CreateElement("Student");
//在Student元素里再添加id和name两个元素节点,在节点内还添加了文本内容
XmlElement x11 = dom.CreateElement("id");
x11.InnerText = "id001";
x1.AppendChild(x11);
XmlElement x12 = dom.CreateElement("name");
x12.InnerText = "张三";
x1.AppendChild(x12);
x.AppendChild(x1);

}

XML文档操作相关的类和说明
Document XmlDocument类 树中所有节点的容器,也称作文档根,文档根并非总是与根元素相同
DocumentFragment XmlDocumentFragment类 包含一个或多个不带任何树结构的节点的临时段
DocumentType XmlDocumentType类 <! DOCTYPE…>节点
EntityReference XmlEntityReference类 非扩展的实体引用文本
Element XmlElement类 元素节点
Attr XmlAttribute类 元素的属性
ProcessingInstruction XmlProcessingInstruction类 处理指令节点
Comment XmlComment类 注释节点
Text XmlText类 属于某个元素或属性的文本
CDATASection XmlCDataSection类 CDATA
Entity XmlEntity类 XML文档(来自内部文档类型定义(DTD)子集或来自外部DTD和参数实体)中的<!ENTITY…>声明
Notation XmlNotation类 DTD中声明的表示法

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
 <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>
</Pivot>
<AppBarButton Label="新增" Icon="Add" Click="AppBarButton_Click_1"/>
//加载页面触发Loaded事件
Loaded += BlankPage_Loaded;
private async void BlankPage_Loaded(object sender, RoutedEventArgs e)
{
Files.Items.Clear();
//获取应用程序的本地存储文件
StorageFolder storage = await ApplicationData.Current.LocalFolder.CreateFolderAsync("ShoppingList",CreationCollisionOption.OpenIfExists);
var files = await storage.GetFilesAsync();
{
//获取购物清单文件夹里面存储的文件
foreach (StorageFile file in files) {
//动态构建一个Grid
Grid a = new Grid();
//定义第一列
ColumnDefinition col = new ColumnDefinition();
col.Width = GridLength.Auto;
a.ColumnDefinitions.Add(col);

//定义第二列
ColumnDefinition col2 = new ColumnDefinition();
col2.Width = GridLength.Auto;
a.ColumnDefinitions.Add(col2);
//添加一个TextBlock现实文件名到第一列
TextBlock txbx = new TextBlock();
txbx.Text = file.DisplayName;
Grid.SetColumn(txbx,0);
//添加一个HyperlinkButton链接到购物详细清单页面,这是第二列
HyperlinkButton btn = new HyperlinkButton();
btn.Content = "查看详细";
btn.Name = file.DisplayName;
btn.Click += (s,ea)=> {
Frame.Navigate(typeof(DisplayPage), file);
};
Grid.SetColumn(btn,1);
a.Children.Add(txbx);
a.Children.Add(btn);
Files.Items.Add(a);
}
}
}
/// <summary>
///
/// </summary>
public void SaveAsXml() {
//创建一个XmlDocument对象代表着是一个Xml的文档对象.
XmlDocument dom = new XmlDocument();
//创建和添加一条XML的评论
XmlComment dec = dom.CreateComment("THis is Students Message.");
dom.AppendChild(dec);

//添加一个students的元素为Xml文档的根元素
XmlElement x = dom.CreateElement("studetns");
dom.AppendChild(x);
//创建一个Student元素后面再添加到students元素的节点上
XmlElement x1 = dom.CreateElement("Student");
//在Student元素里再添加id和name两个元素节点,在节点内还添加了文本内容
XmlElement x11 = dom.CreateElement("id");
x11.InnerText = "id001";
x1.AppendChild(x11);
XmlElement x12 = dom.CreateElement("name");
x12.InnerText = "张三";
x1.AppendChild(x12);
x.AppendChild(x1);

}

private void AppBarButton_Click_1(object sender, RoutedEventArgs e)
{
Frame.Navigate(typeof(AddItem));
}
AddItem.xaml .cs
<Page
x:Class="App1.AddItem"
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"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

<Grid>
<Grid x:Name="ContentPanel" Margin="12,0,12,0">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100*"/>
<ColumnDefinition Width="346*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Text="名称:"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBox Name="nameTxt" Grid.Column="1" Grid.Row="0" MaxHeight="40"/>
<TextBlock Grid.Column="0" Grid.Row="1" Text="价格:" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBox x:Name="priceTxt" Grid.Column="1" Grid.Row="1" MaxHeight="40"/>
<TextBlock Grid.Row="2" Grid.Column="0" Text="数量:"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBox Name="quanTxt" Grid.Column="1" Grid.Row="2" MaxHeight="40"/>
<Button x:Name="BtnSave" Content="保存" HorizontalAlignment="Stretch" Grid.Row="3" Grid.ColumnSpan="2" VerticalAlignment="Top" Click="BtnSave_Click"/>

</Grid>
</Grid>
</Page>
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Data.Xml.Dom;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.Storage;
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=234238 上介绍了“空白页”项模板

namespace App1
{
/// <summary>
/// 可用于自身或导航至 Frame 内部的空白页。
/// </summary>
public sealed partial class AddItem : Page
{
public AddItem()
{
this.InitializeComponent();
}

private async void BtnSave_Click(object sender, RoutedEventArgs e)
{
//获取购物清单的文件夹对象
StorageFolder storage = await ApplicationData.Current.LocalFolder.GetFolderAsync("ShoppingList");
//创建一个Xml的对象,示例格式如<Apple pric="23" quantity="3"/>
XmlDocument _doc= new XmlDocument();
//使用商品的名称来创建一个xml元素作为根节点
XmlElement _item=_doc.CreateElement(nameTxt.Text);
//使用属性来作为信息的标识符,用属性的值来存储相关的信息
_item.SetAttribute("price", priceTxt.Text);
_item.SetAttribute("quantity",quanTxt.Text);
_doc.AppendChild(_item);
//创建一个应用文件
StorageFile file = await storage.CreateFileAsync(nameTxt.Text+".xml",CreationCollisionOption.ReplaceExisting);
//把XML的信息保存到文件中去
await _doc.SaveToFileAsync(file);
//调回清单主页
Frame.GoBack();
}
}
}
DisplayPage.xaml .cs
<Page
x:Class="App1.DisplayPage"
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"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

<Grid x:Name="ContentPanel" Margin="12,0,12,0">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>

<TextBlock Grid.Column="0" Grid.Row="0" Text="名称:"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBlock x:Name="nameTxt" Grid.Column="1" Grid.Row="0" FontSize="30"/>

<TextBlock Grid.Row="1" Text="价格:" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBlock x:Name="priceTxt" Grid.Column="1" Grid.Row="1" FontSize="30"/>

<TextBlock Grid.Column="0" Grid.Row="2" Text="数量:"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBlock Name="quanTxt" Grid.Column="1" Grid.Row="2" FontSize="30"/>
<TextBlock Name="PageTitle" Grid.Row="3"/>
</Grid>

</Page>
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Data.Xml.Dom;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.Storage;
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=234238 上介绍了“空白页”项模板

namespace App1
{
/// <summary>
/// 可用于自身或导航至 Frame 内部的空白页。
/// </summary>
public sealed partial class DisplayPage : Page
{
public DisplayPage()
{
this.InitializeComponent();
}

protected override async void OnNavigatedTo(NavigationEventArgs e)
{
//获取上一个清单列表传递过来的参数,该参数是一个StorageFile对象.
StorageFile file=e.Parameter as StorageFile;
if (file == null) return;
//获取文件的名字
string itemName = file.DisplayName;
PageTitle.Text = itemName;
//把应用文件作为一个xml文档加载进来
XmlDocument doc=await XmlDocument.LoadFromFileAsync(file);
//获取xml文档的信息
priceTxt.Text = doc.DocumentElement.Attributes.GetNamedItem("price").NodeValue.ToString();
quanTxt.Text = doc.DocumentElement.Attributes.GetNamedItem("quantity").NodeValue.ToString();
nameTxt.Text = itemName;

}
}
}

6.4.安装包文件数据
可以在应用程序里获取安装包下的文件,就是在应用程序项目中添加的文件.编译文件源代码和资源文件是获取不到的.安装包文件数据和应用文件应用设置所存储的位置是不一样的,如果是保存应用程序业务的相关信息不建议保存到安装包的目录下,安装包文件数据通常是用于内置一些固定的文件数据.
6.41.安装包文件访问
获取安装包的文件可以先通过Windows.ApplicationModel.Package类的InstalledLocation属性来获取安装包的文件夹.
StorageFolder localFolder=Windows.ApplicationModel.Package.Current.InstalledLocation;

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
 <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>
/// <summary>
/// 打开应用程序的根目录
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void btnGetFile_Click(object sender, RoutedEventArgs e)
{
//清理文件夹列表
lbFolder.Items.Clear();
//获取根目录
StorageFolder localFolder =Windows.ApplicationModel.Package. Current.InstalledLocation;
//添加遍历根目录的文件夹到文件夹列表
foreach (var folder in await localFolder.GetFoldersAsync())
{
ListBoxItem item = new ListBoxItem();
item.Content = "应用程序目录:" + folder.Name;
item.DataContext = folder;
lbFolder.Items.Add(item);
}
//清理文件列表
lbFile.Items.Clear();
//添加遍历根目录的文件到文件列表
foreach (var file in await localFolder.GetFilesAsync())
{
ListBoxItem item3 = new ListBoxItem();
item3.Content = "文件:" + file.Name;
item3.DataContext = file;
lbFile.Items.Add(item3);
}
}
/// <summary>
/// 打开选中的文件夹
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void open_Click(object sender, RoutedEventArgs e)
{
if (lbFolder.SelectedIndex == -1) {
await new MessageDialog("请选择一个文件夹").ShowAsync();

}
else
{
ListBoxItem item = lbFolder.SelectedItem as ListBoxItem;
//获取选中的文件夹
StorageFolder folder = item.DataContext as StorageFolder;
//清理文件夹列表
lbFolder.Items.Clear();
//添加遍历到的文件夹到文件夹列表
foreach (var folder2 in await folder.GetFoldersAsync())
{
ListBoxItem item2 = new ListBoxItem();
item2.Content = "文件夹:" + folder2.Name;
item2.DataContext = folder;
lbFolder.Items.Add(item2);
}
//清理文件列表
lbFile.Items.Clear();
//添加遍历到的文件到文件列表
foreach (var file in await folder.GetFilesAsync())
{
ListBoxItem item3 = new ListBoxItem();
item3.Content = "文件:" + file.Name;
item3.DataContext = file;
lbFile.Items.Add(item3);
}

}
}
/// <summary>
/// 在选中的文件中新建一个文件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void create_Click(object sender, RoutedEventArgs e)
{
if (lbFolder.SelectedIndex == -1)
{
await new MessageDialog("请选择一个文件夹").ShowAsync();

}
else {
ListBoxItem item = lbFolder.SelectedItem as ListBoxItem;
//获取选中的文件夹
StorageFolder folder=item.DataContext as StorageFolder;
//在文件夹中创建一个文件
StorageFile file = await folder.CreateFileAsync(DateTime.Now.Millisecond.ToString()+".txt");
//添加到文件列表中
ListBoxItem item3 = new ListBoxItem();
item3.Content = "文件:" + file.Name;
item3.DataContext= file;
lbFile.Items.Add(item3);
await new MessageDialog("创建文件成功").ShowAsync();
}
}

/// <summary>
/// 删除选中的文件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void delete_Click(object sender, RoutedEventArgs e)
{
if (lbFile.SelectedIndex == -1) {
await new MessageDialog("请选择一个文件夹").ShowAsync();
}
else
{
ListBoxItem item=lbFile.SelectedItem as ListBoxItem;
//获取选中的文件
StorageFile file=item.DataContext as StorageFile;
//删除文件
await file.DeleteAsync();
lbFile.Items.Remove(item);
await new MessageDialog("删除成功").ShowAsync();
}
}

6.42 安装包文件的URI方案
应用文件可以通过URI来访问,安装包的文件也一样可以通过URI来访问,不过两者之间的URI方案是有区别的,应用文件使用的是以字符串”ms-appdata:///“开头的URI地址,而安装包使用的是”ms-appx:///“.例如获取安装包PackageTest文件夹下的test.xml:

1
var file=await Windows.Storage.StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///PackageTest/test.xml"));

如果是图片文件也可以直接在XAML上给Image控件的Source属性赋值
1.访问存储在本地文件夹中的文件:
2.访问存储在漫游文件夹中的文件:
3.访问存储在临时文件夹中的文件:
4.访问安装包文件夹中的文件:或者
下面给出访问应用文件夹和安装包文件夹图片文件的示例:打开示例程序会先将安装包的一个程序图标文件复制到应用文件夹里,然后通过两种不同的URI方案来给Image控件添加图片资源.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<PivotItem Header="AppUri">
<StackPanel>
<TextBlock Text="安装包的图片展示:"/>
<Image x:Name="packageImage" Height="200"/>
<TextBlock Text="应用存储的图片展示:"/>
<Image x:Name="appImage" Height="200"/>
</StackPanel>
</PivotItem>
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
ApplicationData appData = ApplicationData.Current;
//将程序包内的文件保存到应用存储中的TemporaryFolder
StorageFile imgFile = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Assets/LockScreenLogo.scale-200.png"));
await imgFile.CopyAsync(appData.TemporaryFolder,imgFile.Name,NameCollisionOption.ReplaceExisting);
//引用应用存储内的图片文件并显示
appImage.Source = new BitmapImage(new Uri("ms-appdata:///temp/LockScreenLogo.scale-200.png"));
//引用程序包内的图片文件并显示
packageImage.Source = new BitmapImage(new Uri("ms-appx:///Assets/LockScreenLogo.scale-200.png"));

}

图形绘图

7.11.图形中常用的结构.
在图形中经常用到Point,Size,Rect和Color这些数据结构,常用于对图形形状的相关属性赋值
1.Point结构.
Windows.Foundation.Point定义点的位置.
public Point(double x,double y)
2.Size结构
public Size(double width,double height)
3.Rect结构
矩形Rect Windows.Foundation.Rect

1.属性X,Y:矩形结构左上角的x,y坐标.
2.只读属性Left,Top:矩形结构左上角的x,y坐标.
3.只读属性Right和Bottom:矩形结构对象右下角的x,y坐标
4.属性Width,Height,Size矩形结构对象的宽度和高度.
5.构造函数Rect(Point location,Size size) 参数1代表矩形结构左上角点结构,参数2代表矩形宽和高的Size结构.
6.构造函数Rect(Double x,Double y,Double width,Double height):参数依次为矩形左上角x坐标,y坐标,宽和高.
7.方法public void Intersect(Rect rect) 得到调用该方法的矩形结构对象和参数表示的矩形结构的交集

4.Color结构
Windows.UI.Color. a,b,r,g 透明度,蓝色,绿色,红色合成.Color结构支持两种色彩空间sRGB和scRGB.
sRGB用无符号32位数代表一种颜色,红色,绿色,蓝色以及透明度各占一个字节,透明度等于0为完全透明,255为完全不透明.例如完全不透明红色用16进制数表示为#ffff0000,scRGB代表的颜色中的红色,绿色,蓝色以及透明度分别用0-1之间的Sing类型数表示,透明度等于0.0为完全透明,1.0为完全不透明.红色,绿色,蓝色全为0.0 表示黑色,全为1.0表示白色.例如不透明红色表示为sc#1.0,1.0,0.0,0.0,其常用属性和方法如下:
public static Color FromArgb(byte a,byte r,byte g,byte b):sRGB颜色.
7.12.画图相关的类
有两组类可用于定义空间的区域:Shape和Geometry.主要区别是:Shape拥有与之关联的画笔并可以呈现到屏幕,但Geometry只定义空间的区域并且不会呈现.可以认为Shape是由Geometry定义的有边界的UIElement,
Shape类 有 画线段类Line,Rectangle类,Ellipse,多条线段类Polyline,画由多条线段组成的闭合图形类Polygon,Path.Windows.UI.Xaml.Shapes.Shape
1.Fill 填充的画刷 Winodws.UI.Xaml.Media.Brush类型
2.Stroke 表示笔触 图形的边界颜色 Windows.UI.Xaml.Media.Brush类型
3.StrokeThickness 笔触尺寸.可以理解为图形边界大小.
4.Stretch None Fill Uniform UniformToFill
5.StrokeDashArray 表示虚线和间隙值的集合,用于画虚线,StrokeDashArray参数采用S[,G][,S*,G**]*的形式,其中S表示笔画的长度值,G表示间隙的长度值,如果忽略了G,则间隙长度与前一个笔画长度相同,例如线宽=1,”2”表示2个像素的实线和2个像素的空白组成的虚线,”3,2”表示3个像素实线和2个像素空白组成的虚线,
“2,2,3,2”表示2个像素的实线和2个像素的空白+3个像素的实线和2个像素的空白(如此反复)组成的虚线.
实际实线和空白间隔像素还受线宽的影响.
6.StrokeDashCap:表示虚线两端(线帽)的类型,用于画虚线(Windows.UI.Xaml.Media.PenLineCap枚举类型)
PenLineCap是描述直线或线段末端的形状,枚举值Flat表示一个未超出直线上最后一点的线帽,等同于无线帽,
Square表示一个高度等于直线粗细,长度等于直线粗细一半的矩形,Round表示一个直径等于直线粗细的半圆形;Triangle表示一个底边长度等于直线粗细的等腰直角三角形.
7.StrokeStartLineCap:虚线起始端(线帽)的类型(Windows.UI.Xaml.Media.PenLineCap枚举类型)
8.StrokeEndLineCap:虚线终结端(线帽)的类型
9.StrokeDashOffset:虚线的起始位置,用于画虚线,从虚线的起始端的StrokeDashOffset距离处开始描绘虚线.
10.StrokeLineJoin:图形连接点处的连接类型(Windows.UI.Xaml.Media.PenLineJoin枚举类型)
PenLineJoin是描述连接两条线或线段的形状的枚举类型—Miter表示线条连接使用常规角顶点;Bevel表示线条连接使用斜角顶点.Round表示线条连接使用圆角顶点.
11.StrokeMiterLimit:斜接长度与StrokeThickness/2的比值.默认值为10,最小值为1.
7.13.基础的图形形状
1.Line线段
控件Line来画线段,X1 Y1 起点 X2 Y2 终点

1
<Line X1="0" Y1="0" X2="100" Y2="100" Stroke="Black" StrokeThickness="10"/>

2.Rectangle矩形
控件Rectangle可用来画各种矩形,属性Width,Height,RadiusX和RadiusY分别是矩形的宽,高,圆角矩形的圆角x轴半径和y轴半径.x轴半径要小于等于Width二分之一,y轴半径要小于等于height二分之一,当二者都等于二分之一,则圆形变为圆或椭圆.

3.Ellipse椭圆
控件Ellipse画椭圆时,如果Width=height,则为圆.

4.Polyline开放多边形和Polygon封闭多边形
Polyline类属性Ponints是点结构数组,将数组元素Ponints[0]和Ponints[1],Ponints[1]和Ponints[2],等点连接为多条线段 设置类Polyline属性IsClose=true 也能完成Polygon类相同功能.

1
2
<Polyline Points="10,110 60,10 110,110" Stroke="Red" StrokeThickness="4"/>
<Polygon Points="10,110 60,10,110,110" Stroke="Red" StrokeThickness="4"/>

实现更高的灵活化,采用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
<PivotItem Header="Line">
<Canvas x:Name="ContentPanelCanvas" Background="Transparent">

</Canvas>
</PivotItem>

//注册画图的指针移动事件,用来捕获手指在设备屏幕上的点
this.ContentPanelCanvas.PointerMoved += ContentPanelCanvas_PointerMoved;
//注册指针按下事件,用来记录第一个触摸点
this.ContentPanelCanvas.PointerPressed += ContentPanelCanvas_PointerPressed;
/// <summary>
/// 指针按下事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <exception cref="NotImplementedException"></exception>
private void ContentPanelCanvas_PointerPressed(object sender, PointerRoutedEventArgs e)
{
currentPoint = e.GetCurrentPoint(ContentPanelCanvas).Position;
oldPoint= currentPoint;

}

private void ContentPanelCanvas_PointerMoved(object sender, PointerRoutedEventArgs e)
{
///获取相对于画布控件的点坐标
currentPoint=e.GetCurrentPoint(ContentPanelCanvas).Position;
//根据上一个画图的点和当前的画图的点新建一条线段
Line line = new Line() { X1=currentPoint.X,Y1=currentPoint.Y,X2=oldPoint.X,Y2=oldPoint.Y};
//设置线段的相关属性
line.Stroke = new SolidColorBrush(Colors.Red);
line.StrokeThickness = 5;
line.StrokeLineJoin = PenLineJoin.Round;
line.StrokeStartLineCap= PenLineCap.Round;
line.StrokeEndLineCap= PenLineCap.Round;
//把线段添加到画布面板上
this.ContentPanelCanvas.Children.Add(line);
//把当前的画图的点作为上一个画图的点
oldPoint= currentPoint;


}

7.2 Path图形
7.21.两种Path图形的创建方法
最重要的属性是Data,Data的数据类型是Geometry(几何图形),正是使用这个属性将一些基本的线段拼接起来,形成复杂的图形.Data属性赋值的方法有两种,一种是使用Geometry图形绘制的标准语法;另一种是专门用于绘制几何图形的”路径标记语法”.用Geometry图形实现的Path图形显得更加直观.用路径标记语法则显得更加简洁.

1
2
3
4
5
6
<Path Fill="Gold" Stroke="Black" StrokeThickness="1">
<Path.Data>
<RectangleGeometry Rect="0,0,100,100"/>
</Path.Data>
</Path>
<Path Data="M0,L100,0 L100,100 L0,100 z" Fill="Gold" Height="100" Strenth="Fill" Width="100"/>

7.22.使用简单的几何图形来创建Path
使用Geometry来创建Path图形,是非常直观的语法,它最大的优点是不仅仅可以通过XAML实现,还可以使用C#代码创建,所以如果要动态地改变Path的形状就需要使用Geometry来创建Path图形.
Path的Data属性是Geometry类,但是Geometry类是一个抽象类,所以不可能在XAML中直接使用标签.可以使用Geometry的子类.Geometry的子类包括以下几种
1.LineGeometry:直线几何图形
2.RectangleGeometry:矩形几何图形
3.EllipseGeometry:椭圆几何图形
4.PathGeometry:路径几何图形,PathGeometry是Geometry中最灵活的,可以绘制任意的2D几何图形
5.GeometryGroup:由多个基本几何图形组合在一起,形成几何图形组.
定义空间的区域有Shape和Geometry两种类型.Shape类型就是前面所讲的Line,Rectangle,Ellipse类,
Geometry类型不仅仅可以用在Path的Data属性上,还可以用在UIElement的Clip属性上.
Geometry对象可以分为3个类别:简单几何图形,路径几何图形以及复合几何图形.
简单几何图形类包括LineGeometry,RectangleGeometry和EllipseGeometry.用于创建基本的几何形状,例如直线,矩形和圆.
1.LineGeometry 通过指定直线的起点和终点来定义.
2.RectangleGeometry 通过使用Rect结构来定义,该机构指定矩形的相对位置,高度和宽度.
3.EllipseGeometry 通过中心点,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
<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>
</Pivot>

7.23.使用PathGeometry来创建Path
PathGeometry是Geometry里最灵活的几何图形,相当于Shape里面的Path一样,PathGeometry的核心是PathGeometry对象的集合,这些对象之所以这样命名是因为每个图形都描绘PathGeometry中的一个离散状态,每个PathFigure自身又由一个或多个PathSegment对象组成,每个这样的对象均描绘图形的一条线段.
PathGeometry所支持的Segment
ArcSegment 在两个点之间创建一条椭圆弧线
BezierSegment 在两个点之间创建一条三次方贝塞尔曲线
LineSegment 在两个点之间创建一条直线
PolyBezierSegment 创建一系列三次方贝塞尔曲线.
PolyQuadraticBezierSegment 创建一系列二次方贝塞尔曲线
PolyLineSegment 创建一系列直线
QuadraticBezierSegment 创建一条二次贝塞尔曲线
PathGeometry的Figures属性可以容纳PathFigure对象.而PathFigure对象的Segments属性又可以容纳各种线段用来组合成复杂的图形.
1.LineSegment和PolylineSegment
LineSegment类表示在两个点之间绘制一条线,它可能是Path数据的PathFigure的一部分.使用PathFigure对象通过LineSegment对象和其他线段来创建复合形状.LineSegment类不包含用于直线起点的属性,直线的起点是前一条线段的终点,如果不存在其他线段,则为PathFigure的StartPoint;其他的Segment也是一样的规则.
可以使用Line生成线条的简单形状,LineSegment用于在更复杂的几何组中绘制线.

1
2
3
4
5
6
7
8
9
10
11
<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>

PolyLineSegment类表示由PointCollection定义的线段集合,每个Point指定线段的终点.
PolyLineSegment在Xaml上语法示例如下,省略掉部分Path的代码与上文相同

1
<PolyLineSegment Points="50,100,50,150"/>

2.ArcSegment绘制圆弧.
Point属性指明圆弧连接的终点,Size属性表示完整椭圆的横轴和纵轴半径.RotationAngle属性指明圆弧母椭圆的旋转角度,SweepDirection属性指明圆弧是顺时针方向还是逆时针方向,IsLargeArc属性指明是否使用大弧去连接,如果椭圆上的两个点的位置不对称,这两点间的圆弧就会分为大弧和小弧.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<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>

3.BezierSegment,PolyBezierSegment,PolyQuadraticBezierSegment和QuadraticBezierSegment
一条三次方贝塞尔曲线由4个点定义:一个起点,一个终点,和两个控制点(Point1和Point2).三次方贝塞尔曲线的两个控制点作用像磁铁一样,朝着自身的方向吸引本应为直线的部分,从而形成一条曲线.第一个控制点Point1影响曲线的开始部分;第二个控制点Point2影响曲线的结束部分.注意,曲线不一定必须通过两个控制点之一.每个控制点将直线的一部分朝着自己的方向移动,但不能通过自身,PolyBezierSegment通过将Points属性设置为点集合来指定一条或多条三次方贝塞尔曲线.PolyBezierSegment实质上可以有无限个控制点,这些点和终点的值由Points属性值提供.
QuadraticBezierSegment表示二次方贝塞尔曲线,与BezierSegment类似,只是控制点由两个变成了一个.也就是说QuadraticBezierSegment由3个点决定:起点(即前一个线段的终点或PathFigure的StartPoint),终点(Point2属性,即曲线的终止位置)和控制点(Point1属性)
7.24.使用路径标记语法创建Path
路径标记语法 就是各种线段的简记法.可以简写为”L 150,5”
还增加了一些更实用的绘图命令 H用来绘制水平线,”H 180”就是指从当前点画一条水平直线,终点的横坐标是180.(不需要考虑纵坐标,纵坐标和当前点一致.) 类似的还有V命令,用来划竖直直线.
使用路径标记语法绘图一般分三步,移动至起点—绘图—闭合图形 移动M 绘图 L ,H,V,A,C,Q等.如果图形是闭合的,需要使用闭合命令Z,这样最后一条线段终点与第一条线段的起点就会连接上一条直线段.
在路径标记语法中使用两个Double类型的数值来表示一个点.第一个值表示的是横坐标(记作X),第二个值表示纵坐标(记作y),两个数字可以使用逗号分隔(x,y),也可以使用空格分隔.

1
<Path Stroke="Red" StrokeThickness="3" Data="M 100,200 C 100,25 400,350 400,175 H 280"/>

上面Path创建了由一条贝塞尔曲线线段和一条线段组成的Path.Data字符串以’move’命令开头(M),建立起点,大写的M表示新的当前点的绝对位置.小写的m表示相对位置.第一个线段是一个三次方贝塞尔曲线,该曲线从(100,200)开始,在(400,175)结束,使用(100,25)和(400,350)这两个控制点绘制.此线段由Data字符串中的C命令指示.同样,大写的C表示绝对路径,小写的c代表相对路径.第二个线段以绝对水平线命令H开头,它指定绘制一条从前面的子路径的终结点(400,175)到新终结点(280,175)的直线.由于他是一个水平线命令,因此指定的值是x坐标.
路径标记语法
移动指令 M M x,y或m x,y 大写M x,y是绝对值;小写的m指示x,y是相对于上一个点的偏移量, 0,0表示不存在偏移.在move命令之后列出多个点时,即使指定的是线条命令,也将绘制出连接这些点的线.
绘制指令 通过使用一个大写或小写字母输入各命令,大写表示绝对值,小写表示相对值,线段的控制点是相对于上一线段终点而言的,依次输入多个同一类型的命令时,可以省略重复的命令项,例如,L 100,200 300,400 等同于L 100,200 L 300,400
直线 Line L 格式:L结束点坐标 或I结束点坐标. L 100
水平直线 Horizontal line H H x值或h x值(x为System.Double类型的值) 绘制从当前点到指定x坐标的水平直线. H 100
垂直直线 Vertical line V V y或v y值 绘制从当前点到指定y坐标的垂直直线. V1 100
三次方贝塞尔曲线 Cubic Bezier curve C C第一控制点 第二控制点 结束点 通过指定两个控制点来绘制由当前点到指定结束点间的三次方程贝塞尔曲线. C 100,200 200,400 300,200 100,200为第一控制点
二次方程式贝塞尔曲线 Quadratic Bezier curve Q Q控制点结束点或q控制点结束点 通过指定的一个控制点来绘制由当前点到指定结束点间的二次方程贝塞尔曲线. q 100,200 300,200 100,200控制点 300,200 结束点
平滑三次方程式贝塞尔曲线 Smooth cubic Bezier curve (S) S控制点结束点或s控制点结束点 通过一个指定点来”平滑”控制当前点到指定点的贝塞尔曲线 如 S 100,200 200,300
平滑二次方程式贝塞尔曲线 smooth quadratic bezier curver T T控制点 结束点 T 100,200 200,300
椭圆圆弧:elliptical Arc A A尺寸圆弧旋转角度值优势弧的标记正负角度标记结点 在当前点与指定结束点间绘制圆弧.
尺寸 Size 指定椭圆圆弧X,Y方向上的半径值.
旋转角度 rotationAngle System.Double类型. 圆弧旋转角度值
优势弧的标记(isLargeArcFlag) 是否为优势弧,如果弧的角度大于等于180°,则设为1,否则为0
正负角度标记(sweepDirectionFlag) 当正角方向绘制时设为1 否则为0.
结束点 endPoint 例如:A 5,5 0 0 1 10,10
关闭指令 可选 用Z或z表示.用以将图形的首,尾点用直线连接,以形成一个封闭的区域
填充规则 fileRule 如果省略此命令,则路径使用默认行为,即EvenOdd.如果指定此命令,则必须将其置于最前面.
EvenOdd填充规则 F0指定EvenOdd填充规则.EvenOdd确定一个点是否位于填充区域内的规则方法:从该点沿任意方向画一条无限长的射线,然后计算该射线在给定形状中因交叉而形成的路径段数.如果该数为奇数,则点在内部;如果为偶数,则点在外部
Nonzero填充规则 F1指定Nonzero填充规则 Nonzero确定一个点是否位于路径填充区域内的的规则方法:从该点沿任意方向画一条无限长的射线,然后检查形状段与该射线的交点,从0开始计数,每当线段从左到右穿过该射线时加1,而每当路径段从左到右穿过该射线时加1,而每当路径段从右向左穿过该射线-1.计算交点的数目后,如果结果为0,则说明该点位于路径外部,否则,它位于路径内部.
7.25.使用Path实现自定义图形
布局面板通过重载FrameworkElement类的ArrangeOverride方法对子对象进行排列,从Path类派生的图形类一样也可以通过重载ArrangeOverride方法对图形进行初始化.在初始化逻辑生成适用于自定义形状的几何图形,随后再将其设置赋值给Path.Data属性,可以充分利用Width和Height等这些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
  <Grid x:Name="HexagonContentPanel" Margin="12,0,12,0">
<controls:Hexagon Height="300" Width="300" Stroke="Yellow" StrokeThickness="8" Fill="Red"/>
</Grid>

public class Hexagon : Path
{
private double lastWidth = 0;
private double lastHeight = 0;
private PathFigure figure;
public Hexagon()
{
CreateDataPath(0,0);
}

private void CreateDataPath(double width,double height)
{
//把高度和宽度先减去边的宽度
height -= this.StrokeThickness;
width-= this.StrokeThickness;
//用来跳出布局的循环,因为只创建一次
if (lastWidth == width && lastHeight == height)
return;
lastWidth= width;
lastHeight= height;
PathGeometry geometry= new PathGeometry();
figure= new PathFigure();
//取左上角的点为第一个点
figure.StartPoint=new Windows.Foundation.Point(0.25*width,0);
//算出每个顶点的坐标然后创建线段连接起来
AddPoint(0.75*width,0);
AddPoint(width,0.5*height);
AddPoint(0.75*width,height);
AddPoint(0.25*width,height);
AddPoint(0,0.5*height);
figure.IsClosed= true;
geometry.Figures.Add(figure);
this.Data = geometry;
}

private void AddPoint(double x, double y)
{
LineSegment segment= new LineSegment();
segment.Point=new Point(x+0.5*StrokeThickness,y+0.5*StrokeThickness);
figure.Segments.Add(segment);
}
protected override Size MeasureOverride(Size availableSize)
{
return availableSize;
}

protected override Size ArrangeOverride(Size finalSize)
{
//初始化图形
CreateDataPath(finalSize.Width,finalSize.Height);
return finalSize;
}


}

7.26.利用Expression Blend工具创建Path图形
1.使用笔或者铅笔工具画出Path图形
打开Expression Blend工具,可以找到左边图标工具栏上的笔或铅笔
2.把相关的图形转换成Path图形.
在Blend里面除了基本图形外,还封装了很多其他的图形,可以直接把这些图形转换为Path图形.转换的方法是在图形上面,右键——>路径——>转换为路径,这时候就把相关的图形转换为Path绘图的方式.
3.通过合并图形的方式生成Path图形
合并图形的方式是指在Blend里面创建了多个图形,按住Ctrl键把要合并的图形选中,然后右键—>合并—>相并/拆分/相交/相减/排除重叠.
4.导入设计的源文件
在Blend里可以导入Illustrator文件/FXG文件/Photoshop文件,把这些文件转换为XAML编码,同时也会把里面相关的图形转换为Path图形,这是Blend工具提供的一个非常有用的一个功能,通常设计师会采用Phoneshop设计图片,直接把设计好的Photoshop文件导入就可以把文件转成为XAML编码
7.3画刷
使用XAML时,在控件的Background,Foreground和Fill属性中简单的输入颜色名,应用程序运行时就会将字符串值转换为有效的颜色资源对应的画刷.
7.31.SolidColorBrush画刷
SolidColorBrush画刷主要用来填充单色形状或控件,可以直接通过颜色进行创建,在Xaml中直接输入颜色名使用的就是SolidColorBrush画刷,可以通过直接使用Colors里面的颜色值进行赋值.

1
<Rectangle width="100" height="100" Fill="#FFFF0000"/>

7.32.LinearGradientBrush画刷
线性渐变画刷(LinearGradientBrush)用来填充一个复合渐变色到一个元素中,并且可以任意搭配两种或两种以上的颜色,重要的属性有倾斜点(GradientStop),渐变颜色(Color),起始坐标点(StartPoint),结束坐标点(EndPoint).

1
2
3
4
5
6
7
8
<Path Canvas.Left="15" Canvas.Top="50" Stroke="Black" Data="M 0,0 A 15,5 180 1 1 200,0 L 200,100 L 300,100 L 300,200 A 15,5 180 1 1 100,200 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>

7.33.ImageBrush画刷
ImageBrush画刷使用图像绘制一个区域,由其ImageSource属性指定的JPEG或PNG图像绘制区域,默认情况下,ImageBrush会将其图像拉伸以完全充满要绘制的区域,如果绘制的区域和该图像的长宽比不同,则可能会扭曲该图像.可以通过将Stretch属性从默认值Fill更改为None,Uniform或者UniformToFill来更改此现象.

1
2
3
4
5
<Ellipse Height="180" Width="180" Margin="50,0,0,0">
<Ellipse.Fill>
<ImageBrush ImageSource="Assets/ApplicationIcon.png" Stretch="Fill"/>
</Ellipse.Fill>
</Ellipse>

通常会利用ImageBrush来做一些特别的遮罩效果,
7.4 图形裁剪
Geometry类型还有一个用途就是可以用在UIElement的Clip属性上对从UIElement派生的控件进行图形裁剪,裁剪出来的形状就是Geometry类型所构成的图形.Geometry类型在UIElement的Clip属性上的语法和在Path的Data属性上的语法完全一样.
7.41.使用几何图形进行裁剪
裁剪图像时是指仅把图像的某个局部区域的图像显示出来.可以通过使用UIElement的Clip属性来设置图像裁剪的区域,Clip属性值为Geometry类型的派生类,可以取值为RectangleGeometry类型,这就意味着可以从图像中裁切掉矩形的几何形状.

1
2
3
4
5
<Image Source="myImage.jpg" Width="200" Height="150">
<Image.Clip>
<RectangleGeometry Rect="0,0,50,50"/>
</Image.Clip>
</Image>

因为Clip属性是UIElement的属性,所以不仅仅只有Image控件可以使用其来裁剪图形,所有的UI对象都可以使用Clip属性对UI进行裁剪.
7.42.对布局区域进行剪裁
使用布局面板来布局一些子控件,实际上这些子控件所在的位置可以超出布局面板的范围,实现一个布局面板,让面板里的子控件都在面板的范围里,超出面板就不可见.可以利用Clip属性去实现,因为Clip属性是UIElement类的属性,也就是说所有的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
  <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>

public class Clip
{


public static bool GetToBounds(DependencyObject obj)
{
return (bool)obj.GetValue(ToBoundsProperty);
}

public static void SetToBounds(DependencyObject obj, bool value)
{
obj.SetValue(ToBoundsProperty, value);
}

// Using a DependencyProperty as the backing store for ToBounds. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ToBoundsProperty =
DependencyProperty.RegisterAttached("ToBounds", typeof(bool), typeof(Clip), new PropertyMetadata(false,OnToBoundsPropertyChanged));
/// <summary>
/// 定义属性改变事件
/// </summary>
/// <param name="d"></param>
/// <param name="e"></param>
/// <exception cref="NotImplementedException"></exception>
private static void OnToBoundsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FrameworkElement fe = d as FrameworkElement;
if (fe != null)
{
//裁剪图形
ClipToBounds(fe);
//当对象加载或者大小改变时都需要重新处理边界
fe.Loaded += Fe_Loaded;
fe.SizeChanged += Fe_SizeChanged;
}
}

private static void Fe_SizeChanged(object sender, SizeChangedEventArgs e)
{
ClipToBounds(sender as FrameworkElement);
}

private static void Fe_Loaded(object sender, RoutedEventArgs e)
{
ClipToBounds(sender as FrameworkElement);
}

private static void ClipToBounds(FrameworkElement fe)
{
//如果ToBounds设置为true则进行裁剪
if (GetToBounds(fe))
{
//使用布局面板的实际高度和宽度来创建一个RectangleGeometry赋值给Clip属性
fe.Clip = new Windows.UI.Xaml.Media.RectangleGeometry()
{
Rect = new Windows.Foundation.Rect(0, 0, fe.ActualWidth, fe.ActualHeight)
};
}
else
{
fe.Clip = null;
}
}
}

7.5.使用位图编程
Image控件用于显示图片,在Win10中将一张图片显示出来可以添加一个Image控件,然后在控件中设置图片的路径.使用Image控件显示图片语法如下:

1
2
3
<Image Source="myPicture.png"/>
Image myImage=new Image();
myImage.Source=new BitmapImage(new Uri("ms-appx:///myPicture.jpg",UriKind.Absolute));

Source属性用于指定要显示的图像位置,在路径中,使用ms-appx URI方案名称编写安装文件夹地址:ms-appx:///,表示指向于安装包文件的地址.
7.51 拉伸图像
如果没有设置Image的Width和Height值,使用图像自然尺寸显示,Stretch:None,Uniform,Uniform ToFill.
7.52.使用RenderTargetBitmap类生成图片
RenderTargetBitmap类可以将可视化对象转换为位图,也就是说可以将任意的UIElement以位图的形式呈现,在实现的编程中通常利用RenderTartgetBitmap类对UI界面进行截图操作.
使用RenderTargetBitmap类生成图片一般有两种用途,一种是直接把生成的图片在当前的页面上进行展示,还有一种是把生成的图片当作文件存储起来,或者通过某种分享方向把图片文件分享出去.
使用RenderTargetBitmap类生成图片操作主要是依赖于RenderTargetBitmap类的RenderAsync方法.
RenderAsync方法有两个重载;RenderAsync(UIElement)和RenderAsync(UIElement,Int32,Int32),可在后者处指定要与源可视化树的自然大小不同的所需图像源尺寸.RenderAsync方法设计为异步方法,因此无法保证与UI源进行精确的框架同步,但大多数情况下都足够及时.由于RenderTargetBitmap是ImageSource的子类,因此,可以将其用作Image元素或ImageBrush画笔的图像源.

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
<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>
/// <summary>
/// 指针释放的事件处理程序
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void root_PointerReleased(object sender, PointerRoutedEventArgs e)
{
//创建一个RenderTargetBitmap对象,对界面中的Grid控件root生成图片
RenderTargetBitmap bitmap = new RenderTargetBitmap();
await bitmap.RenderAsync(root);
//把图片展现出来
img.Source=bitmap;
}

7.53.存储生成的图片文件
在调用RenderAsync方法时会初始化RenderTargetBitmap类的对象,但是RenderTargetBitmap类的对象本身并不能作为图片来进行存储,要生成图片文件需要获取到图片的二进制数据,如果想要获取DataTransferManager操作 如共享协定交换的图像,或者想要使用Windows.Graphics.Imaging Api将效果应用到图像上或对图像进行转码,就需要用到像素数据,如果访问RenderTargetBitmap的pIxels数据,需要在用RenderAsync方法将UIElement定义为RenderTargetBitmap后m在调用RenderTargetBitmpa的GetPixelsAsync方法来获取其Pixels数据,该方法返回值是IBuffer类型,里面存储的是二进制位图数,这个IBuffer可以转换成一个Byte数组,数组里面的数据以BGRA8格式存储.
以下代码示例如何从一个RenderTargetBitmap对象中获得以Byte数组类型存储的像素数.需要特别注意的是IBuffer实例调用的ToArray方法是一个扩展方法,需要在项目中加入System.Runtime.InteropServices.WindowsRuntime这个命名空间.

1
2
3
4
var bitmap=new RenderTargetBitmap();
await bitmap.RenderAsync(elementToRender);
IBuffer pixelBuffer=await bitmap.GetPixelsAsync();
byte[] pixels=pixelBuffer.ToArray();

在获取到了图像的二进制数据之后,如果要把二进制的数据生成图片文件,需要使用到BitmapEncoder类.BitmapEncoder类包含创建,编辑和保存图像的各种方法.创建图片文件首先需要调用BitmapEncoder类CreateAsync方法,使用文件的流创建一个BitmapEncoder对象,然后再使用BitmapEncoder类的SetPixelData设置图像有关帧的像素数据.SetPixelData的方法参数如下:

1
SetPixelData(BitmapPixelFormat pixelFormat,BitmapAlphaMode alphaMode,uint width,uint height,double dpiX,double dpiY,byte[] pixels)

其中,pixelFormat表示像素数据的像素格式,alphaMode表示像素数据的alpha模式,width表示像素数据的宽度(以像素为单位),height表示像素数据的高度(以像素为单位);dpiX表示像素数据的水平分辨率(以每英寸点数为单位);pixels表示像素数据.该方法是同步的,因为直到调用FlushAsync,GoToNextFrameAsync或GoToNextFrameAsync(IIterable(IkeyValuePair))才会提交数据.该方法将所有像素数据视为sRGB颜色空间中的像素数据.
保存截图文件的示例

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
           <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>
/// <summary>
/// 按钮事件生成图片并保存到程序的存储里
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void bt_save_Click_2(object sender, RoutedEventArgs e)
{
//生成RenderTargetBitmap对象.
RenderTargetBitmap bitmapbitmap = new RenderTargetBitmap();
await bitmapbitmap.RenderAsync(root1);
//获取图像的二进制数据
var pixelBuffer=await bitmapbitmap.GetPixelsAsync();
//创建程序文件存储
IStorageFolder applicationFolder=ApplicationData.Current.LocalFolder;
IStorageFile saveFile = await applicationFolder.CreateFileAsync("snapshot.png",CreationCollisionOption.OpenIfExists);
//把图片的二进制数据写入文件存储
using (var fileStream=await saveFile.OpenAsync(FileAccessMode.ReadWrite))
{
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId,fileStream);
encoder.SetPixelData(
BitmapPixelFormat.Bgra8,
BitmapAlphaMode.Ignore,
(uint)bitmapbitmap.PixelWidth,
(uint)bitmapbitmap.PixelHeight,
DisplayInformation.GetForCurrentView().LogicalDpi,
DisplayInformation.GetForCurrentView().LogicalDpi,
pixelBuffer.ToArray());
await encoder.FlushAsync();
}
}
/// <summary>
/// 展示程序存储图片的按钮事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void bt_show_Click(object sender, RoutedEventArgs e)
{
// “ms-appdata:///local”表示是程序存储的根目录
BitmapImage bitmapImage=new BitmapImage(new Uri("ms-appdata:///local/snapshot.png",UriKind.Absolute));
img1.Source= bitmapImage;
}

深入浅出Windows通用应用程序开发(一)


创建Windows 10通用应用

创建Hello World项目

1.打开vs,New Project,C#,Windows,Universal/UWP,选择一个空白项目模板(Universal Windows),单击OK按钮完成项目的创建.
2.MainPage.xaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<Page
x:Class="App1.MainPage"
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"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

<Grid>
<Button HorizontalAlignment="Center" VerticalAlignment="Center" Content="Hello,App" Click="Button_Click"/>
</Grid>
</Page>

3.编辑部署程序
选择Deploy Solution在模拟器上运行应用程序.

解析应用

1.MainPage.xaml
Background=”{ThemeResource ApplicationPageBackgroundThemeBrush}”>
表示当前背景使用的是系统的主题资源背景.
2.App.xaml.cs

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
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation;
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.Media.Animation;
using Windows.UI.Xaml.Navigation;

namespace App1
{
/// <summary>
/// 提供特定于应用程序的行为,以补充默认的应用程序类。
/// </summary>
sealed partial class App : Application
{

private TransitionCollection transitions;
/// <summary>
/// 初始化单一实例应用程序对象。这是执行的创作代码的第一行,
/// 已执行,逻辑上等同于 main() 或 WinMain()。
/// </summary>
public App()
{
this.InitializeComponent();
this.Suspending += OnSuspending;
}

/// <summary>
/// 在应用程序由最终用户正常启动时进行调用。
/// 将在启动应用程序以打开特定文件等情况下使用。
/// </summary>
/// <param name="e">有关启动请求和过程的详细信息。</param>
protected override void OnLaunched(LaunchActivatedEventArgs e)
{
Frame rootFrame = Window.Current.Content as Frame;

// 不要在窗口已包含内容时重复应用程序初始化,
// 只需确保窗口处于活动状态
if (rootFrame == null)
{
// 创建要充当导航上下文的框架,并导航到第一页
rootFrame = new Frame();
//设置当前的页面框架可以缓存的页面数量

rootFrame.CacheSize=1;

rootFrame.NavigationFailed += OnNavigationFailed;
//判断程序的上一个状态.
if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
{
//如果是从休眠程序启动,这里要加载上一个状态的相关数据
//TODO: 从之前挂起的应用程序加载状态
}

// 将框架放在当前窗口中
Window.Current.Content = rootFrame;
}

if (e.PrelaunchActivated == false)
{
//判断框架内容是否为空
if (rootFrame.Content == null)
{
//如果相关的动画值不为空则添加动画
if (rootFrame.ContentTransitions!=null)
{
this.transitions= new TransitionCollection();
foreach (var item in rootFrame.ContentTransitions)
{
this.transitions.Add(item);
}
}
rootFrame.ContentTransitions= null;
//添加导航跳转完成的事件
rootFrame.Navigated += RootFrame_Navigated;
///导航跳转到引用程序的MainPage页面
if (!rootFrame.Navigate(typeof(MainPage), e.Arguments)) {
throw new Exception("Failed to create initial page.");
}
// 当导航堆栈尚未还原时,导航到第一页,
// 并通过将所需信息作为导航参数传入来配置
// 参数
rootFrame.Navigate(typeof(MainPage), e.Arguments);
}
// 确保当前窗口处于活动状态
Window.Current.Activate();
}
}
//导航事件完成的处理程序,从新创建启动动画的集合.
private void RootFrame_Navigated(object sender, NavigationEventArgs e)
{
var rootFrame = sender as Frame;
rootFrame.ContentTransitions=this.transitions= new TransitionCollection() { new NavigationThemeTransition()};
rootFrame.Navigated-= RootFrame_Navigated;
}

/// <summary>
/// 导航到特定页失败时调用
/// </summary>
///<param name="sender">导航失败的框架</param>
///<param name="e">有关导航失败的详细信息</param>
void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
{
throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
}

/// <summary>
/// 在将要挂起应用程序执行时调用。 在不知道应用程序
/// 无需知道应用程序会被终止还是会恢复,
/// 并让内存内容保持不变。
/// </summary>
/// <param name="sender">挂起的请求的源。</param>
/// <param name="e">有关挂起请求的详细信息。</param>
private void OnSuspending(object sender, SuspendingEventArgs e)
{
var deferral = e.SuspendingOperation.GetDeferral();
//TODO: 保存应用程序状态并停止任何后台活动
deferral.Complete();
}
}
}

应用程序的三种主要状态,Running,NotRunning,Suspended.
1.应用启动 从其他状态到Running
2.应用激活 从NotRunning到Running
activated事件参数包括一个PreviousExecutionState属性,该属性告诉你应用在激活之前处于哪种状态.此属性是ApplicationExecutionState属性枚举值之一,CloseByUser,Terminated,NotRunning.PreviousExecutionState.Running,Suspended.应用以前不是被终止,因此不用担心还原数据.
3.应用挂起.从Running状态到Suspended状态.
4.应用恢复 从Suspended状态到Running状态.

3.Package.appxmanifest
声明应用的标识,应用的功能以及用来进行部署和更新的信息.
Package元素是整个清单的根节点.Identity元素表示应用程序版本发布者名称的信息.mp:PhoneIdentity元素表示应用程序相关的唯一标识符信息.Properties元素包含应用程序的名称,发布者名称等信息的设置.Prerequisites元素则是用于设置应用程序所支持的系统版本号,Resources元素表示应用程序所使用资源信息.Applicaitons元素包含了与应用程序相关的logo设置,闪屏图片设置等可视化的设置信息.Capablities元素表示当前应用程序所使用的一些手机特定功能,例如internetClient表示使用网络的功能

不同平台设备的适配

特定平台的API调用

Window10里面有Windows Mobile Extension SDK和Windows Desktop Extension SDK分别表示手机版本和桌面版本的扩展SDK.都是内置在WIndow10SDK里面的,默认情况下不会给项目工程加上,需要使用时可以在项目工程里引用.Project,Add Reference,Window Universal,Extensions.在使用Api时必须判断当前环境支不支持.
判断方法为Windows.Foundation.Metadata.ApiInformation.IsTypePresent(string typeName),typename表示带完整命名空间的API名称,i返回true表示支持.ji返回false表示不支持.下面通过一个手机硬件后退键的适配来演示如何使用和适配特定平台的API.
首先创建WIndow10通用应用程序项目,BackButtonDemo,在项目里面引用Windows Mobile Extension SDK.
1.BlackPage.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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
<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"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

<Grid>
<StackPanel VerticalAlignment="Top" Orientation="Horizontal">
<Button x:Name="backButton" Margin="24,24,24,24" Click="backButton_Click" Style="{StaticResource NavigationBackButtonNormalStyle}"/>
<TextBlock Text="你好" FontSize="30" HorizontalAlignment="Left" VerticalAlignment="Center"/>

</StackPanel>
</Grid>
</Page>
//App.xaml.cs
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=234238 上介绍了“空白页”项模板

namespace App1
{
/// <summary>
/// 可用于自身或导航至 Frame 内部的空白页。
/// </summary>
public sealed partial class BlankPage : Page
{
public BlankPage()
{
this.InitializeComponent();
if (App.IsHardwareButtonsAPIPresent)
{
backButton.Visibility = Visibility.Collapsed;
}
else
{
backButton.Visibility = Visibility.Visible;
}
}
/// <summary>
/// 后退按钮处理
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void backButton_Click(object sender, RoutedEventArgs e)
{
if(this.Frame.CanGoBack)
{ this.Frame.GoBack(); }
}
}
}
///MainPage.xaml
<Page
x:Class="App1.MainPage"
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"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

<Grid>
<StackPanel Orientation="Vertical">
<Button HorizontalAlignment="Center" VerticalAlignment="Center" Content="Hello,App" Click="Button_Click"/>
<Button Content="跳转到新的页面" Click="Button2_Click" VerticalAlignment="Center" HorizontalAlignment="Center"/>

</StackPanel>
</Grid>
</Page>
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=402352&clcid=0x804 上介绍了“空白页”项模板

namespace App1
{
/// <summary>
/// 可用于自身或导航至 Frame 内部的空白页。
/// </summary>
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}

private async void Button_Click(object sender, RoutedEventArgs e)
{
MediaElement media=new MediaElement();
var synth = new Windows.Media.SpeechSynthesis.SpeechSynthesizer();
Windows.Media.SpeechSynthesis.SpeechSynthesisStream stream = await synth.SynthesizeTextToStreamAsync("Hello,App!");
media.SetSource(stream,stream.ContentType);
media.Play();

}

private void Button2_Click(object sender, RoutedEventArgs e)
{
this.Frame.Navigate(typeof(BlankPage));
}
}
}

界面适配

1.使用相对的布局控件来进行排列,如Grid,RelativePanel等.
2.使用相对的属性来设置控件在容器的位置.如orizontaolAligment,VertaicalAlignment属性
3.设置长度宽度的最大值最小值限制;
4.根据设备实际分辨率来设置长度宽度或者位置;

xaml界面原理和语法

对象元素

Xaml的对象元素是指Xaml中一个完整的节点.

设置属性

1.使用属性语法
2.使用属性元素语法
3.使用内容元素语法
4.使用集合语法

附加属性

标记扩展

1.Binding 实现在Xaml载入时,将数据绑定到xaml对象.
2.StaticResource标记扩展,实现引用数据字典中定义的静态资源.
3.ThemeResource标记扩展,表示系统内置的静态资源.
4.TemplateBinding标记扩展,实现在Xaml页面中对象模板绑定调用.
5.RelativeSource标记扩展,实现对特定数据源的绑定.

事件

<元素对象 事件名称 =”事件处理”>

Xaml原理

动态加载xaml

动态加载Xaml是指在程序运行时通过解析xaml格式的字符串或文件来动态生成UI的效果.
使用XamlReader.Load方法动态加载XAML对XAML的字符串是有一定要求的,这些格式良好的XAML片段必须符合以下要求:
1.Xaml内容串必须定义单个根元素,使用XamlReader.Load创建的内容只能赋予一个Win10对象,它们是1对1关系.
2.内容字符串Xaml必须是格式良好的xml,并且必须是可分析的Xaml.
3.所需的根元素还必须指定某一默认的Xml命名空间值,这通常是命名空间xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation"
动态加载Xaml的示例:演示了使用XamlReader.Load方法加载Xaml字符串生成一个按钮和加载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
   <StackPanel x:Name="sp_Show">
<Button x:Name="bt_addXaml" Content="加载Xaml按钮" Click="bt_addXaml_Click"/>
</StackPanel>

/// <summary>
/// 加载Xaml按钮
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void bt_addXaml_Click(object sender, RoutedEventArgs e)
{
//注意Xaml字符串里面的命名空间"http://schemas.microsoft.com/winfx/2006/presentation"不能少
string buttonXaml = $"<Button xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' Content=\"加载XAML文件\" Foreground=\"Red\"/>";
Button btnRed=(Button)XamlReader.Load(buttonXaml);
btnRed.Click += BtnRed_Click;
sp_Show.Children.Add(btnRed);
}
/// <summary>
/// 已加载的XAML按钮关联的事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void BtnRed_Click(object sender, RoutedEventArgs e)
{
string xaml = string.Empty;
//加载程序的Retangle.xaml文件
StorageFile fileRead=await Windows.ApplicationModel.Package.Current. InstalledLocation.GetFileAsync("Rectangle.xaml");
//读取文件的内容
xaml=await FileIO.ReadTextAsync(fileRead);
//加载Rectangle
Rectangle rectangle=(Rectangle)XamlReader.Load(xaml);
sp_Show.Children.Add(rectangle);
}

/// Rectangle.xaml
<Rectangle
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="100" Width="200"
>

<Rectangle.Fill>
<LinearGradientBrush>
<GradientStop Color="Black" Offset="0"/>
<GradientStop Color="Red" Offset="0.5"/>
<GradientStop Color="Black" Offset="1"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>

添加Rectangle.xaml文件,需要手动设置Build Action属性为Content.表示作为内容资源来使用。

XAML树结构

可视化树

VisualTreeHelper类
VisualTreeHelper是一个静态帮助器类,它提供了一个在可视化对象级别编程的低级功能
提供了用来枚举可视化树成员的功能.可以在运行时对可视化树执行操作,并且可以遍历到模板部件,这是一种可用来检查模板组成情况的有用手段.此外,可以检查可能通过数据绑定填充的子集合,或者是应用程序代码可能无法全部了解运行时对象树的完整本质的子集合.若要检索父级,请调用GetParent方法.若要检索父级,请调用GetParent方法,若要检索可视化对象的子级或直接子代,请调用GetChild方法,此方法返回父级在指定索引处的子对象.
VisualTreeHelper4个常用静态方法
1.FindElementsInHostCoordinates 检索一组对象,这些对象位于某一对象的坐标空间的指定点或矩形内
2.GetChild 使用提供的索引,通过检查可视化树获取所提供对象的特定子对象
3.GetChildrenCount 返回在可视化树中在某一对象的子集合中存在的子级的数目.
4.GetParent 返回可视化树中某一对象的父对象.

遍历可视化树

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
 <StackPanel x:Name="stackPanel" Margin="12,0,12,0">
<TextBox>
</TextBox>
<Button Content="遍历" Click="Button_Click1">
</Button>
</StackPanel>

string visualTreeStr = "";
/// <summary>
/// 单击事件,弹出XAML页面里面StackPanel控件的可视化树的所有对象.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void Button_Click1(object sender, RoutedEventArgs e)
{
visualTreeStr = "";
GetChildType(stackPanel);
MessageDialog messageDialog = new MessageDialog(visualTreeStr);
await messageDialog.ShowAsync();

}
/// <summary>
/// 获取某个XAML元素的可视化对象的递归方法
/// </summary>
/// <param name="stackPanel"></param>
/// <exception cref="NotImplementedException"></exception>
private void GetChildType(DependencyObject reference)
{
//获取子对象的个数
int count=VisualTreeHelper.GetChildrenCount(reference);
//如果子对象的个数不为0 继续递归
if (count>0)
{
for (int i = 0; i <= VisualTreeHelper.GetChildrenCount(reference)-1; i++)
{
//获取当前节点的子对象.
var child=VisualTreeHelper.GetChild(reference, i);
//获取子对象的类型
visualTreeStr += child.GetType().ToString() + count + "";
//递归调用
GetChildType(child);
}
}
}

框架和页面

Frame frmae=Window.current.Content as Frame;
this.Frame.Navigate(typeof(BlackPage1));

页面导航

应用的导航历史记录是一种后进先出的堆栈结构.该结构还称为后退堆栈,因为它所包含的一组页面在后退导航的堆栈结构中,最新的页面会被添加到顶部,此操作称为推送操作.删除堆栈顶部项的操作称为弹出操作.
还可以使用Frame类BackStack属性获取后退堆栈的项目,后退堆栈的项目为PageStackEntry类的对象.PackStackEntry类表示后退或前进导航历史记录中的一个条目.通过PageStackEntry类的Type属性和Parameter属性可以知道导航过来的Page对象的类型和参数.

控件编程

扁平化设计风格
系统控件基类有FrameworkElement类.Panel类,Control类.ContentControl类.和ItemControl类.所有的系统控件都派生自这5个基类.这5个基类自身之间也是派生的关系.
FrameworkElement类
Panel类 Control类
Control类
ContentControl类 ItemControl类
1.FrameworkElement类
2.Panel类
布局
3.Control类
表示用户界面元素的基类.这些元素使用ControlTemplate来定义其外观.ControlTemplate是控件的模板.由多个元素组成,并且可以直接由用户去定义和修改.
4.ContentControl类
Content属性
5.ItemsControl类.
ItemsControl类是派生自Control类,表示一个可用于呈现项的集合的控件.ItemsControl类本身也是一个列表控件类.或用于显示从指向某个对象的数据绑定中获取的列表.当然,ItemsControl类只是封装了列表的一些基本特性,ItemSource属性,指定为对某个对象集合的引用.

5各类别
1.面板控件 Pannel派生 Canvas,Grid,StackPanel,RelativePanel.
2.内容控件 Button,RadioButton,HyperlinkButton,CheckBox,ScrollViewer.

ContentPresenter元素用于ContentControl的Template的内部,不同的内容控件(如Button,CheckBox)会把ContentPresenter放置在控件内部不同的地方.内容控件通过给Content属性赋值就会把相关信息传递到了ContentPresenter里面进行呈现和显示.
3.列表控件
这类控件由ItemsControl类派生,经常用于显示数据的集合,包括ListBox,Pivot,ListView和GridView控件.
最主要的属性 Items表示生成控件内容的集合.ItemsPanel表示定义了控制项的布局的面板.ItemSource表示生成ItemsControl的内容的集合.ItemTemplate表示用于显示每个项的DataTemplate.
4.普通控件
直接派生Control类,包含TextBox,PasswordBox,ProgressBar,ScrollBar,Slider等控件.这类控件的共性特点是可以自定义或者修改其控件的数据模板,也就是说控件内部的呈现元素可以通过模板去修改.
5.其他控件
FrameworkElement TextBlock. Border.Image,MediaElement.Popup.
4.2 Button
1.Windows.UI.Controls.Button.
按钮控件三个状态. 分别是按下状态(Press),悬停状态(Hover),释放状态(Release) 通过设置按钮控件的ClickMode属性来控制按钮在哪种状态下才执行Clcik事件.
2.文字的相关属性
Win10内置的SymbolIcon图形作为Content属性.在Visual Studio里面打开按钮属性窗口.
选择Symbol的图形就可以看到一系列扁平化的图标图形.
TextBox
如需支持Enter换行 需要把AcceptsReturn设为true.
通过InputScope属性来设置在控件输入信息所提供的键盘类型,比如TextBox只是要求用户输入手机号,
设置InputScope=”TelephoneNumber”来制定电话号码的输入键盘.可以通过枚举值InputScopeNameValue来看到所有的键盘类型,包括EmailSmtpAddress,Url,Number等.
textBox1.InputScope=new InputScope();
textBox1.InputScope.Names.Add(new InputScopeName(){NameValue=InputScopeNameValue.TelephoneLocalNumber});
3.控件头
Header 请输入用户名
4.5边框 Border
边框是指在另一个对象周围绘制边框,背景或同时绘制二者的控件.
4.6 超链接
<HyperlinkButton …/> 或 内容
4.7 单选按钮
4.9 ProgressBar
重复模式 非重复模式 IsIndeterminate true 重复 默认

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
<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"/>

private void cancel_Click(object sender, RoutedEventArgs e)
{
//隐藏进度条
progressBar1.Visibility= Visibility.Collapsed;
}

private void begin_Click(object sender, RoutedEventArgs e)
{
//设置进度条为可见
progressBar1.Visibility = Visibility.Visible;
if (radioButton1.IsChecked==true)
{
//设置进度条为不可重复模式
progressBar1.IsIndeterminate = false;
//使用一个定时器,每一秒钟触发以下进度的改变
DispatcherTimer timer= new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += Timer_Tick;
timer.Start();
}
else
{
//设置进度条的值为0
progressBar1.Value = 0;
//设置进度条为重复模式
progressBar1.IsIndeterminate=true;

}
}
//定时器的定时触发的事件处理
private async void Timer_Tick(object sender, object e)
{
//如果进度没有到达100则在原来的进度基础上再增加10
if (progressBar1.Value<100)
{
progressBar1.Value += 10;
}
else
{

//进度完成
(sender as DispatcherTimer).Tick-= Timer_Tick;
//停止定时器的运行
(sender as DispatcherTimer).Stop();
await new MessageDialog("进度完成").ShowAsync();

}
}

4.10 滚动视图(ScrollViewer)

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
<ScrollViewer Height="200" Width="200" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
<ScrollViewer.Content>
<StackPanel>
<Image Source="/Assets/StoreLogo.png"/>
</StackPanel>
</ScrollViewer.Content>
</ScrollViewer>

<StackPanel>
<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"/>
//添加图片到ScrollViewer里面的StackPanel中.
for (int i = 0; i <=30; i++)
{
Image imgItem = new Image();
imgItem.Width = 200;
imgItem.Height = 200;
//4张图片循环添加到StackPanel的子节点上
if (i % 4 == 0)
{
imgItem.Source = (new BitmapImage(new Uri("a.jpg", UriKind.RelativeOrAbsolute)));

}
else if (i % 4 == 1)
{
imgItem.Source = (new BitmapImage(new Uri("b.jpg", UriKind.RelativeOrAbsolute)));
}
else if (i % 4 == 2)
{
imgItem.Source = (new BitmapImage(new Uri("c.jpg", UriKind.RelativeOrAbsolute)));
}
else if (i % 4 == 3) {
imgItem.Source = (new BitmapImage(new Uri("d.jpg", UriKind.RelativeOrAbsolute)));
};
this.stkpnImage.Children.Add(imgItem);
}
//初始化tmrDown定时触发器
tmrDowm = new DispatcherTimer();
//每500毫秒跑一次
tmrDowm.Interval=new TimeSpan(500);
//加入每次tick的事件
tmrDowm.Tick += TmrDowm_Tick;
//初始化tmrUp定时触发器
tmrUp=new DispatcherTimer();
tmrUp.Interval=new TimeSpan(500);
tmrUp.Tick += TmrUp_Tick;

private DispatcherTimer tmrDowm;
//往上滚动的定时触发器
private DispatcherTimer tmrUp;
//往上按钮事件
private void btnUp_Click(object sender, RoutedEventArgs e)
{
tmrDowm.Stop();
tmrUp.Start();
}

private void TmrUp_Tick(object sender, object e)
{
//将VerticalOffset-10将出现图片将往上滚动的效果
scrollViewer1.ChangeView(0,scrollViewer1.VerticalOffset-10,0);
}
private void TmrDowm_Tick(object sender, object e)
{
//先停止往上的定时触发器
tmrUp.Stop();
//将VerticalOffset+10将出现图片往下滚动的效果
scrollViewer1.ChangeView(0, scrollViewer1.VerticalOffset + 10, 0);
}

private void btnDown_Click(object sender, RoutedEventArgs e)
{
tmrUp.Stop();
tmrDowm.Start();
}

private void stop_Click(object sender, RoutedEventArgs e)
{
tmrDowm.Stop();
tmrUp.Stop();
}

4.11.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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
 <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="ellipse1" Stroke="Black" StrokeThickness="1" Width="224"/>
<TextBlock x:Name="textBlockColor" Text="颜色" FontSize="26"/>

</StackPanel>
//三个Slider控件的初始值
redSlider.Value = 128;
greenSlider.Value= 128;
blueSlider.Value= 128;
if (App.IsHardwareButtonsAPIPresent)
{
backButton.Visibility = Visibility.Collapsed;
}
else
{
backButton.Visibility = Visibility.Visible;
}
private void redSlider_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
{
//
Color clr = Color.FromArgb(255,(byte)redSlider.Value,(byte)greenSlider.Value,(byte)blueSlider.Value);
//将三种颜色的混合色设置为ellipse1的填充颜色
ellipse1.Fill = new SolidColorBrush(clr);
textBlockColor.Text = clr.ToString();
redText.Text = clr.R.ToString("X2");
greenText.Text = clr.G.ToString("X2");
blueText.Text = clr.B.ToString("X2");
}

4.12 时间选择器和日期选择器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 <StackPanel Grid.Row="2">
<TimePicker x:Name="time" Header="请选择时间:" TimeChanged="time_TimeChanged"/>
<DatePicker x:Name="date" Header="请选择日期:"
DateChanged="date_DateChanged"/>
<TextBlock x:Name="info" FontSize="20" TextWrapping="Wrap"/>
</StackPanel>
///DatePicker TimePicker
//控件默认的时间和日期是设备当前的时间和日期
info.Text = $"时间:{time.Time.ToString()} 日期:{date.Date.ToString()}";
private void time_TimeChanged(object sender, TimePickerValueChangedEventArgs e)
{
info.Text = $"时间改变为:{time.Time} 日期:{date.Date}";
}
/// <summary>
///
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void date_DateChanged(object sender, DatePickerValueChangedEventArgs e)
{
info.Text = $"时间:{time.Time} 日期改变为:{date.Date}";
}

4.13 枢轴控件(Pivot)
枢轴控件Pivot提供了一种快捷的方式来管理应用中的视图或页面,通过一种类似页签的方式来将视图分类,这样就可以在一个界面上通过切换页签来浏览多个数据集,或者切换应用视图.枢轴控件水平放置独立的视图,同时处理左侧和右侧的导航,在触摸屏上可以通过划动或者平移手势来切换枢轴控件中的视图,同时处理左侧和右侧的导航,在触摸屏上通过划动或平移手势来切换枢轴控件中的视图.语法如下

1
2
3
4
5
6
7
8
<Pivot>
<PivotItem>
<!--UI内容-->
</PivotItem>
<PivotItem>
<!--UI内容-->
</PivotItem>
</Pivot>

Pivot分两部分 标题 内容
1.标题 通过Title设置 PivotItem Header 富文本信息
2.内容 SelectionChanged SelectedIndex SelectedItem 可以获取到当前选中的页签索引和对象
4.14 全景视图控件(Hub)
全景视图控件Hub是Win10中独特的视图控件,给用户提供一种纵向拉伸延长的效果,提供了独特显示控件,数据,服务的方式.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<Hub>
<HubSection>
<HubSection.ContentTemplate>
<DateTemplate>
<!--UI内容-->
</DateTemplate>
</HubSection.ContentTemplate>
</hubSection>
<HubSection Header="second item">
<HubSection.ContentTemplate>
<DataTemplate>
<!--UI内容-->
</DataTemplate>
</HubSection.ContentTemplate>
</Hub>

Hub控件和Pivot控件是同一类型的控件,都是使用页签的方式来组织页面的内容,不过这两个控件的交互效果却不太一样
在使用Hub控件时通常会关注Hub控件的三个部分,背景,全景背景,全景区域.
背景是Hub控件包装下的整个应用程序的背景,位于全景应用的最底层,这个背景会铺满整个设备屏幕.
背景通常是一张全景图,他可能是应用程序最直观的部分,也可以不设置.全景标题与全景区域跟Pivot控件的作用类似.

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
<!--在大标题上添加程序的图标-->            
<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>

4.15 浮出控件(Flyout)
浮出控件(Flyout)是一个轻型的辅助型的弹出控件,通常作为提示或者要求用户进行相关的交互来使用.
Flyout控件与Win10里面的弹出框MessageDialog有很大区别.Flyout控件是一个辅助控件,需要与其他控件组合起来才能使用.其次,取消的规则不一样,Flyout控件收集用户输入,显示与某个项目相关的更多信息或者要求用户确认某个操作,只有当为了相应用户点击时才应显示Flyout控件,也就是说Flyout控件并不是直接显示出来,而是需要用户操作才能呈现出来,当用户在弹出窗口外部点击时,Flyout控件就会消失,这也是Flyout控件默认的关闭规则.

1
2
3
4
5
6
7
<Button>
<Button.Flyout>
<Flyout>
<!--浮出的UI内容-->
</Flyout>
</Button.Flyout>
</Button>

非Button控件也一样可以使用Flyout控件,也可以使用FlyoutBase.AttachedFlyout附加属性,将Flyout控件附加到任何FrameworkElement对象.因为Flyout控件必须要响应相应某个用户的操作,Button控件默认关联到了Clcik事件,如果用FlyoutBase.AttachedFlyout附加属性来添加Flyout控件就必须相应FrameworkElement控件上的交互,并在代码中打开Flyout控件

1
2
3
4
5
6
7
8
9
10
11
12
13
<TextBlock text="Tapped事件触发Flyout" Tapped="Textblock_Tapped">
<FlyoutBase.AttachedFlyout>
<Flyout>
<!--浮出的UI内容-->
</Flyout>
</FlyoutBase.AttachedFlyout>
</TextBlock>
private void TextBlock_Tapped(object sender,TappedRoutedEventArgs e){
FrameworkElement element=sender as FrameworkElement;
if(element!=null){
FlyoutBase.ShowAttachedFlyout(element);
}
}

Flyout有两种创建方式,一种方式 Button Flyout 添加 2.通过FlyoutBase.AttachedFlyout附加属性给任何FrameworkElement对象来添加,在Win10上Flyout控件一共有6种不同的类型,Flyout,DatePickerFlyout,TimePickerFlyout,PickerFlyout,ListPickerFlyout和MenuFlyout.
1.Flyout
Flyout类型表示处理自定义浮出窗口,Flyout控件经常使用的事件是Closed,Opened和Opening,分别表示在关闭,已打开,正在打开,在实际种在Closed事件获取用户操作结果.这三种事件也是其他共性
2.DatePickerFlyout TimerPickerFlyout
3,PickerFlyout ListPickerFlyout
PickerFlyout表示选择的浮出窗口,可以在页面底下添加确认的菜单栏用于用户确认;
ListPickerFlyout表示列表形式展示的浮出窗口,需要通过集合数据绑定来呈现列表的选择.
与其他区别是提供了选中确认的时间,分别是PickerFlyout对应的Confirmed事件和ListPickerFlyout对应的ItemsPicked事件.ListPickerFlyout需要通过数据绑定来实现选择的列表
4.MenuFlyout
MenuFlyout表示上下文菜单的选择浮出窗口,一个MenuFlyout会包含若干个MenuFlyoutItem.
每个MenuFlyoutItem表示一个选项,用户可以单击,同时通过MenuFlyoutItem的Click事件来处理单击请求.

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
 <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>

//Flyout
//绑定ListPickerFlyout数据源
listPickerFlyout.ItemsSource = new List<string> { "Apple", "Honor", "RedMi", "TSD","HTC" };
private void MenuFlyoutItem_Click(object sender, RoutedEventArgs e)
{
menuFlyoutButton.Content = (sender as MenuFlyoutItem).Text;
}
//ListPickerFlyout的选中事件,点击列表的某一项便会触发,在事件处理程序中通常会获取选中的项目来进行相关逻辑的处理.
private async void listPickerFlyout_ItemsPicked(ListPickerFlyout sender, ItemsPickedEventArgs args)
{
if (sender.SelectedItem!=null)
{
await new MessageDialog($"你选择的是:{sender.SelectedItem.ToString()}").ShowAsync();
}
}

private async void DatePickerFlyout_DatePicked(DatePickerFlyout sender, DatePickedEventArgs args)
{
await new MessageDialog(args.NewDate.ToString()).ShowAsync();
}
// TimerPickerFlyout 时间选中事件,在事件处理程序里可以获取选中的时间
private async void TimePickerFlyout_TimePicked(TimePickerFlyout sender, TimePickedEventArgs args)
{
await new MessageDialog(args.NewTime.ToString()).ShowAsync();
}

private async void PickerFlyout_Confirmed(PickerFlyout sender, PickerConfirmedEventArgs args)
{
await new MessageDialog("你点击了确定").ShowAsync();
}
// 通过Flyoutase.ShowAttachedFlyout方法来展示Flyout控件
private void TextBlock_Tapped(object sender, TappedRoutedEventArgs e)
{
FrameworkElement element= sender as FrameworkElement;
if (element!=null)
{
FlyoutBase.ShowAttachedFlyout(element);
}
}

4.16 ComboBox
对ComboBox控件下拉数据进行绑定,可以通过ItemTemplate设置选项的模板和绑定的属性,然后再绑定数据源.
常用事件:DropDownOpened(下拉框打开时触发的事件) DropDownClosed(下拉框关闭时触发的事件)
再选中选项后关闭菜单,获取选中选项.

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
<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>

private void comboBox2_DropDownClosed(object sender, object e)
{
if (comboBox2.SelectedItem!=null)
{
Man man = comboBox2.SelectedItem as Man;
Info.Text = $"name:{man.Name} age:{man.Age}";
}
}

4.17 命令栏/菜单栏(CommandBar)
命令栏(CommandBar)也是程序中的菜单栏控件,win10中可以作为底部的菜单栏来使用.
Page页面的BottomAppBar属性表示页面的底部菜单栏,这个菜单栏必须是CommandBar控件类型,所以CommandBar控件通常都用在页面的底部菜单栏上,菜单栏有两种类型的按钮:一种是图形的图形按钮,另一种是文字的菜单按钮,这两种按钮都是使用AppBarButton控件来表示,只不过文字的菜单按钮需要放在CommandBar控件的SecondaryCommands属性上.

1
2
3
4
5
6
7
8
9
<Page.BottomAppBar>
<CommandBar>
<AppBarButton/>

<CommandBar.SecondaryCommands>
<AppBarButton/>
</CommandBar.SecondaryCommands>
</CommandBar>
</Page.BottomAppBar>

对于CommandBar里面的应用栏按钮控件AppBarButton,通常需要设置其Label和Icon属性用于定义应用栏按钮的内容,除此之外,AppBarButton控件可以作为一种图形的图形按钮单独使用,Label属性属于设置按钮的文本标签,Icon属性用于设置按钮的图形,图形的图标可以使用以下4种类型:
1.FontIcon:图标基于来自指定字体系列的字型
2.BitmapIcon:图标基于带指定URI的位图图像文件
3.PathIcon:图标基于Path数据.
4.SymbolIcon:图标基于来自Segoe UI Symbol字体的字型预定义列表.

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
<Page.BottomAppBar>
<CommandBar Opened="CommandBar_Opened" Closed="CommandBar_Closed">
<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="&#x03a3;"/>
</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>

private void CommandBar_Opened(object sender, object e)
{
info.Text = "The CommmandBar is Opened";
}

private void CommandBar_Closed(object sender, object e)
{
info.Text = "The CommandBar is Closed";
}

private void AppBarButton_Click(object sender, RoutedEventArgs e)
{
info.Text = $"单击了菜单栏:{(sender as AppBarButton).Label}";
}

4.18 分屏控件(SPlitView)
SplitView是Win10通用应用程序推荐的交互控件,通常和一个汉堡按钮搭配作为一种抽屉式菜单来进行呈现.

1
2
3
4
5
6
<SplitView>
<SplitView.Pane>
...菜单面板的内容
</SplitView.Pane>
...主体内容
</SplitView>

菜单面板是通过Pane属性来进行赋值,并且通过IsPaneOpen属性来控制打开和关闭状态.true表示打开,false表示关闭,当菜单关闭时,页面将全部显示SplitView的主体内容,主题内容为Content属性

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
<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="&#xE700;"/>

</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>
private void ToggleButton_Click(object sender, RoutedEventArgs e)
{
Splitter.IsPaneOpen = (Splitter.IsPaneOpen == true) ? true : false;
}

private void Button_Click_1(object sender, RoutedEventArgs e)
{
Splitter.IsPaneOpen = false;
tb.Text = $"你好,{(sender as Button).Content}";
}

布局控件

5.14 Canvas

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<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>

5.15. 相对布局(RelativePanel)
控件的位置是按照相对位置来计算的,后一个控件在什么位置依赖于前一个控件的基本位置,是最灵活的一种布局.
RelativePanel也是通过附加属性来控制控件的布局,类似于Canvas的left.Top等.
Relative主要附加属性如下所示
Above:在某元素的上方
Belo:在某元素的下方
LeftOf:在某元素的左方
RightOf:在某元素的右方
AlignBottmWith:本元素的下边缘和某元素的下边缘对齐.
AlignLeftWith:本元素的左边缘和某元素的左边缘对齐
AlignRightWith:本元素的右边缘和某元素的右边缘对齐.
AlignTopWith:本元素的上边缘和某元素的上边缘对齐.
AlignVerticalCenterWith:本元素和某元素的垂直居中对齐.
5.16 多分辨率的适配布局

1
2
3
4
5
6
7
8
9
10
<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>

5.16.多分辨率的适配布局
1.对于要自适应布局部分不要硬编码 设置HorizontalAlignment VerticalAlignment 设置为strenth.通过MaxWidth,MinWidth来控制长度范围.
2.利用Grid面板或者RelativePanel面板动态布局.
规则的 Grid 反之
3.直接根据分辨率进行适配
真正的长和宽 WIndows.UI.Xaml.Window.Current.Bounds类的Height和Width属性来获取.
4.使用AdaptiveTrigger进行适配
通过设置MinWindowHeight,MinWindowWidth来设置触发的时机,当最小的窗口高度或者宽度超过限制将会触发布局属性的修改.AdaptiveManager需要在VisualStateManger里面使用.并且设置触发某个状态的改变.
5.23.布局系统的重要方法和属性
1.UIElement类
关于布局,重要属性 DesiredSize RenderSize 重要方法 Measure Arrange.
DesiredSize:只读 Size类 表示在布局过程的测量处理过程中计算的大小
RenderSize:这是一个只读属性,类型是Size类,表示UI元素最终呈现大小,RenderSize和DesiredSize并不一定相等.RenderSize就是其ArrangeOverride方法的返回值.
public void Measure(Size availableSize)方法:更新UIElement的DesiredSize属性,测量出UI元素的大小.[如果在该元素上实现了FrameworkElement.MeasureOverride(System.Windows.Size)方法,将会用此方法以形成递归布局更新.参数availableSize表示父对象可以为子对象分配可用空间.子对象可以请求大于可用空间的空间,如果特定面板中允许滚动或其他调整大小行为,则提供的大小可以适应此空间.
public void Arrange(Rect finalRect):Arrange方法所做的事情是定位子对象并确定UIElement的大小,也就是DesiredSize属性的值,如果在该元素上实现了FrameworkElement,ArrangeOverride(System.Windows.Size)方法,将会用此方法以形成递归布局更新,参数finalRect表示布局中父对象为子对象计算的最终大小,作为System.WIndows.Rect值提供.
2.FrameworkElement类
FrameworkElement MeasureOverride ArrangeOverride
Protected virtual Size MeasureOverride(Size availableSize)
Protected virtual Size ArrangeOverride(Size finalSize)
5.24 测量和排列过程
1.测量的过程是为了确认DesiredSize的值,最终是要提供给排列的过程去使用.
2.DesiredSize是根据Margin,Width,Height等属性来决定.
3.排列的过程确定RenderSize,以及最终子对象被安置的空间.RenderSize就是ArrangeOverride的返回值,还没有被裁剪过的值.
4.Margin,Width,Height只是控件表面上的属性,而实际掌控这些效果的是布局的测量排列过程.
5.Margin,Width,Height等属性的改变会重新触发布局的过程.
5.3.自定义布局规则

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
public class TestPanel :Panel
{
public TestPanel()
{
}
protected override Size MeasureOverride(Size availableSize)
{
Debug.WriteLine($"进入父对象:{this.Name}的MeasureOverride方法测量大小.");
foreach (var item in this.Children)
{
item.Measure(new Size(120,120));
Debug.WriteLine($"子对象的DesiredSize值Width:{item.DesiredSize.Width} Height:{item.DesiredSize.Height}");
Debug.WriteLine($"子对象的RenderSize值Width:{item.RenderSize.Width} Height:{item.RenderSize.Height}");

}
Debug.WriteLine($"父对象的DesiredSize值Width:{this.DesiredSize.Width} Height:{this.DesiredSize.Height}");
Debug.WriteLine($"父对象的RenderSize值Width:{this.RenderSize.Width} Height:{this.RenderSize.Height}");
return availableSize;
}

protected override Size ArrangeOverride(Size finalSize)
{
Debug.WriteLine($"进入父对象:{this.Name}的ArrangeOverride方法进行排列");
double x = 0;
foreach (var item in this.Children)
{
//排列子元素
item.Arrange(new Rect(x,0,item.DesiredSize.Width,item.DesiredSize.Height));
x += item.DesiredSize.Width;
Debug.WriteLine($"子对象的DesiredSize值Width:{item.DesiredSize.Width} Height:{item.DesiredSize.Height}");
Debug.WriteLine($"子对象的RenderSize值Width:{item.RenderSize.Width} Height:{item.RenderSize.Height}");

}
Debug.WriteLine($"父对象的Desired值Width:{this.DesiredSize.Width} Height:{this.DesiredSize.Height}");
Debug.WriteLine($"父对象的Render值Width:{this.RenderSize.Width} Height:{this.RenderSize.Height}");
return base.ArrangeOverride(finalSize);
}
}

public class TestUIElement : Panel
{
public TestUIElement()
{

}
protected override Size MeasureOverride(Size availableSize)
{
Debug.WriteLine($"进入子对象:{this.Name}的MeasureOverride方法测量大小.");
return availableSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
Debug.WriteLine($"进入子对象:{this.Name}的ArrangeOverride方法进行排列");
return finalSize;
}

}

<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>

5.3.1.创建布局类
需要注意的是,自定义的半径属性发生改变的时候,需要调用InvalidateArrange方法重新触发布局的排列过程,否则修改半径后将不会起到任何作用.

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
public class CirclePanel:Panel
{
//自定义的半径变量
private double _radius = 0;

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(CirclePanel), new PropertyMetadata(0.0,OnRadiusPropertyChanged));

public CirclePanel()
{
}

private static void OnRadiusPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//获取触发属性改变的CirclePanel对象.
CirclePanel target = (CirclePanel)d;
//获取传递进来的最新的值,并赋值给半径变量
target._radius = (double)e.NewValue;
//使排列状态失效,进行重新排列
target.InvalidateArrange();
}

protected override Size MeasureOverride(Size availableSize)
{
//处理测量子对象的逻辑
//最大的宽度的变量
double maxElementWidth = 0;
//遍历所有的子对象,并调用子对象的Measure方法进行测量,取出最大的宽度的子对象.
foreach (var item in Children)
{
//测量子对象
item.Measure(availableSize);
maxElementWidth = Math.Max(item.DesiredSize.Width,maxElementWidth);
}
//取两个半径的大小和最大的宽度的两倍作为面板的宽度.
double panelWidth = 2 * this.Radius + 2 * maxElementWidth;
//取面板所分配的高度,宽度和计算出来的宽度的最小值作为面板的实际大小.
double width = Math.Min(panelWidth,availableSize.Width);
double height = Math.Min(panelWidth,availableSize.Height);
return new Size(width,height);
}

protected override Size ArrangeOverride(Size finalSize)
{
//当前的角度,从0开始排列
double degree = 0;
//计算每个子对象所占用的角度大小.
double degreeStep = (double)360 / this.Children.Count;
//计算
double mX = this.DesiredSize.Width / 2;
double mY = this.DesiredSize.Height / 2;
//遍历所有子对象进行排列
foreach (var item in Children)
{
//把角度转换为弧度单位
double angle = Math.PI * degree / 180.0;
//根据弧度计算出圆弧上的x,y的坐标值
double x = Math.Cos(angle) * this._radius;
double y = Math.Sin(angle) * this._radius;
//使用变换效果让控件旋转角度degree
RotateTransform rotateTransform = new RotateTransform();
rotateTransform.Angle = degree;
rotateTransform.CenterX = 0;
rotateTransform.CenterY = 0;
item.RenderTransform = rotateTransform;
//排列子对象
item.Arrange(new Rect(mX+x,mY+y,item.DesiredSize.Width,item.DesiredSize.Height));
//角度递增
degree += degreeStep;
}
return finalSize;
}


}

<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>

private void Slider_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
{
if (circlePanel!=null)
{
circlePanel.Radius = e.NewValue * 10;
}
}