如今的软件市场,竞争已经进入白热化阶段,功能强、运算快、界面友好、Bug少、价格低都已经成为了必备条件。这还不算完,随着计算机的多媒体功能越来越强,软件的界面是否色彩亮丽、是否能通过动画、3D等效果是否吸引用户的眼球也已经成为衡量软件的标准。

软件项目成功的三个要素是:资源、成本、时间。无论是为了在竞争中保持不败还是为了激发起用户对软件的兴趣,提高软件界面的美化程度、恰当的将动画和3D等效果引入应用程序都是一个必然趋势。然而使用传统的桌面应用程序开发工具和框架(如Winform、MFC、VB、Delphi等)进行开发时,为了使软件界面变漂亮、加入动画或者3D效果,边际成本会非常的高。体现在:

资源消耗增大:需要招聘懂得动画和3D编程的程序员,还需要更多的设计师、工薪和沟通成本随着上升。

开发时间增加:界面美化、动画和3D开发远远比业务逻辑开发困难、耗时。

成本增加:随着资源消耗的增加和开发周期的拉长,成本必然增加。

之所以会出现这种情况,根本原因在于传统开发工具和框架并没有原生的支持美化用户界面、向应用程序中添加动画和3D效果等功能。举个简单的例子,当用户提出需要把TextBox的外观改成圆角时,Winform和Delphi程序员只能通过派生新类并在底层做修改的方法来实现。类似的用户需求还有好多不得不实现,否则客户会怀疑我们的开发能力;即使实现了也没有什么额外的经济效益,因为这些东西在客户的眼里都是很简单的东西。

WPF的推出可谓是对症下药、专门解决上述问题。体现在:

XAML语言针对的是界面美化的问题,可以让设计师直接加入开发团队、降低沟通成本。

XAML的图形绘制功能非常强大,可以轻易绘出复杂的图标、图画。

WPF支持滤镜功能,可以像PhotoShop一样为对象添加各种效果。

WPF原生支持动画开发,无论是设计师还是程序员,都能够使用XAML或C#轻松开发制作绚丽的动画效果。

WPF原生支持3D效果,甚至可以将其它3D建模工具创建的模型导进来、为我所用。

Blend作为专门的设计工具让WPF如虎添翼,即能够帮助不了解编程的设计师快速上手,又能够帮助资深开发者快速建立图形或者动画的原型。

1.1   WPF绘图

与传统的.net开发使用GDI+进行绘图不同,WPF拥有自己的一套绘图API。使用这套API不但可以轻松绘制出精美的图形,还可以为各种图形添加类似与PhotoShop的“滤镜效果”及“变形效果”。本节我们就一起研究WPF图形API绘图,效果和变形等功能。

先观察下面一组图片:


显然,这组图片是矢量图(Vector Image),无论怎样放大缩小都不会出现锯齿。你可能会想:“这是组PNG格式的图片吗?”答案是“NO”。这组图是用XAML语言绘制的!XAML绘图本身就是矢量的,而且支持各式各样的填充和效果,甚至还可以添加滤镜,这些功能丝毫不亚于Photoshop。以前,使用PhotoShop制作出来的图形需要程序员使用.net的绘图接口进行二次转换才能应用到程序里,现在好了,直接把XAML代码拿来用就可以了。

绘图并不是VisualStudio的强项,这些漂亮的XAML矢量图是怎么画出来的呢?答案是借助Microsoft Expression Studio中的Blend和Design两个工具。Blend我们已经介绍过了,用它可以直接绘制XAML图形;Design可以像PhotoShop或者FireWorks那样绘制图形,再由设计者决定导出xaml格式还是png格式。虽然“唯代码派”的程序员们在Visualstudio里一行一行写代码也能把复杂的图形以非可视化的形式创建出来,但在Blend和Design中画出原型再在VisualStudio里面进行细节的修饰才是提高效率之道。

积沙成塔,集腋成裘,别看前面那些图片很复杂,但都是由几个有限的基本图形组成的。WPF的基本图形包括以下几个(它们都是Shap类的派生类):

Line:直线段,可以设置其笔触(Stroke)。

Rectangle:矩形,既有笔触,又有填充(Fill)。

Ellipse:椭圆,长宽相等的椭圆即为正圆,既有笔触又有填充。

Polygon:多边形,由多条直线线段围成的闭合区域,既有笔触又有填充。

PolyLine:折线(不闭合),由多条首尾相接的直线组成。

Path:路径(闭合区域),基本图形中功能最强的一个,可由若干直线,圆弧,被塞尔曲线组成。

1   直线

直线是最简单的图形。使用X1,Y1两个属性值可以设置它的起点坐标,X2,Y2两个属性值可以设置它的终点坐标。控制终点/起点做标就可以实现平行,交错等效果。Stroke(笔触)属性的数据类型是Brush(画刷),凡是Brush的派生类均可以用于给这个属性赋值。因为WPF提供多种渐变色画刷,所以画直线也可以画出渐变效果。同时,Line的一些属性还可以帮助我们画出虚线以及控制线段终点的形状。下面的例子综合了这些属性:

<Window x:Class="WpfApplication1.Window45"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="Window45" Height="293" Width="437"><Grid><Line X1="10" Y1="20" X2="260" Y2="20" Stroke="Red" StrokeThickness="10"></Line><Line X1="10" Y1="40" X2="260" Y2="40" Stroke="Orange" StrokeThickness="6"></Line><Line X1="10" Y1="60" X2="260" Y2="60" Stroke="Green" StrokeThickness="3"></Line><Line X1="10" Y1="80" X2="260" Y2="80" Stroke="Purple" StrokeThickness="2"></Line><Line X1="10" Y1="100" X2="260" Y2="100" Stroke="Black" StrokeThickness="1"></Line><Line X1="10" Y1="120" X2="260" Y2="120" StrokeDashArray="3" Stroke="Black" StrokeThickness="1"></Line><Line X1="10" Y1="140" X2="260" Y2="140" StrokeDashArray="5" Stroke="Black" StrokeThickness="1"></Line><Line X1="10" X2="260" Y1="160" Y2="160" Stroke="Black" StrokeThickness="6" StrokeEndLineCap="Flat"></Line><Line X1="10" X2="260" Y1="180" Y2="180" Stroke="Black" StrokeThickness="8" StrokeEndLineCap="Triangle"></Line><Line X1="10" X2="260" Y1="200" Y2="200" StrokeEndLineCap="Round" StrokeThickness="10"><Line.Stroke><LinearGradientBrush EndPoint="0,0.5" StartPoint="1,0.5"><GradientStop Color="Blue"></GradientStop><GradientStop Offset="1" Color="Red"></GradientStop></LinearGradientBrush></Line.Stroke></Line></Grid>
</Window>
程序运行效果如下:


有一点需要特别注意,初学者认为绘图一定要在Canvas中完成(谁叫它的名字叫画布呢),其实不然,绘图可以在任何一种布局控件中完成,WPF会自动根据容器的不同计算图形的坐标,日常生活中,常用的绘图容器有Canvas和Grid。

2      矩形

矩形有笔触(Stroke,即边线)和填充(Fill)构成。Stroke属性的设置和Line一样,Fill属性的数据类型是Brush。Brush是一个抽象类,所以我们不可能拿一个Brush类的实例为Fill属性赋值而只能用Brush派生类来进行赋值。WPF绘图系统中包含非常丰富的Brush类型,常用的有:

SolidColorBrush:实心画刷。在XAML中可以使用颜色名称字符串直接赋值。

LinearGradientBrush:线性渐变画刷。色彩沿设定的直线方向,按设定的变化点进行渐变。

RadialGradientBrush:径向渐变画刷。色彩沿半径的方向、按设定的变化点进行渐变,形成圆形填充。

ImageBrsh:使用图片作为填充类容。

DrawingBrush:使用矢量图(Vector)和位图(BitMap)作为填充内容。

VisualBrush:WPF中的每个控件都是有FrameWrokElement派生而来的,而FrameWorkElment类又是由Visual类派生而来的。Visual意为“可视”之意,每个控件的可视化形象就可以通过Visual类的方法获得。获得这个可视化形象之后,我们可以用这个形象进行填充,这就是VisualBrush。比如我想把窗体上的某个控件拖到另外一个位置,当鼠标松开之前需要在鼠标指针下显示一个幻影,这个幻影就是使用VisualBrush填充出来的一个矩形,并让矩形捕捉鼠标的位置、随鼠标移动。

下面是使用不同画刷填充矩形的综合实例:

<Window x:Class="WpfApplication1.Window46"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="Window46" Height="390" Width="600"><Grid><Grid.ColumnDefinitions><ColumnDefinition Width="180" /><ColumnDefinition Width="10" /><ColumnDefinition Width="180" /><ColumnDefinition Width="10" /><ColumnDefinition Width="180*" /></Grid.ColumnDefinitions><Grid.RowDefinitions><RowDefinition Height="160" /><RowDefinition Height="10" /><RowDefinition Height="160" /></Grid.RowDefinitions><!--实心填充--><Rectangle Grid.Row="0" Grid.Column="0" Stroke="Black" Fill="LightBlue"></Rectangle><!--线性渐变--><Rectangle Grid.Row="0" Grid.Column="2"><Rectangle.Fill><LinearGradientBrush StartPoint="0,0" EndPoint="1,1"><GradientStop Color="#FFB6F8F1" Offset="0"></GradientStop><GradientStop Color="#FF0082BD" Offset="0.25"></GradientStop><GradientStop Color="#FF95DEFF" Offset="0.6"></GradientStop><GradientStop Color="#FF004F72" Offset="1"></GradientStop></LinearGradientBrush></Rectangle.Fill></Rectangle><!--径向渐变--><Rectangle Grid.Row="0" Grid.Column="4"><Rectangle.Fill><RadialGradientBrush><GradientStop Color="#FFB6F8F1" Offset="0"></GradientStop><GradientStop Color="#FF0082BD" Offset="0.25"></GradientStop><GradientStop Color="#FF95DEFF" Offset="0.75"></GradientStop><GradientStop Color="#FF004F72" Offset="1.5"></GradientStop></RadialGradientBrush></Rectangle.Fill></Rectangle><!--图片填充--><Rectangle Grid.Row="2" Grid.Column="0"><Rectangle.Fill><ImageBrush ImageSource="./01077_1.png" Viewport="0,0,0.3,0.3" TileMode="Tile"></ImageBrush></Rectangle.Fill></Rectangle><Rectangle Grid.Row="2" Grid.Column="2"><Rectangle.Fill><DrawingBrush Viewport="0,0,0.2,0.2" TileMode="Tile"><DrawingBrush.Drawing><GeometryDrawing Brush="LightBlue"><GeometryDrawing.Geometry><EllipseGeometry RadiusX="10" RadiusY="10"></EllipseGeometry></GeometryDrawing.Geometry></GeometryDrawing></DrawingBrush.Drawing></DrawingBrush></Rectangle.Fill></Rectangle><!--无填充,使用线性渐变填充边框--><Rectangle Grid.Row="2" Grid.Column="5" StrokeThickness="10"><Rectangle.Stroke><LinearGradientBrush StartPoint="0,0" EndPoint="1,1"><GradientStop Color="White" Offset="0.3"></GradientStop><GradientStop Color="Blue" Offset="1"></GradientStop></LinearGradientBrush></Rectangle.Stroke></Rectangle></Grid>
</Window>

运行效果如下图:


使用画刷的时候,建议先在Blend里面绘制图大致的效果然后再在Visual Studio里面微调。

接下来让我们看一个VisualBrush的例子。为了简单起见,目标控件是一个Button,实际工作中换成复杂的控件也一样。程序的XAML代码如下:

<Window x:Class="WpfApplication1.Window47"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="Window47" Height="300" Width="400" Background="Orange"><Grid Margin="10"><Grid.ColumnDefinitions><ColumnDefinition Width="160" /><ColumnDefinition Width="*" /><ColumnDefinition Width="160" /></Grid.ColumnDefinitions><StackPanel Background="White" x:Name="spleft"><Button Height="40" Content="OK" x:Name="btnReal" Click="btnReal_Click"></Button></StackPanel><Button Grid.Column="1" Content=">>" Margin="5,0"></Button><StackPanel Grid.Column="2" Background="White" x:Name="spRight"></StackPanel></Grid>
</Window>

Button的事件处理器代码如下:

        double o = 1;//不透明度指数private void btnReal_Click(object sender, RoutedEventArgs e){VisualBrush vb = new VisualBrush(this.btnReal);Rectangle rtg = new Rectangle();rtg.Width = btnReal.Width;rtg.Height = btnReal.Height;rtg.Fill = vb;rtg.Opacity = o;o -= 0.2;this.spRight.Children.Add(rtg);}

运行效果如下图:


3.       椭圆

椭圆也是一种常见的几何图形,它的使用方法和矩形没有什么区别。下面的例子是绘制一个球体,球体的轮廓是正圆(Circle),Width和Height相等的椭圆即为正圆:球体的光影使用径向渐变实现,XAML代码如下:

<Window x:Class="WpfApplication1.Window48"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="Window48" Height="300" Width="300"><Grid><Ellipse Height="140"  Name="ellipse1" Stroke="Gray" Width="140" Cursor="Hand" ToolTip="A Ball"><Ellipse.Fill><RadialGradientBrush GradientOrigin="0.2,0.8" RadiusX="0.75" RadiusY="0.75"><RadialGradientBrush.RelativeTransform><TransformGroup><RotateTransform Angle="90" CenterX="0.5" CenterY="0.5"></RotateTransform></TransformGroup></RadialGradientBrush.RelativeTransform><GradientStop Color="#FFFFFFFF" Offset="0" /><GradientStop Color="#FF444444" Offset="0.66" /><GradientStop Color="#FF999999" Offset="1" /></RadialGradientBrush></Ellipse.Fill></Ellipse></Grid>
</Window>
运行效果如下图:


与前面提到的一样,椭圆的绘制和色彩填充在Blend里面完成的,在VS里面又做了一些相应的调整。


4      路径

路径(Path)可以说是WPF绘图最强大的工具,一来是因为它完全可以替代其它几种图形,而来它可以将直线,圆弧,贝塞尔曲线等基本元素组合起来,形成更复杂的图形。路径最重要的一个属性就是Data,Data的数据类型是Geometry(几何图形),我们正是使用这个属性将一些基本的线段拼接起来,形成复杂的图形。

为Data属性赋值的方法有两种:一种是标签式的标准语法,另外一种是专门用于绘制几何图形的“路径标记语法”。本小节我们使用标准标签语法认识各种线段,下一节我们将学习绘制几何图形的路径标记语法。

想要使用Path路径绘制图形,首先要知道几何图形数据是如何组合到Data属性中的。Path的Data属性是Geometry类,但是Geometry类是一个抽象类,所以我们不可能在XAML中直接使用<Geometry>标签。

        <!--不可能出现--><Path><Geometry><!----></Geometry></Path>

我们可以使用Geometry的子类。Geometry的子类包括:

LineGeometry:直线几何图形。

RectangleGeometry:矩形几何图形。

EllipseGeometry:椭圆几何图形。

PathGeometry:路径几何图形。

StreamGeometry:PathGeometry的轻量级替代品,不支持Binding、动画等效果。

CombinedGeometry:由多个基本几何图形关联在一起,形成的单一几何图形。

GeometryGroup:由多个基本几何图形组合在一起,形成的几何图形组。

可能让大家比较迷惑的是:前面已经见过Line,Rectangle,Ellipse等类,怎么现在又出来了LineGeometry、RectangleGeometry、EllipseGeometry类呢?它们的区别在于前面介绍的Line,Rectangle,Ellipse都是可以独立存在的对象,而这些*Geometry类只能用于结合成其它几何图形、不能独立存在-----当我们在Blend里面选中一组独立的几何图形并在菜单里执行组合路径命令时,本质上就是把原来独立的Line,Rectangle,Ellipse对象转换成了*Geometry对象并结合成一个新的复杂几何图形。

回到Data的Path属性,下面这个例子简要的展示了各种几何图形:

<Window x:Class="WpfApplication1.Window49"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="Window49" Height="350" Width="340"><Grid><Grid.RowDefinitions><RowDefinition Height="160" /><RowDefinition Height="160" /></Grid.RowDefinitions><Grid.ColumnDefinitions><ColumnDefinition Width="160" /><ColumnDefinition Width="160" /></Grid.ColumnDefinitions><!--直线--><Path Stroke="Blue" StrokeThickness="2" Grid.Row="0" Grid.Column="0"><Path.Data><LineGeometry StartPoint="0,0" EndPoint="160,160"></LineGeometry></Path.Data></Path><!--矩形路径--><Path Stroke="Orange" Fill="Yellow" Grid.Row="0" Grid.Column="1"><Path.Data><RectangleGeometry Rect="20,20,120,120" RadiusX="10" RadiusY="10"></RectangleGeometry></Path.Data></Path><!--椭圆路径--><Path Stroke="Green" Fill="LawnGreen" Grid.Column="0" Grid.Row="1"><Path.Data><EllipseGeometry Center="80,80" RadiusX="60" RadiusY="40"></EllipseGeometry></Path.Data></Path><!--自定义路径--><Path Stroke="Yellow" Fill="Orange" Grid.Row="1" Grid.Column="1"><Path.Data><PathGeometry><PathGeometry.Figures><PathFigure StartPoint="25,140" IsClosed="True"><PathFigure.Segments><LineSegment Point="20,40"></LineSegment><LineSegment Point="40,110"></LineSegment><LineSegment Point="50,20"></LineSegment><LineSegment Point="80,110"></LineSegment><LineSegment Point="110,20"></LineSegment><LineSegment Point="120,110"></LineSegment><LineSegment Point="140,40"></LineSegment><LineSegment Point="135,140"></LineSegment></PathFigure.Segments></PathFigure></PathGeometry.Figures></PathGeometry></Path.Data></Path></Grid>
</Window>

运行效果如下图:


其实LineGeometry、RectangleGeometry、EllipseGeometry都比较简单,现在着重来看PathGeometry。可以说,WPF绘图的重点是Path,Path的重点在于PathGeometry。PathGeometry之所以这么重要的原因是因为Path的Figures属性可以容纳PathFigure对象,而PathFigure对象的Segments属性又可以容纳各种线段用来组合成复杂的图形。XAML代码结构如下:

        <Path><Path.Data><PathGeometry><PathGeometry.Figures><PathFigure><PathFigure.Segments><!--线段内容--></PathFigure.Segments></PathFigure></PathGeometry.Figures></PathGeometry></Path.Data></Path>
因为Figures是PathGeometry的默认内容属性、Segments是PathFigure的默认内容属性,所以常简化为这样:

        <Path><Path.Data><PathGeometry><PathFigure><!--线段内容--></PathFigure></PathGeometry></Path.Data></Path>
了解了上面两个格式之后,我们可以把眼光集中在各种线段上,它们是:

LineSegment:直线段。

ArcSegment:圆弧线段。

BezierSegment:三次方贝塞尔曲线段(默认的贝塞尔曲线指的就是三次方贝塞尔曲线,所以Cubic一词被省略)。

QuadraticBezierSegment:二次方贝塞尔曲线段。

PolyLineSegment:多直线段。

PolyBezierSegment:多三次方贝塞尔曲线段。

PolyQuadraticBezierSegment:多二次方贝塞尔曲线段。

在绘制这些线段的时候需要注意,所有的这些线段多是没有起点的(StartPoint),因为起点就是前一个线段的终点,而第一个线段的起点则是PathFigure的StartPoint。请看下面这些例子:

LineSegment最为简单,只需要控制它的终点(Point)即可。

<Window x:Class="WpfApplication1.Window50"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="Window50" Height="307" Width="384"><Grid VerticalAlignment="Center" HorizontalAlignment="Center"><Path Stroke="Green" Fill="LawnGreen" StrokeThickness="2"><Path.Data><PathGeometry><PathFigure StartPoint="0,0" IsClosed="True"><LineSegment Point="150,0"></LineSegment><LineSegment Point="150,30"></LineSegment><LineSegment Point="90,30"></LineSegment><LineSegment Point="90,150"></LineSegment><LineSegment Point="60,150"></LineSegment><LineSegment Point="60,30"></LineSegment><LineSegment Point="0,30"></LineSegment></PathFigure></PathGeometry></Path.Data></Path></Grid>
</Window>
运行效果如下图:

ArcSegment用来绘制圆弧。point属性用来指明圆弧连接的终点;圆弧截取至椭圆,SIZE属性即是完整椭圆的横轴和纵轴半径,SweepDirection属性指明圆弧是顺时针方向还是逆时针方向;如果椭圆上的两个点位置不对称,那么这两点间的圆弧就会分为大弧和小弧,IsLargeArc属性用于指明是否使用大弧去连接;RotationAngle属性用来指明圆弧母椭圆的旋转角度,如下图所示是对几个属性的变化做出的详细对比:


BezierSegment(三次方贝塞尔曲线)由4个点决定:

(1)起点:即前一个线段的终点或PathFigure的StartPoint。

(2)终点:Point3属性,即曲线的终点位置。

(3)两个控制点:Point1和Point2属性。

初略的说,三次方贝塞尔曲线就是由起点出发走向Point1方向,再走向Point2方向,最后到达终点的平滑曲线,具体的算法请查阅维基百科“被塞尔曲线”词条。

如下代码是XAML代码表示的三次方贝塞尔曲线:

<Window x:Class="WpfApplication1.Window51"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="Window51" Height="300" Width="300"><Grid><Path Stroke="Black" StrokeThickness="2"><Path.Data><PathGeometry><PathFigure StartPoint="0,0"><BezierSegment Point1="250,0" Point2="50,200" Point3="300,200"></BezierSegment></PathFigure></PathGeometry></Path.Data></Path></Grid>
</Window>
运行效果如下图:


QuadraticBezierSegment(二次方贝塞尔曲线)与BezierSegment类似,只是控制点由两个变为了一个。也就是说QuadraticBezierSegment由3个点决定:

(1)起点:即前一个线段的终点或PathFigure的StartPoint。

(2)终点:Point2属性,即曲线的终止位置。

(3)控制点:Point1属性。

如下的代码就表示的是二次方贝塞尔曲线:

<Window x:Class="WpfApplication1.Window52"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="Window52" Height="339" Width="325"><Grid><Path Stroke="Blue" StrokeThickness="2"><Path.Data><PathGeometry><PathFigure StartPoint="0,300"><QuadraticBezierSegment Point1="150,-150" Point2="300,300"></QuadraticBezierSegment></PathFigure></PathGeometry></Path.Data></Path></Grid>
</Window>

运行效果如下图:


至此,简单的路径就介绍完了。如果想绘制出复杂的图形来,我们要做的仅仅是在PathFigure把Segment一段段的加上去。

GeometryGroup也是Geometry的一个派生类,它最大的特点是可以将一组PathGeometry组合在一起,如下面的例子:

<Window x:Class="WpfApplication1.Window53"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="Window53" Height="300" Width="300"><Grid><Path Stroke="Black" Fill="LightBlue" StrokeThickness="1"><Path.Data><GeometryGroup><PathGeometry><PathFigure StartPoint="0,0"><BezierSegment Point1="250,0" Point2="50,200" Point3="300,200"></BezierSegment></PathFigure></PathGeometry><PathGeometry><PathFigure StartPoint="0,0"><BezierSegment Point1="230,0" Point2="50,200" Point3="300,200"></BezierSegment></PathFigure></PathGeometry><PathGeometry><PathFigure StartPoint="0,0"><BezierSegment Point1="210,0" Point2="50,200" Point3="300,200"></BezierSegment></PathFigure></PathGeometry><PathGeometry><PathFigure StartPoint="0,0"><BezierSegment Point1="190,0" Point2="50,200" Point3="300,200"></BezierSegment></PathFigure></PathGeometry><PathGeometry><PathFigure StartPoint="0,0"><BezierSegment Point1="170,0" Point2="50,200" Point3="300,200"></BezierSegment></PathFigure></PathGeometry><PathGeometry><PathFigure StartPoint="0,0"><BezierSegment Point1="150,0" Point2="50,200" Point3="300,200"></BezierSegment></PathFigure></PathGeometry><PathGeometry><PathFigure StartPoint="0,0"><BezierSegment Point1="130,0" Point2="50,200" Point3="300,200"></BezierSegment></PathFigure></PathGeometry></GeometryGroup></Path.Data></Path></Grid>
</Window>
运行效果如下图:


5        路径标记语法

Path是如此强大,可以让我们随心所欲的绘制图形,然而它的一大缺点是不容忽视的,那就是其标签语法的繁琐。一般情况下,复杂图形(Path)是由数10条线段连接而成,按照标签式语法,每条线段是一个标签(Segment)、每个标签占据一行,一个图形就要占几十行代码。而这仅仅是一个图形,要组成一个完整的图画又往往需要10几个图形组合在一起,有可能占据数百行代码!幸好这种事情没有发生,因为我们可以借助专供WPF绘图使用的路径标记语法(Path MarkUp Syntax)来极大的简化Path的描述。

路径标记语法实际上就是各种线段的简记法,比如<LineSegment  point="150,5"/>可以简写为"L 150,5",这个L就是路径标记语法中的一个绘图命令。不仅如此,路径标记语法还增加了一些更实用的绘图命令,比如H用来绘制水平线,“H  180”就是指从当前点画一条水平直线,终点的横坐标是180(你不需要考虑纵坐标,纵坐标和当前点一致)。类似的还有V命令,用来画竖直直线。

使用路径标记语法绘图一般分三步:移动至起点---绘图----闭合图形。这三步使用的命令稍有区别。移动到起点使用的移动命令M,绘图使用的是绘图命令,包括:L,H,V,A,C,Q等,下面会逐一介绍;如果图形是闭合的,需要使用闭合命令Z,这样最后一条线段的终点与第一条线段的起点间就会连接上一条直线段。

路径标记语法是不区分大小写的,所以A和a,H和h是等价的。在路径标记语法中使用两个Double类型的数值来表示一个点,第一个值表示的是横坐标(记做X),第二个值表示纵坐标(记作y),两个数字可以使用逗号分割(x,y)又可以使用空格分割(x y)。由于路径标记语法中使用的空格作为两个点之间的分割,为了避免混淆,建议使用逗号作为点横纵坐标的分隔符。

如下图所示是常用的路径标记语法的总结:



在上述的命令中,S和T两个命令比较特殊。S用于绘制平滑的赛贝尔曲线,但只需要给出一个控制点,这个控制点相当于普通赛贝尔曲线的第二个控制点,之所以第一个控制点省略不写是因为平滑三次方赛贝尔曲线会把前一条贝塞尔曲线的第二空控制点以起点为对称中心的对称点当作当作自己的第一个控制点(如果前面的线段不是贝塞尔曲线,则第一个控制点和起点相同)。例如,下面两条曲线是等价的:

        <Path Stroke="Red" Data="M 0,0 C 30,0 70,100 100,100 S 170,0 200,0"></Path><Path Stroke="Blue" Data="M 0,0 C 30,0 70,100 100,100 C 130,100 170,0 200,0"></Path>
与S相仿,T命令用于绘制平滑二次贝塞尔曲线,绘制的时候如果前面也是一条二次贝塞尔曲线的话,T命令会把前面的这段曲线的控制点以起点为对称中心的对称点当作自己的控制点(如果前面的线段不是二次贝塞尔曲线则控制点与起点相同)。下面两条曲线等价:

        <Path Stroke="Red" Data="M 0,200 Q 100,0 200,200 T 400,200"></Path><Path Stroke="Blue" Data="M 0,200 Q 100,0 200,200 Q 300,400 400,200"></Path>

现在我们就可以使用路径标记语法来绘图了!使用方法是吧这这些命令串起来、形成一个字符串,然后赋值给Path的Data属性。使用Blend绘图时,Blend会自动使用路径标记语法来记录数据而不是是用代码量巨大的标签式语法。

6     使用Path剪切界面元素

实际工作中经常会遇到制作不规则的窗体或者控件,WPF在这方面做了良好的支持,仅需使窗体和控件的Clip属性就可以轻松做到。

Clip属性被定义在UIEelment类中,因此,WPF窗体的所有控件、图形都具有这个属性。Clip属性的数据类型是Geometry,与Path的Data属性一致。因此,我们只需要按照需求制作好特殊形状的Path并把Path的Data属性值赋值给目标窗体、控件或者其它图形,对目标的剪切就算完成了。请看下面这个不规则窗体的例子。

<Window x:Class="WpfApplication1.Window56"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="Window56" Height="250" Width="300" WindowStyle="None" AllowsTransparency="True" WindowStartupLocation="CenterScreen" Background="Yellow"><Grid VerticalAlignment="Center" HorizontalAlignment="Center"><Path Stroke="Orange" Fill="Yellow" x:Name="clipPath0" Visibility="Hidden" Data="M 55,100 A 50,50 0 1 1 100,60 A 110,95 0 0 1 200,60 A 50,50 0 1 1 250,100 A 110,95 0 1 1 55,100 Z"></Path><Button Content="Clip" Width="80" Height="25" Click="Button_Click" HorizontalAlignment="Center" VerticalAlignment="Center"></Button></Grid>
</Window>

如果想让一个窗体可以被裁切,那么其AllowsTransparency必须要设置为True,这个属性设为True之后,WindowStyle必须要设置为None。

Button的事件处理器中代码如下:

        private void Button_Click(object sender, RoutedEventArgs e){this.Clip = clipPath0.Data;}

运行程序,单击按钮,运行效果如下图:

    

1.2    图形的效果和滤镜

以往,程序员们很头疼的一件事情就是要精确实现UI设计师们给出的样稿。有些时候程序员可以使用设计师提供的图片作为背景或者贴图,但这些图片往往都是位图而非矢量图,所以当应用了这些图片的窗体或控件的尺寸发生改变时,图片就会出现锯齿、马赛克或失真等情况。对于那些对用户体验要求比较高的软件程序员不能直接使用位图了,只能用代码来实现设计师的设计。要知道,设计师手里用的是PhotoShop,FireWorks这些专业的设计工具,加个阴影,生成个发光效果就是点点鼠标的事,同样的效果让程序员使用C#实现就没有那么容易了,有的时候甚至需要用到一些在游戏里面才会用到的技术和知识,这无疑增加了开发的难度和成本。

WPF的出现无疑是程序员的福音,因为不但像阴影,发光效果可以使用一两个属性来实现,就连通道、动态模糊这些高级的效果也可以轻松实现。同时,设计师和程序员还可以像为PhotoShop开发滤镜一样为WPF开发效果类库,届时只需要把类库引入到项目中就可以使用其中的效果了(微软官方网站和一些开源网站上已经有很多效果类库可供使用)。

在UIElement类的成员中你可以找到BitmapEffect和Effect这两个属性,这两个属性都是为UI元素添加效果。你可能会问:为做同一件事准备了两个属性,难道不冲突吗?答案是:的确冲突。WPF最早的版本里面只有BitmapEffect这个属性,这个属性使用CPU的运算能力为UI元素添加效果,这样做的问题就是效果一多或者让带有效果UI元素参与动画,程序的性能会因为CPU资源被大量占用而大幅降低(要么反应变慢,要么刷新或者动画变的很卡)。随后的版本中,微软决定转用显卡GPU的运算能力为UI元素添加效果,于是添加了Effect这个属性。这样即减少了对CPU的浪费又将应用程序的视觉效果拉平到与游戏程序一个级别。

因为有Effect这个属性替换BitmapEffect,所以你在MSDN文档里面看到BitmapEffect被标记为已过时,不过在WPF4.0中Bitmapeffect仍然可以使用,也就是说未来两三年里,bitmapEffect仍然可以使用。下面让我们尝试如何使用这两种效果:

1.2.1    简单易用的BitmapEffect

BitmapEffect定义在UIElement类中,它的数据类型是BitmapEffect类。BitmapEffect是一个抽象类,所以我们只能使用它的派生类来为UIElement的BitmapEffect属性赋值。BitmapEffect类的派生类并不多,包括以下几个:

BevelBitmapEffect:斜角效果。

BitmapEffectGroup:复合效果(可以把多个BitmapEffect组合在一起)。

BlurBitmapEffect:模糊效果。

DropShadowBitmapEffect:投影效果。

EmbossBitmapEffect:浮雕效果。

OuterGlowBitmapEffect:外发光效果。

每个效果都有自己的一系列属性来做调整,比如你可以调整投影效果的投影高度,阴影深度和角度,让用户感觉光是由某个角度投射下来;你也可以调整外发光效果的颜色和延展距离。下面一个DropShadowBitmapEffect的简单例子:

<Window x:Class="WpfApplication1.Window57"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="Window57" Height="300" Width="300"><Grid><Button Width="80" Height="50"><Button.BitmapEffect><DropShadowBitmapEffect Direction="-45" Opacity="0.75" Color="Red" ShadowDepth="7"></DropShadowBitmapEffect></Button.BitmapEffect></Button></Grid>
</Window>

运行效果如下图:



对于每个BitmapEffect的派生类MSDN都有相当详细的记述,请大家自行查阅。

1.2.2    丰富多彩的Effect

绘图软件Photoshop能够大获全胜的一个重要因素就是它的插件标准是公开的,这样,众多的第三方公司和人员就可以为它设计各种各样的插件、极大的丰富它的功能----众人拾柴火焰高。在众多的Photoshop插件中,最重要的一类就是滤镜,或者说是图片的效果。比如说我要把一张图片制作成老照片的效果,在没有滤镜的情况下,就需要手工调整很多图片属性才能做到,而使用滤镜,则只需要把滤镜加在图片上即可。显然使用滤镜效果可以获得如下好处:

  • 提高工作效率。
  • 得到更专业的效果。
  • 对使用者的技术水平要求相对较低。

WPF中引进了这种滤镜插件的思想,其成功就是UIElement类的Effect属性。Effect属性的类型是Effect类,Effect是抽象类,也就是说UIElement的Effect属性可以接收Effect类的任何一个派生类实例作为自己的值。Effect类位于System.Windows.Media.Effects名称空间中,它的派生类有3个,分别是:

  • BlurEffect:模糊效果。
  • DropShadowEffect:投影效果。
  • ShaderEffect:着色器效果(抽象类)。

你可能会想----强大的Effect派生类怎么还没有已经废弃的BitmapEffect类多呢?答案是这样的:因为模糊和投影在编程中使用的最多,所以.NET Framwork内建了这两个效果。这两个效果使用起来非常方便,而且请注意,这两个效果是使用GPU进行渲染,而不像BitmapEffect那样使用CPU渲染。ShaderEffect仍然是个抽象类,它就是流给滤镜开发人员的接口。只要你开发派生自ShaderEffect的效果类,别人就可以直接拿来用。

开发着色器效果需要使用Pixel Shader语言(简写和Photoshop一样,也是PS)和一些DirectX知识,超出了本书的范围。感兴趣的读者可以在微软的官网上找到它的SDK和开发文档。

对于大多数WPF开发人员来说,我们需要的是现成的滤镜效果,所以官方的滤镜包可以丛这里下载:http://wpffx.codeplex.com。解压下载的ZIP文件,可以看到分别为WPF和Silveright准备的两套滤镜。进入WPF文件夹,ShaderEffectLibrary文件夹里的项目就是效果库。效果库的使用方法如下:

首先到http://wpf.codeplex.com/releases/view/14962下载Shader Effect BuildTask And Templates.zip。解压ZIP文件之后按照其中的Readme文档进行安装,配置。这是着色器效果的编译/开发环境,没有它,着色器效果项目将不能被编译。如果你想开发自己的效果滤镜,也必须安装这个环境。检验安装是否成功的方法是启动VS,查看是否可以新建WPF Shader Effect Library项目,如下图所示:


新建一个WPF解决方案,把ShaderEffectLibrary中的项目添加进来,并为WPF项目添加对WPFShaderEffectLibrary项目的引用,你就可以使用效果库里面的效果了。


使用滤镜库,只需要设置几个属性,层次分明、动感十足的图片效果就出来了!这样的工作即可以由设计师来完成,也可以由程序员来完成。如果对效果不满意,直接在XAML文件里面修改并保存就可以了,而不必再像之前那样再用Photoshop等工具进行返工。同时,同一张原图片可以加载为不同的效果,也不必像之前一样先由设计师制作出多张图片再添加进应用程序,这样,程序的编译结果也会小很多。

1.3   图形的变形

当我们看到“变形”这个词时,首先会想起什么?拉长、挤扁?放大、缩小?还是... 变形金刚?其实WPF中的“变形“的含义很广,尺寸、位置、坐标系比例、旋转角度等的变化都算变形。

WPF中的变形是和UI元素分开的。举个例子,你可以设计一个”向左旋转45度“的变形,然后把这个变形赋值给不同的UI元素的变形控制属性,这些UI元素都会向左旋转45度了。这种将元素和变形控制属性分开的设计方案即减少了为UIElement类添加过多的属性,又提高了变形类实例的复用性,可谓一举两得。这种设计模式非常符合策略模式中的”有一个“比”是一个“更加灵活的思想。

控制编写的属性有两个,分别是:

RenderTransform:呈现变形,定义在UIElement类中。

LayoutTransform:布局变形,定义在FramworkElement类中。

因为FramworkElment类派生自UIElement类,而控件的基类Control类又派生自FramworkElment类中,所以在控件级别,你两个属性都可以看到。这两个属性都是依赖属性,它们的数据类型都是Transform抽象类,也就是说,Transform类的派生类均可为这两个属性赋值。

Transform抽象类的派生类有如下一些:

MatrixTransform:矩阵变形,把容纳被变形UI元素的矩形顶点看做是一个矩形进行变形。

RotateTransform:旋转变形,以给定的点为旋转中心,以角度为单位进行旋转变形。

ScaleTransform:坐标系变形,调整被变形元素的坐标系,可产生缩放效果。

SkewTransform:拉伸变形,可在横向和纵向上对被变形元素进行拉伸。

TranslateTransform:偏移变形,使被变形元素在横向或者纵向上偏移一个给定的值。

TransformGroup:变形组,可以把多个独立的变形合成为一个变形组、产生复合变形效果。

1.3.1      呈现变形

什么是呈现呢?相信大家都见过海市蜃楼吧!远远望去,远方的天空中漂浮着一座城市,而实际上那里没有城市,有的只是沙漠和海洋... ...,海市蜃楼形成的原因是密度不均的空气使光线产生折射,最终让人看到城市的影像呈现在本不应该出现的位置上---这就是城市影像的呈现出现了变形。WPF的RederTransform属性就是要起到这个作用,让UI元素呈现出来的属性与它本来的属性不一样!比如,一个按钮本来处于Canvas或者Grid的左上角,而我可以使用RenderTransform让它呈现在右下角并且旋转45°。

观察下面这个例子:

<Window x:Class="WpfApplication1.Window59"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="Window59" Height="334" Width="485"><Grid Margin="10" Background="AliceBlue"><Grid.ColumnDefinitions><ColumnDefinition Width="Auto"></ColumnDefinition><ColumnDefinition Width="*"></ColumnDefinition></Grid.ColumnDefinitions><Grid.RowDefinitions><RowDefinition Height="Auto"></RowDefinition><RowDefinition Height="*"></RowDefinition></Grid.RowDefinitions><Button Width="80" Height="80" Content="OK"><Button.RenderTransform><!--复合变形--><TransformGroup><!--旋转变形--><RotateTransform CenterX="40" CenterY="40" Angle="45"></RotateTransform><!--偏移变形--><TranslateTransform X="300" Y="150"></TranslateTransform></TransformGroup></Button.RenderTransform></Button></Grid>
</Window>

在布局Grid里,布局分为两行两列,并且第一行行高,第一列列宽都是由Button来决定的。同时,我为Button的RenderTransform设置了一个复合变形,使用Transform将一个偏移变形和一个旋转变形组合在了一起。偏移变形将Button的呈现(而不是Button本身)向右移动300像素,向下移动150像素;旋转变形将Button的呈现向右旋转45°。在窗体的设计器里面,我们可以清晰的看到Button的位置并没有改变(第一行和第一列并没有变化),但Button却出现在了右下(300,150)的位置,并向右旋转了45°。如下图所示:


运行效果如下图:


用户并不能觉察到究竟是控件本身的位置、角度发生了变化,还是呈现的位置发生了变化。

为什么需要呈现变形呢?答案是:为了效率!在窗体上移动UI元素本身会导致窗体布局的改变,而窗体的布局的每一个(哪怕是细微的)变化都将导致所有的窗体元素的尺寸测算函数,位置测算函数、呈现函数等的调用,造成系统资源占用激增、程序性能陡降。而使用呈现变形则不会遇到这样的问题,呈现变形值改变元素显示在哪里,所以不牵扯布局的变化、只涉及窗体的重绘。所以,当你需要制作动画的时候,请切记要使用RenderTransform。

1.3.2   布局变形

与呈现变形不同,布局变形会影响窗体的布局、导致窗体布局的重新测算。因为窗体布局的重新测算和绘制会影响程序的性能,所以布局变形一般只用在静态变形上,而不用于绘制动画。

考虑这样一个需求:制作一个文字纵向排列的浅蓝色标题栏。如果我们使用呈现变形,代码如下:

<Window x:Class="WpfApplication1.Window60"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="Window60" Height="338" Width="471"><Grid x:Name="titleBar" Background="LightBlue"><Grid.ColumnDefinitions><ColumnDefinition Width="Auto"></ColumnDefinition><ColumnDefinition Width="*"></ColumnDefinition></Grid.ColumnDefinitions><TextBlock FontSize="24" Text="Hello Transformer" VerticalAlignment="Bottom" HorizontalAlignment="Center"><TextBlock.RenderTransform><RotateTransform Angle="-90"></RotateTransform></TextBlock.RenderTransform></TextBlock></Grid>
</Window>
设计器中的效果如下:


尽管我们让显示文字的TextBlock“看起来”旋转了90°,但TextBlock本身并没有变化,改变的只是它的显示,所以,它的宽度仍然是吧宽度设为Auto的第一列撑的很宽。显然这不是我们希望看到的。

分析需求,我们实际需要的是静态改变TextBlock的布局,因此应该使用LayoutTransform。仅需要对上面的代码进行一处改动:

<Window x:Class="WpfApplication1.Window60"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="Window60" Height="338" Width="471"><Grid x:Name="titleBar" Background="LightBlue"><Grid.ColumnDefinitions><ColumnDefinition Width="Auto"></ColumnDefinition><ColumnDefinition Width="*"></ColumnDefinition></Grid.ColumnDefinitions><TextBlock FontSize="24" Text="Hello Transformer" VerticalAlignment="Bottom" HorizontalAlignment="Center"><TextBlock.LayoutTransform><RotateTransform Angle="-90"></RotateTransform></TextBlock.LayoutTransform></TextBlock></Grid>
</Window>

设计器中的效果如图所示:


1.4      动画

何为动画?动画自然就是“会动的画”。所谓“会动”不光指位置会移动,还包括角度的旋转、颜色的变化、透明度的增减等。细心的读者已经发现,动画本质就是在一个时间段里对象位置、角度、颜色、透明度等属性值的连续变化。这些属性中,有些是对象自身的属性,有些则是上一节所学的图形变形的属性。有一点需要注意,WPF规定,可以用来制作动画的属性必须是依赖属性。

变化即是运动。“没有脱离运动的物体,也没有脱离物体的运动”,唯物主义如是说。WPF的动画也是一种运动,这种运动的主体就是各种UI元素,这种运动本身就是施加在UI控件上的一些Timerline派生类的实例。在实际工作中,我们要做的事情往往就是先设计好一个动画构思、用一个Timerline派生类的实例加以表达,最后让某个UI元素来执行这个动画、完成动画与动画主体的结合。

简单的动画用一个元素来完成就可以了,就像一个演员的独角戏,WPF把简单动画称为AnimationTimeline。复杂的(即并行的,复合的)动画就需要UI上多个元素协同完成,就像电影中的一段场景。复杂动画的协同包括有哪些UI元素参与动画、每个元素的动画行为是什么、动画何时开始何时结束等。WPF把一组协同的动画也称做Storyboard。

Timeline、AnimationTimeline、Storyboard的关系如下图所示:


本节分两部分,先研究了如何设计简单独立的动画,再研究如何把简单的动画组合在一起形成场景。

1.4.1        简单独立动画

前面说过,动画就是“会动的画”,而这个会动指的是能够让UI元素变形的某个属性值产生了连续的变化。任何一个属性都有自己的数据类型,比如UIElement的Width和Height属性为Double类型,Window的Title属性为string类型。几乎针对每个可能的数据类型,WPF的动画子系统都为其准备了相应的动画类,这些动画类均派生自AnimationTimeline。它们包括:


上面列出的这些类都带有Base后缀,说明它们都是抽象基类。完整的情况下,这些抽象的基类又能派生出三种动画,即简单动画、关键帧动画、沿路径运动的动画。例如DoubleAnimationBase,它完整的派生出了3个具体的动画,如下图所示:


而针对Int类型的Int32AnimationBase只派生出了Int32Animation和Int32AnimationUsingKeyFrames两个具体的动画类。BooleanAnimationBase和CharAnimationBase的派生类则更少,只有关键帧动画类。

因为WPF动画系统中Double类型属性用的最多,而且DoubleAnimationBase的派生类也最完整,所以本节只讲述DoubleAnimationBase的派生类。学习完这个类,其它的动画类型亦可触类旁通。

1,简单的线性动画

所谓简单的线性动画,就是指仅有变化起点、变化终点、变化幅度、变化时间4个要素构成的动画。

变化时间(Duration属性):必须指定,数据类型是Duration。

变化终点(To属性):如果没有指定变化终点,程序将采用上一次的动画的终点或默认值。

变化起点(From属性):如果没有指定变化的起点则以变化目标属性的当前值为起点。

变化幅度(By属性):如果同时指定了变化终点,变化幅度将被忽略。

让我们分析一个例子,简单的XAML代码如下:

<Window x:Class="WpfApplication1.Window61"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="Window61" Height="372" Width="475"><Grid><Button Width="80" Height="80" HorizontalAlignment="Left" VerticalAlignment="Top"><Button.RenderTransform><TranslateTransform X="0" Y="0" x:Name="tt"></TranslateTransform></Button.RenderTransform></Button></Grid>
</Window>

用户界面上只包含了一个Button,这个Button的RederTransform属性值是一个名为tt的TranslateTransform对象,改变这个对象的X,Y值就会让Button的显示位置(而不是现在的真实位置)变化。Button的Click事件处理器代码如下:

        private void Button_Click(object sender, RoutedEventArgs e){DoubleAnimation dax = new DoubleAnimation();DoubleAnimation day = new DoubleAnimation();//指定起点dax.From = 0;day.From = 0;//指定终点Random rdm = new Random();dax.To = rdm.NextDouble() * 300;day.To = rdm.NextDouble() * 300;//指定时长Duration duration = new Duration(TimeSpan.FromMilliseconds(3000));dax.Duration = duration;day.Duration = duration;//动画主体是TranslatTransform变形,而非Buttonthis.tt.BeginAnimation(TranslateTransform.XProperty,dax);this.tt.BeginAnimation(TranslateTransform.YProperty,day);}
因为TranslateTransform的X,Y属性均为Double类型,所有我们选用DoubleAnimation来使之产生变化。代码中声明的dax、day两个DoubleAnimation变量并分别为之创建引用实例。接下来的代码依次为它们设置了起始值、终止值、变化时间。最后,调用BeginAnimation方法,让dax作用在TranslateTransform的XProperty属性上,让day作用在TranslateTransform的YProperty属性上。运行程序,每次单击按钮,按钮都会从起始位置(窗体的左上角)向窗体的右下角长宽不超过300像素的矩形内的某点运动,完成运动的时长为300毫秒。运行效果如下图:



这段代码有以下几处值得注意的地方:

  • 因为指定了daX和daY的起始值为0,所以每次按钮都会“跳”回窗体的左上角开始动画。如果想让按钮从当前位置开始下一次动画,只需要把“dax.From=0;”和"day.From=0"去掉即可。
  • 尽管表现出来的是button在移动,但DoubleAnimation的作用目标并不是Button而是TranslateTransform实例,因为TranslateTransform实例是Button的RenderTransform属性,所以Button“看上去”是移动了。
  • 前面说过,能用来制作动画效果的属性必须是依赖属性,TranslateTransform的XProperty和YProperty就是两个依赖属性值。
  • UIElement和Animation两个类都定义了BeginAnimation这个方法。TranslateTransform派生自Animation类,所以具有这个方法。这个方法的调用者就是动画要作用的目标对象,两个参数分别指明被作用的依赖属性(TranslateTransform.XProperty和TranslateTransform.YProperty)和设计好的动画(dax和day)。可以猜想,如果要动画改变Button的宽度和高度(这两个属性也是double类型),也应该首先创建DoubleAnimation实例,然后设置起至值和动画时间,最后调用Button的BeginAnimation方法,使用动画对象影响Button的WidthProperty和HeightProperty。

如果把事件处理器中的代码改成这样:

        private void Button_Click(object sender, RoutedEventArgs e){DoubleAnimation dax = new DoubleAnimation();DoubleAnimation day = new DoubleAnimation();//指定起点//dax.From = 0;//day.From = 0;//指定终点//Random rdm = new Random();//dax.To = rdm.NextDouble() * 300;//day.To = rdm.NextDouble() * 300;dax.By=100D;day.By = 100D;//指定时长Duration duration = new Duration(TimeSpan.FromMilliseconds(300));dax.Duration = duration;day.Duration = duration;//动画主体是TranslatTransform变形,而非Buttonthis.tt.BeginAnimation(TranslateTransform.XProperty,dax);this.tt.BeginAnimation(TranslateTransform.YProperty,day);}

运行的效果如下:


2     高级动画控制

使用From、To、By、Duration几个属性进行组合已经可以制作很多不同效果的动画了,然而WPF的动画系统的控制属性远远不止这些。如果想制作出更加复杂或逼真的动画,还需要使用如下一些效果:



对于这些属性,大家可以自己动手尝试---对它们进行组合往往可以产生很多意想不到的效果。

在这些属性中,EasingFunction是一个扩展性非常强的属性。它的取值类型是一个IEasingFunction接口类型,而WPF自带的IEasingFunction派生类就有10多种,每个派生类都能产生不同的结束效果。比如BounceEase可以产生乒乓球弹跳式效果,我们可以直接拿来使用而不必花精力亲自创作。

如果把前面的例子改成这样:

        private void Button_Click(object sender, RoutedEventArgs e){DoubleAnimation dax = new DoubleAnimation();DoubleAnimation day = new DoubleAnimation();//设置反弹BounceEase be = new BounceEase();//设置反弹次数为3be.Bounces = 3;be.Bounciness = 3;//弹性程度,值越大反弹越低day.EasingFunction = be;//设置终点dax.To = 300;day.To = 300;//指定时长Duration duration = new Duration(TimeSpan.FromMilliseconds(2000));dax.Duration = duration;day.Duration = duration;//动画主体是TranslatTransform变形,而非Buttonthis.tt.BeginAnimation(TranslateTransform.XProperty,dax);this.tt.BeginAnimation(TranslateTransform.YProperty,day);}

运行效果如下图:


3     关键帧动画

动画是UI元素属性连续发送变化产生的视觉效果。属性每次细微的变化都会产生一个新的画面,每个新画面就称为一帧,帧的连续播放就产生了动画效果。如同电影一样,单位时间内播放的帧数越多,动画的效果就会越细致。前面讲到的简单动画只设置了起点和终点,之间的动画帧都是由程序计算出来并绘制的,程序员无法进行控制。关键帧动画则允许程序员为一段动画设置几个“里程碑”,动画执行到里程碑所在的时间点时,被动画控制的属性值也必须达到设定的值,这些时间线上的“里程碑”就是关键帧。

思考这样一个需求:我想让一个Button用900毫秒的时间从左上角移动到右下角,但移动的路线不是直接走动而是走Z字形。如下图所示:


如果我们不知道有关键帧动画可用而只使用简单的动画,那么我们需要创建若干个简单的动画分别控制TranslateTransform的X和Y,比较棘手的是需要控制这些动画之间的协同。协同策略有两种,一种是靠时间来协同,也就是设置后执行动画的BeginTime以等待前面动画执行完毕,另一种是靠事件协同,也就是为先执行的动画添加Complated事件处理器,在事件处理器中开始下一段动画。因为是多个动画的协同,所以在动画需要改变的时候,代码的改动会比较大。

使用关键帧动画情况就会大有改观----我们只需要创建两个DoubleAnimationUsingKeyFrames实例,一个控制TranslateTransForm的X属性,另一个控制TranslateTransForm的Y属性即可。每个DoubleAnimationUsingKyeFrames各拥有3个关键帧用于指明X或Y在三个时间点(两个拐点和一个终点)应该达到什么样的值。

程序的XAML代码如下:

<Window x:Class="WpfApplication1.Window61"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="Window61" Height="372" Width="475"><Grid><Button Width="80" Height="80" HorizontalAlignment="Left" VerticalAlignment="Top" Click="Button_Click"><Button.RenderTransform><TranslateTransform X="0" Y="0" x:Name="tt"></TranslateTransform></Button.RenderTransform></Button></Grid>
</Window>

Button的Click事件处理器代码如下:

        private void Button_Click(object sender, RoutedEventArgs e){DoubleAnimationUsingKeyFrames dakX = new DoubleAnimationUsingKeyFrames();DoubleAnimationUsingKeyFrames dakY = new DoubleAnimationUsingKeyFrames();//设置动画总时长dakX.Duration = new Duration(TimeSpan.FromMilliseconds(900));dakY.Duration = new Duration(TimeSpan.FromMilliseconds(900));//创建,添加关键帧LinearDoubleKeyFrame x_kf_1 = new LinearDoubleKeyFrame();LinearDoubleKeyFrame x_kf_2 = new LinearDoubleKeyFrame();LinearDoubleKeyFrame x_kf_3 = new LinearDoubleKeyFrame();x_kf_1.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(300));x_kf_1.Value = 200;x_kf_2.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(600));x_kf_2.Value = 0;x_kf_3.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(900));x_kf_3.Value = 200;dakX.KeyFrames.Add(x_kf_1);dakX.KeyFrames.Add(x_kf_2);dakX.KeyFrames.Add(x_kf_3);LinearDoubleKeyFrame y_kf_1 = new LinearDoubleKeyFrame();LinearDoubleKeyFrame y_kf_2 = new LinearDoubleKeyFrame();LinearDoubleKeyFrame y_kf_3 = new LinearDoubleKeyFrame();y_kf_1.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(300));y_kf_1.Value = 0;y_kf_2.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(600));y_kf_2.Value = 180;y_kf_3.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(900));y_kf_3.Value = 180;dakY.KeyFrames.Add(y_kf_1);dakY.KeyFrames.Add(y_kf_2);dakY.KeyFrames.Add(y_kf_3);//执行动画tt.BeginAnimation(TranslateTransform.XProperty, dakX);tt.BeginAnimation(TranslateTransform.YProperty,dakY);}

在这组关键帧动画中,我们使用了最简单的关键帧LinearDoubleKeyFrame,这种关键帧的特点就是只需要你给定时间点(KeyTime属性)和到达时间点时的目标属性值(Value属性)动画就会让目标属性值在两个关键帧之间匀速运动。比如这两句代码:

            x_kf_1.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(300));x_kf_1.Value = 200;x_kf_2.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(600));x_kf_2.Value = 0;
x_kf_1关键帧处在时间线300毫秒处,目标属性值在这一时刻必须达到200(是什么属性这个时候并不知道,但要求这个属性值在这个时间一定要达到200),类似,x_kf_2在时间线的位置是600毫秒处,目标属性的值为0。当动画开始执行之后,程序会自动计算出目标属性在这两个关键帧之间的匀速变化。

前面的代码中,为关键帧的KeyTime属性使用的是KeyTime.FromTimeSpan静态方法,这样可以得到一个绝对的时间点。使用KeyTime.FromPercent静态方法则可以获得百分比计算的相对时间点,程序将整个关键帧的动画时长(Duration)视为100%。我们就可以把前面的代码改成这样:

            x_kf_1.KeyTime = KeyTime.FromPercent(0.33);x_kf_1.Value = 200;x_kf_2.KeyTime = KeyTime.FromPercent(0.66);x_kf_2.Value = 0;x_kf_3.KeyTime = KeyTime.FromPercent(1);x_kf_3.Value = 200;dakX.KeyFrames.Add(x_kf_1);dakX.KeyFrames.Add(x_kf_2);dakX.KeyFrames.Add(x_kf_3);
之后我们无论将dakX的Duration改为多少,3个关键帧都会将这个时间划分为均等的3段。

4      特殊关键帧

DoubleAnimationUsingKeyFrames的KeyFrames属性的数据类型是DoubleKeyFrameCollection,此集合类可接收的元素类型是DoubleKeyFrame。DoubleKeyFrame是一个抽象类,前面使用的LinearDoubleKeyFrame就是它的派生类之一。DoubleKeyFrame的所有派生类如下:

LinearDoubleKeyFrame:线性变化关键帧,目标属性值的变化是线性的、均匀的,即变化速率不变。

DisCreteDoubleKeyFrame:不连续变化关键帧,目标属性值变化是跳跃性的,跃迁的。

SplineDoubleKeyFrame:样条函数式变化帧,目标属性的变化值是一条贝塞尔曲线。

EasingDoubleKeyFrame:缓冲是变化关键帧,目标属性值以某种缓冲形式变化。

4个派生类中最常用的就是SplineDoubleKeyFrame(SplineDoubleKeyFrame可以替换LinearDoubleKeyFrame)。使用SplineDoubleKeyFrame可以方便的制作非匀速动画,因为它使用一条赛贝尔曲线来控制目标属性的变化速率。这条用于控制目标属性变化速率的贝塞尔曲线的起点是(0,0)和(1,1),分别映射着目标属性的变化起点和变化终点,意思是目标属性由0%变化到100%。这条贝塞尔曲线有两个控制点----ControlPoint1和ControlPoint2,意思是贝塞尔曲线从起点出发先想ControlPoint1移动、再向ControlPoint2移动,最后到达终点,形成一条平滑的曲线。如果设置ControlPoint1和ControlPoint2的横坐标值相等,比如(0,0)、(0.5,0.5)、(1,1)则贝塞尔曲线是一条直线,这时候SplineDoubleKeyFrame和LinearDoubleKeyFrame是等价的。当控制点的横纵坐标不相等时,贝塞尔曲线就会出现很多变化。如下图所示,这些是贝塞尔曲线控制点处的典型位置是出现的速率曲线,X1,Y1是ControlPoint0的坐标,X2,Y2是ControlPoint2的坐标。


下面是一个SplineDoubleKeyFrame的一个实例。程序的XAML代码如下:

<Window x:Class="WpfApplication1.Window62"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="Window62" Height="303" Width="544"><Grid Margin="10" Background="AliceBlue"><Button Width="80" Height="80" Content="Move"  HorizontalAlignment="Left" VerticalAlignment="Top" Click="Button_Click"><Button.RenderTransform><TranslateTransform x:Name="tt" X="0" Y="0"></TranslateTransform></Button.RenderTransform></Button></Grid>
</Window>

Button的Click事件处理器代码如下:

        private void Button_Click(object sender, RoutedEventArgs e){//创建动画DoubleAnimationUsingKeyFrames dakX = new DoubleAnimationUsingKeyFrames();dakX.Duration = new Duration(TimeSpan.FromMilliseconds(1000));//创建、添加关键帧SplineDoubleKeyFrame kf = new SplineDoubleKeyFrame();kf.KeyTime = KeyTime.FromPercent(1);kf.Value = 400;KeySpline ks = new KeySpline();ks.ControlPoint1 = new Point(0,1);ks.ControlPoint2 = new Point(1,0);kf.KeySpline = ks;dakX.KeyFrames.Add(kf);//执行动画this.tt.BeginAnimation(TranslateTransform.XProperty ,dakX);}

关键帧动画会控制Button的位置变形、让Button横向运动。整个动画只有一个关键帧,这个关键帧使用的是SplineDoubleKeyFrame,变化速率控制曲线的两个控制点分别是(0,1)和(1,0)。与上图中的最后一幅图一致,因此目标属性会以快--慢---快的形式变化。程序的执行效果如下图所示:

5.       路径动画

如何让目标对象沿着一条给定的路径移动呢?答案是使用DoubleAnimationUsingPath类。DoubleAnimationUsingPath需要一个PathGeometry来指明移动路径,PathGeometry的数据信息可以用XAML中的Path语法书写。PathGeometry的另外一个重要属性是Source,Source属性的数据类型是PathAnimationSource枚举,枚举值可取X、Y或Angle。如果路径动画Source属性的取值是PathAnimationSource.X,意味着这个动画关注的是曲线上每一点横坐标的变化。如果路径动画Source属性的取值是PathAnimationSource.Y,意味着这个动画关注的是曲线上每一点纵坐标的变化;如果路径动画的Source属性取值是PathAnimationSource.Angle,意味着这个动画关注的是曲线上每一点切线方向的变化。

下面这个例子讲的是让一个Button沿着一条贝塞尔曲线做波浪运动。程序的XAML代码如下:

<Window x:Class="WpfApplication1.Window63"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="Window63" Height="314" Width="517"><Grid x:Name="layoutRoot"><Grid.Resources><!--移动路径--><PathGeometry x:Key="movePath" Figures="M 0,50 C 300,-100 300,400 600,120"></PathGeometry></Grid.Resources><Button Content="Move" Width="80" Height="80" HorizontalAlignment="Left" VerticalAlignment="Top" Click="Button_Click"><Button.RenderTransform><TranslateTransform X="0" Y="0" x:Name="tt"></TranslateTransform></Button.RenderTransform></Button></Grid>
</Window>

Button的Click事件处理代码如下:

        private void Button_Click(object sender, RoutedEventArgs e){//从XAML代码中获取移动路径数据PathGeometry pg = this.layoutRoot.FindResource("movePath") as PathGeometry;Duration duration = new Duration(TimeSpan.FromMilliseconds(600));//创建动画DoubleAnimationUsingPath dpX = new DoubleAnimationUsingPath();dpX.Duration = duration;dpX.PathGeometry = pg;dpX.Source = PathAnimationSource.X;DoubleAnimationUsingPath dpY = new DoubleAnimationUsingPath();dpY.Duration = duration;dpY.PathGeometry = pg;dpY.Source = PathAnimationSource.Y;//执行动画this.tt.BeginAnimation(TranslateTransform.XProperty,dpX);this.tt.BeginAnimation(TranslateTransform.YProperty,dpY);}

感兴趣的话,黑可以为动画添加自动返回和循环控制代码:

            dpX.AutoReverse = true;dpX.RepeatBehavior = RepeatBehavior.Forever;dpY.AutoReverse = true;dpY.RepeatBehavior = RepeatBehavior.Forever;

程序运行的效果如下图:

1.4.2        场景

场景,StroyBoard就是并行执行一组动画(前面讲述的关键帧动画则是串行的执行一组动画)。

如果你是一位导演,当你对照剧本构思一个场景的时候脑子里一定想的是应该有多少个演员参加到了这个场景、它们都是什么演员、主角/配角/群众演员分别什么时候到场、每个演员该说什么?做什么?... ...演员具体用谁?由场景的需要来定。到时候开机的时候,一声令下,所有演员都会按照预先分配好的脚本进行表演,一个影视片段就算录成了。

设计WPF的场景时情况也差不多,先是把一组独立的动画组织在一个StoryBoard元素中、安排好它们的协作关系,然后指定哪个动画由哪个UI元素,哪个属性负责完成。StoryBoard设计好后,你可以为它选择一个恰当的触发时机,比如按钮按下时或者下载开始时。一旦触发条件被满足,动画场景就会开始执行,用户就会看到执行效果。

下面是一个SotryBoard例子。程序的XAML代码如下:

<Window x:Class="WpfApplication1.Window64"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="Window64" Height="159" Width="461"><Grid><Grid.RowDefinitions><RowDefinition Height="38" /><RowDefinition Height="38" /><RowDefinition Height="38" /></Grid.RowDefinitions><Grid.ColumnDefinitions><ColumnDefinition /><ColumnDefinition Width="60" /></Grid.ColumnDefinitions><!--跑道(红)--><Border Grid.Row="0" BorderBrush="Gray" BorderThickness="1"><Ellipse Width="36" Height="36" Fill="Red" HorizontalAlignment="Left" x:Name="ballR"><Ellipse.RenderTransform><TranslateTransform X="0" Y="0" x:Name="ttR"></TranslateTransform></Ellipse.RenderTransform></Ellipse></Border><!--跑道(绿)--><Border Grid.Row="1" BorderBrush="Gray" BorderThickness="1,0,1,1"><Ellipse Width="36" Height="36" Fill="Green" HorizontalAlignment="Left" x:Name="ballG"><Ellipse.RenderTransform><TranslateTransform X="0" Y="0" x:Name="ttG"></TranslateTransform></Ellipse.RenderTransform></Ellipse></Border><!--跑道(蓝)--><Border Grid.Row="2" BorderBrush="Gray" BorderThickness="1,0,1,1"><Ellipse Width="36" Height="36" Fill="Blue" HorizontalAlignment="Left" x:Name="ballB"><Ellipse.RenderTransform><TranslateTransform X="0" Y="0" x:Name="ttB"></TranslateTransform></Ellipse.RenderTransform></Ellipse></Border><!--按钮--><Button Content="Go" Grid.RowSpan="3" Grid.Column="1" Click="Button_Click"></Button></Grid>
</Window>

程序的UI效果图如下图,单击按钮后,三个小球分别在不同的时间开始向右以不同的速度移动。


Button的事件处理器代码如下:

        private void Button_Click(object sender, RoutedEventArgs e){Duration duration = new Duration(TimeSpan.FromMilliseconds(600));//红色小球匀速运动DoubleAnimation daRx = new DoubleAnimation();daRx.Duration = duration;daRx.To = 400;//绿色小球做变速运动DoubleAnimationUsingKeyFrames dakGx = new DoubleAnimationUsingKeyFrames();dakGx.Duration = duration;SplineDoubleKeyFrame kfg = new SplineDoubleKeyFrame(400,KeyTime.FromPercent(1));kfg.KeySpline = new KeySpline(1,0,0,1);dakGx.KeyFrames.Add(kfg);//蓝色小球变速运动DoubleAnimationUsingKeyFrames dakBx = new DoubleAnimationUsingKeyFrames();dakBx.Duration = duration;SplineDoubleKeyFrame kfb = new SplineDoubleKeyFrame(400,KeyTime.FromPercent(1));kfb.KeySpline = new KeySpline(0,1,1,0);dakBx.KeyFrames.Add(kfb);//创建场景Storyboard storyBoard = new Storyboard();Storyboard.SetTargetName(daRx,"ttR");Storyboard.SetTargetProperty(daRx, new PropertyPath(TranslateTransform.XProperty));Storyboard.SetTargetName(dakGx, "ttG");Storyboard.SetTargetProperty(dakGx, new PropertyPath(TranslateTransform.XProperty));Storyboard.SetTargetName(dakBx, "ttB");Storyboard.SetTargetProperty(dakBx, new PropertyPath(TranslateTransform.XProperty));storyBoard.Duration = duration;storyBoard.Children.Add(daRx);storyBoard.Children.Add(dakBx);storyBoard.Children.Add(dakGx);storyBoard.Begin(this);storyBoard.Completed += (a, b) => { MessageBox.Show(ttR.X.ToString()); };}

毋庸置疑,使用C#代码实现StoryBoard非常的复杂,出了拿来研究或者遇到非要使用C#动态代码创建StoryBoard的情况,不然我们都是在XAML里面创建StoryBoard的。StoryBoard一般放在UI元素的Trigger里,Trigger在触发时会执行<BeginStoryBoard>标签中的SotryBoard实例:

        <!--按钮--><Button Content="Go" Grid.RowSpan="3" Grid.Column="1" Click="Button_Click"><Button.Triggers><EventTrigger RoutedEvent="Button.Click"><BeginStoryboard><Storyboard Duration="0:0:0.6"><!--红色小球动画--><DoubleAnimation Duration="0:0:0.6" To="400" Storyboard.TargetName="ttR" Storyboard.TargetProperty="X"></DoubleAnimation><!--绿色小球动画--><DoubleAnimationUsingKeyFrames Duration="0:0:0.6" Storyboard.TargetProperty="X" Storyboard.TargetName="ttG"><SplineDoubleKeyFrame KeyTime="0:0:0.6" Value="400" KeySpline="1,0,0,1"></SplineDoubleKeyFrame></DoubleAnimationUsingKeyFrames><!--蓝色小球动画--><DoubleAnimationUsingKeyFrames Duration="0:0:0.6" Storyboard.TargetName="ttB" Storyboard.TargetProperty="X"><SplineDoubleKeyFrame KeyTime="0:0:0.6" Value="400" KeySpline="0,1,1,0"></SplineDoubleKeyFrame></DoubleAnimationUsingKeyFrames></Storyboard></BeginStoryboard></EventTrigger></Button.Triggers></Button>

除了为Button添加了Trigger并去掉对Click事件的订阅之外,XAML代码的其它部分不做任何改动。可以看到,XAML代码编写动画比C#代码简洁了很多-----Blend生成的StoryBoard代码与之非常类似。


转载请注明出处:http://blog.csdn.net/fwj380891124

查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. Ubuntu 安装Openssh服务端

    1.安装Openssh服务端首先,我们需要更新我们的本地库索引。所以如下所见,我们需要先输入“apt-get update”$ sudo apt-get update现在我们可以通过以下命令安装openssh-server:$ sudo apt-get install openssh-server2. 开启openssh服务在OpenSSH已经成功安装在Ubuntu14.04操…...

    2024/4/19 13:15:28
  2. HTML底部返回顶部悬浮按钮

    CSS样式: .back-to {bottom: 55px;overflow: hidden;position: fixed;right: 10px;width: 110px;z-index: 999;}.back-to .back-top {background: url("./images/top.png") no-repeat scroll 0 0 transparent;display: block;float: right;height: 50px;margin-left…...

    2024/4/13 0:39:55
  3. 数据结构与算法(2)链表

    先放上来一个很好玩的网站:VisuAlgo链表节点长什么样子struct node{ElementType data;node *next; };操作我不打算写ADT,因为我觉得ADT写出来会让人有一种惶恐感,本着实用的原则,我不想让大家还没用就有一种“天哪,这玩意这么复杂”的感觉。 这里我主要讲两种操作,只要这…...

    2024/4/13 0:40:10
  4. ubuntu16安装ssh

    1. 安装ssh server 和 安装ssh clientsudo apt-get install openssh-server sudo apt-get install openssh-client2. 检查SSH是否安装成功ps -e | grep ssh*...

    2024/4/30 17:27:14
  5. C#饼状图和柱状图

    当我们的软件需要各种饼状图和柱状图来表示数据时,我们或许会想到用Offices中的图形控件或是第三方控件,但现在的第三方控件大都需要注册,有些免费的控件会有开发商的标记等。而对于使用Offices的图形控件来说,并不能在程序中得于很好控制,其使用的简易程度也较低,所以在…...

    2024/4/13 0:40:10
  6. “返回顶部”实现一例

    网页中,返回顶部很普遍。现有一例子,主要靠图片 + Jquery + CSS来完成:效果有以下几个方面:1、页面在顶部或滚动到顶部时,“返回顶部”图片隐藏2、当页面向下滚动时,“返回顶部”图片显现3、鼠标未在“返回顶部”图片上面时,图片是灰的4、鼠标移到“返回顶部”图片上面时…...

    2024/4/13 0:40:05
  7. 多态---相关操作

    多态—相关操作基本格式与使用Zi类 package cn.xiaoge.day10.demo04;public class Zi extends Fu {@Overridepublic void method() {System.out.println("子类方法");} }Fu类 package cn.xiaoge.day10.demo04;public class Fu {public void method() {System.out.pri…...

    2024/4/15 6:53:57
  8. Ubuntu安装ssh server及分配用户

    1.安装openssh-server # sudo apt-get install openssh-server2.给客户端分配账户和密码 # sudo adduser 用户名 //输入Ubuntu密码和设置的密码,依次添加账户即可3.client端登陆 # ssh username@ip...

    2024/4/8 20:42:24
  9. 优衣库KAWS联名款遭哄抢 大打出手场面惨烈 是我不懂时尚了吗?

    从昨天到今天,微博热搜上一直挂着#优衣库联名款遭哄抢#的话题,热度就没有下来过。6月3号,优衣库与KAWS联名款T恤发售,但由于这是优衣库与KAWS最后一次联名,所以抢购来得格外热烈。线上0点开售之后秒售罄,随后早上各地优衣库门店刚开张,就出现顾客哄抢该联名款T恤的场面。…...

    2024/4/21 19:00:02
  10. iOS中点击首页返回屏幕顶部的代码实现与步骤

    最近发现很多app都支持点击首页返回到屏幕顶部的功能,所以今天自己研究了一下,本着分享的心态跟大家讨论一下. step 1 : 自定义一个UITabBarController, 给这个tabbarController加上一个属性lastSelectedIndex,系统的UITabBarController是没有这个属性的,重写父类的setSelected…...

    2024/4/13 0:40:05
  11. VFP开源项目之:易用的VFP图表FoxCharts

    该图表是基于GdiPlusX ImageCanvas的VFP类,允许VFP开发者直接绘制图表和图形。此类是纯VFP代码创建,无ActiveX控件,因此易于安装、重新定制,关键是开源,你可以根据自己的需要重新设计代码。 下载:http://vfpx.codeplex.com/downloads/get/97914...

    2024/4/19 8:56:11
  12. 零基础:R必知必会

    作者:herain R语言中文社区专栏作者知乎ID:https://www.zhihu.com/people/herain-14个人公众号:趣味数据周刊1:给矩阵 matrix1_1 添加行名称/列名称1dimnames(matix1_1)<list(c(行,名,称), c(列,名,称))2:安装包时,指定镜像地址1install.packages("showtext…...

    2024/4/16 10:53:50
  13. Ubuntu下手动安装openssh

    openssh手动安装方法 网上的普遍方法是安装zlib,openssl之后再安装openssh,但是我这样做没成功,反复尝试终于弄通了,把我的方法和大家分享一下。我反复试验了目前的12.04和13.04两个版本的ubuntu系统,只有在12.04上能成功,13.04目前不是stable版本。。 1,需要用到zlib,…...

    2024/4/21 7:13:04
  14. 原生js返回顶部动画效果怎么实现?

    项目背景:最近在做一个网站页面的改版,有一个返回顶部的功能需求,考虑怎么才能有更好一点的用户体验。建议使用第二种实现方法 1. 第一种实现方法转载 https://www.cnblogs.com/JosephBee/p/7326556.htmlvar scrollTop = document.documentElement.scrollTop || document.bo…...

    2024/4/19 10:23:52
  15. SVG/VML开发工作流图形系统

    工作流就是几个紧密相关的连贯的工作过程的执行。在此过程中,文档、任务或其他信息按一定的规则在用户中间进行传递。完整的工作流管理系统应包括工作流定义、后台引擎、系统管理及任务分配、流程监控等功能。多比图形控件可以很方便地开发工作流定义模块,它不仅支持一般工作…...

    2024/4/13 0:41:01
  16. ubuntu安装ssh失败

    错误原因The following packages have unmet dependencies: openssh-server : Depends: openssh-client (= 1:6.6p1-2ubuntu2.8) but 1:7.2p2-4ubuntu1 is to be installed Recommends: ncurses-term but it is not going to be installed R…...

    2024/4/13 0:40:56
  17. 对话框与其控件的颜色,字体的设置

    要改变对话框的一般颜色,可以在C***App的InitInstance函数里加入如下代码:SetDialogBkColor(RGB(0,255,255),RGB(255,0,0));//背景青蓝、文字红色但是这样不能改变对话框中的图形控件的颜色;每个控件在dialog中都是一个窗口,当要绘制控件时,会发出一个WM_CTLCOLOR消息给它…...

    2024/4/19 23:03:15
  18. 全新设计 水果忍者-穿靴子的猫官方中文版首发

    2011年底一部动画片吸引了广大爱猫人士的眼球,那就是梦工厂推出的《穿靴子的猫》,这只桀骜不驯的猫咪在征服影视界的同时也成功进军游戏界,为众多手机游戏迷们带来了精彩刺激的最新游戏——《水果忍者:穿靴子的猫》!这款游戏是水果忍者的一款升级版本,由于其逼真的设计,…...

    2024/4/20 11:19:59
  19. 如何通过HTML标记或JS代码实现跳转返回页面顶部

    一、使用锚标记返回页面顶部 使用HTML锚标记最简单,就是看起来有点不好看,点击后会在地址栏显示这个锚标记,其它的倒没什么。 页面顶部放置: <a name="top" id="top"></a> 放置位置在<body>标签之后随便找个地方放都可以,只要靠近顶…...

    2024/4/27 5:44:56
  20. Ubuntu下查看ssh服务是否安装或启动的方法

    https://www.jb51.net/article/104076.htm 查看ssh是否启动,有sshd说明已经启动 sudo ps -e |grep ssh 启动ssh服务 sudo service ssh start 下图是没有安装ssh服务时,运行的效果安装ssh服务 sudo apt-get install openssh-server...

    2024/4/13 0:41:11

最新文章

  1. 【C++11】多线程创建/互斥详解

    目录 1. 背景2. 线程创建2.1 使用std::thread类来创建线程2.2 使用std::async 函数来创建线程2.3 std::thread和std::async的区别 3. 线程互斥3.1 互斥体std::mutex3.2 互斥体包装器std::lock_guard3.3 条件变量std::condition_variable3.4 原子类型std::atomic 4. 线程控制自己…...

    2024/4/30 21:06:30
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. ChatGPT 初学者指南

    原文&#xff1a;ChatGPT for Beginners 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 介绍 如果您一直关注新闻和趋势&#xff0c;您可能已经在某个地方读到或听到过&#xff0c;Sam Altman 的生成式人工智能平台 ChatGPT 已经将人工智能推向了一个新的高度 - 许多…...

    2024/4/30 2:06:42
  4. redis之主从复制、哨兵模式

    一 redis群集有三种模式 主从复制&#xff1a; 主从复制是高可用Redis的基础&#xff0c;哨兵和集群都是在主从复制基础上实现高可用的。 主从复制主要实现了数据的多机备份&#xff0c;以及对于读操作的负载均衡和简单的故障恢复。 缺陷&#xff1a; 故障恢复无法自动化&…...

    2024/4/30 12:17:23
  5. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/4/29 23:16:47
  6. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/4/30 18:14:14
  7. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/4/29 2:29:43
  8. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/4/30 18:21:48
  9. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/4/27 17:58:04
  10. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/4/27 14:22:49
  11. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/4/28 1:28:33
  12. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/4/30 9:43:09
  13. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/4/27 17:59:30
  14. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/4/25 18:39:16
  15. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/4/28 1:34:08
  16. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/4/26 19:03:37
  17. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/4/29 20:46:55
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/4/25 18:39:14
  19. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/4/26 23:04:58
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/4/27 23:24:42
  21. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/4/28 5:48:52
  22. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/4/30 9:42:22
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/4/30 9:43:22
  24. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/4/30 9:42:49
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57