一、wpf 动画基础概念
1.1 什么是 wpf 动画
wpf 动画是一种通过随时间改变对象属性值来创建动态视觉效果的技术。与传统的基于帧的动画不同,wpf 动画基于属性驱动,这意味着开发者只需指定动画的起始值、结束值以及持续时间等关键参数,wpf 框架会自动计算并在指定时间内平滑地改变对象的属性值,从而实现动画效果。例如,我们可以通过动画让一个按钮在点击时逐渐放大,或者让一个文本框的背景颜色在一段时间内渐变。
1.2 动画的基本类型
- wpf 主要提供了三种类型的动画:
线性动画(linear animations):这类动画以恒定的速度改变属性值,从起始值线性过渡到结束值。例如,doubleanimation
用于对double类型的属性进行线性动画,如改变控件的宽度、高度或透明度等。
关键帧动画(key - frame animations):关键帧动画允许在动画过程中定义多个关键时间点及其对应的属性值,动画会在这些关键帧之间进行插值计算,从而实现更复杂的动画效果。例如,doubleanimationusingkeyframes
可以定义多个不同时间点的double
值,使控件的属性按照这些关键帧的值进行变化。
路径动画(path animations):路径动画用于使对象沿着指定的路径移动。通过pathgeometry
定义路径,然后使用pointanimationusingpath
等动画类型,让对象能够沿着复杂的路径进行运动,这在创建一些具有特定轨迹的动画效果时非常有用。
1.3 动画的核心元素
在 wpf 中,创建动画主要涉及以下几个核心元素:
动画类(animation classes):如前面提到的doubleanimation
、doubleanimationusingkeyframes
等,这些类继承自timeline类,负责定义动画的具体行为,包括起始值、结束值、持续时间、缓动函数等。
故事板(storyboard):storyboard
是一个用于管理和控制一组动画的容器。它可以包含多个动画,并且可以通过begin
、stop
、pause
等方法来控制动画的播放。例如,我们可以在一个storyboard
中同时包含按钮的放大动画和颜色渐变动画,使按钮在点击时同时产生多种动画效果。
依赖属性(dependency properties):动画是通过改变对象的依赖属性来实现的。依赖属性是 wpf 中一种特殊的属性类型,它具有很多优点,如支持数据绑定、样式设置、动画等。几乎所有 wpf 控件的可视属性,如width
、height
、opacity
等,都是依赖属性,这使得它们能够方便地参与动画过程。
二、线性动画详解
2.1 doubleanimation 的使用
doubleanimation
是最常用的线性动画之一,用于对double
类型的属性进行动画操作。下面是一个简单的示例,展示如何使用doubleanimation
让一个按钮在点击时逐渐放大:
<window x:class="wpfapp1.mainwindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" title="doubleanimation example" height="350" width="525"> <window.resources> <storyboard x:key="buttongrowstoryboard"> <doubleanimation storyboard.targetname="mybutton" storyboard.targetproperty="width" from="100" to="150" duration="0:0:0.5"/> </storyboard> </window.resources> <grid> <button x:name="mybutton" content="click me" horizontalalignment="center" verticalalignment="center" click="mybutton_click"> <button.triggers> <eventtrigger routedevent="button.click"> <beginstoryboard storyboard="{staticresource buttongrowstoryboard}"/> </eventtrigger> </button.triggers> </button> </grid> </window>
在上述代码中:
首先在window.resources
中定义了一个storyboard
,其中包含一个doubleanimation
。storyboard.targetname
指定了动画作用的目标控件为mybutton
,storyboard.targetproperty
指定了要动画的属性为width
。from
属性指定了动画的起始值为100,to属性指定了结束值为150,duration
属性指定了动画持续时间为 0.5 秒。
在button
控件中,通过eventtrigger
监听按钮的click事件,当按钮被点击时,触发beginstoryboard
,从而启动buttongrowstoryboard
动画,使按钮的宽度从 100 逐渐增加到 150。
2.2 coloranimation 实现颜色渐变
coloranimation用于对颜色属性进行动画操作,实现颜色的渐变效果。例如,我们可以让一个矩形的填充颜色在一段时间内从红色渐变为蓝色:
<window x:class="wpfapp1.mainwindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" title="coloranimation example" height="350" width="525"> <window.resources> <storyboard x:key="rectanglecolorstoryboard"> <coloranimation storyboard.targetname="myrectangle" storyboard.targetproperty="(rectangle.fill).(solidcolorbrush.color)" from="red" to="blue" duration="0:0:2"/> </storyboard> </window.resources> <grid> <rectangle x:name="myrectangle" width="200" height="100" fill="red" horizontalalignment="center" verticalalignment="center"> <rectangle.triggers> <eventtrigger routedevent="rectangle.mouseenter"> <beginstoryboard storyboard="{staticresource rectanglecolorstoryboard}"/> </eventtrigger> </rectangle.triggers> </rectangle> </grid> </window>
这里:
storyboard
中的coloranimation
将myrectangle
的填充颜色从红色渐变为蓝色。storyboard.targetproperty
使用了一种较为复杂的语法,因为rectangle
的fill
属性是一个brush
,而我们要动画的是brush
的color
属性,所以使用(rectangle.fill).(solidcolorbrush.color)
来指定。
当鼠标进入矩形时,通过eventtrigger
触发动画,实现颜色渐变效果。
三、关键帧动画深入
3.1 doubleanimationusingkeyframes 创建复杂动画
doubleanimationusingkeyframes
允许通过定义多个关键帧来创建复杂的动画效果。每个关键帧都有一个时间点和对应的属性值。例如,我们可以创建一个让按钮的宽度按照不同的速度和时间进行变化的动画:
<window x:class="wpfapp1.mainwindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" title="doubleanimationusingkeyframes example" height="350" width="525"> <window.resources> <storyboard x:key="buttoncomplexgrowstoryboard"> <doubleanimationusingkeyframes storyboard.targetname="mybutton" storyboard.targetproperty="width"> <easingdoublekeyframe value="100" keytime="0:0:0"/> <easingdoublekeyframe value="120" keytime="0:0:0.3" easingfunction="{staticresource cubiceaseout}"/> <easingdoublekeyframe value="150" keytime="0:0:0.6" easingfunction="{staticresource quadraticeaseout}"/> </doubleanimationusingkeyframes> </storyboard> <cubicease x:key="cubiceaseout" easingmode="easeout"/> <quadraticease x:key="quadraticeaseout" easingmode="easeout"/> </window.resources> <grid> <button x:name="mybutton" content="click me" horizontalalignment="center" verticalalignment="center" click="mybutton_click"> <button.triggers> <eventtrigger routedevent="button.click"> <beginstoryboard storyboard="{staticresource buttoncomplexgrowstoryboard}"/> </eventtrigger> </button.triggers> </button> </grid> </window>
在这个例子中:
定义了三个关键帧。第一个关键帧在动画开始时(keytime="0:0:0"
),按钮宽度为100。第二个关键帧在 0.3 秒时,按钮宽度变为120,并使用了cubiceaseout
缓动函数,使动画在接近该关键帧时减速。第三个关键帧在 0.6 秒时,按钮宽度变为150,使用quadraticeaseout
缓动函数。
通过这种方式,可以创建出比简单线性动画更丰富、更自然的动画效果。
3.2 coloranimationusingkeyframes 实现多色渐变
coloranimationusingkeyframes用于创建颜色的多色渐变动画。比如,我们可以让一个椭圆的填充颜色在不同时间点依次变为红、绿、蓝:
<window x:class="wpfapp1.mainwindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" title="coloranimationusingkeyframes example" height="350" width="525"> <window.resources> <storyboard x:key="ellipsecolorstoryboard"> <coloranimationusingkeyframes storyboard.targetname="myellipse" storyboard.targetproperty="(ellipse.fill).(solidcolorbrush.color)"> <easingcolorkeyframe value="red" keytime="0:0:0"/> <easingcolorkeyframe value="green" keytime="0:0:1"/> <easingcolorkeyframe value="blue" keytime="0:0:2"/> </coloranimationusingkeyframes> </storyboard> </window.resources> <grid> <ellipse x:name="myellipse" width="100" height="100" fill="red" horizontalalignment="center" verticalalignment="center"> <ellipse.triggers> <eventtrigger routedevent="ellipse.mouseenter"> <beginstoryboard storyboard="{staticresource ellipsecolorstoryboard}"/> </eventtrigger> </ellipse.triggers> </ellipse> </grid> </window>
此代码中:
定义了三个关键帧,分别在动画开始、1 秒和 2 秒时将椭圆的填充颜色设置为红、绿、蓝。当鼠标进入椭圆时,触发该动画,实现颜色的多色渐变效果。
四、路径动画探索
4.1 pointanimationusingpath 实现沿路径移动
pointanimationusingpath
用于使对象沿着指定的路径移动。下面是一个简单的示例,让一个圆形沿着一个椭圆路径移动:
<window x:class="wpfapp1.mainwindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" title="pointanimationusingpath example" height="350" width="525"> <window.resources> <storyboard x:key="circlemovestoryboard"> <pointanimationusingpath storyboard.targetname="mycircle" storyboard.targetproperty="(canvas.left, canvas.top)" pathgeometry="{staticresource ellipsepath}" duration="0:0:5" repeatbehavior="forever"/> </storyboard> <pathgeometry x:key="ellipsepath"> <pathfigure startpoint="100,100"> <arcsegment point="300,100" size="100,50" islargearc="true" sweepdirection="counterclockwise"/> </pathfigure> </pathgeometry> </window.resources> <canvas> <ellipse x:name="mycircle" width="20" height="20" fill="red" canvas.left="100" canvas.top="100"> <ellipse.triggers> <eventtrigger routedevent="ellipse.loaded"> <beginstoryboard storyboard="{staticresource circlemovestoryboard}"/> </eventtrigger> </ellipse.triggers> </ellipse> </canvas> </window>
在这段代码中:
首先定义了一个pathgeometry
,它描述了一个椭圆路径。pathfigure
指定了路径的起始点,arcsegment
定义了椭圆弧的终点、大小、是否为大弧以及扫描方向。
pointanimationusingpath
的storyboard.targetproperty
指定为(canvas.left, canvas.top
),表示要同时动画圆形的canvas.lef
t和canvas.top
属性,使其沿着指定的椭圆路径移动。duration
设置为 5 秒,repeatbehavior
设置为forever
,表示动画将无限循环。
当椭圆加载完成时,触发动画,圆形开始沿着椭圆路径移动。
4.2 pathanimation 实现复杂路径动画
pathanimation可以用于对更复杂的路径相关属性进行动画。例如,我们可以让一个路径的笔画宽度沿着路径的长度进行变化:
<window x:class="wpfapp1.mainwindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" title="pathanimation example" height="350" width="525"> <window.resources> <storyboard x:key="pathstrokewidthstoryboard"> <pathanimation storyboard.targetname="mypath" storyboard.targetproperty="strokethickness" pathgeometry="{staticresource complexpath}" duration="0:0:3"> <pathanimation.keyframes> <lineardoublekeyframe value="1" keytime="0:0:0"/> <lineardoublekeyframe value="5" keytime="0:0:1.5"/> <lineardoublekeyframe value="1" keytime="0:0:3"/> </pathanimation.keyframes> </pathanimation> </storyboard> <pathgeometry x:key="complexpath"> <pathfigure startpoint="50,50"> <linesegment point="150,150"/> <arcsegment point="250,50" size="50,50" islargearc="true" sweepdirection="clockwise"/> </pathfigure> </pathgeometry> </window.resources> <canvas> <path x:name="mypath" stroke="blue" strokethickness="1" data="{staticresource complexpath}"> <path.triggers> <eventtrigger routedevent="path.loaded"> <beginstoryboard storyboard="{staticresource pathstrokewidthstoryboard}"/> </eventtrigger> </path.triggers> </path> </canvas> </window>
这里:
定义了一个复杂的pathgeometry
,包含直线段和弧线。pathanimation
用于对path
的strokethickness
属性进行动画。
通过keyframes
定义了三个关键帧,使笔画宽度在动画开始时为 1,1.5 秒时变为 5,3 秒时又变回 1。当路径加载完成时,动画开始,实现路径笔画宽度的动态变化。
五、动画的高级应用与技巧
5.1 缓动函数(easing functions)
缓动函数是 wpf 动画中非常重要的一部分,它能够改变动画的速度曲线,使动画效果更加自然和生动。在前面的关键帧动画示例中,我们已经简单使用了cubiceaseout
和quadraticeaseout
等缓动函数。
wpf 提供了多种内置的缓动函数,如linearease
(线性缓动,动画以恒定速度进行)、backease
(模拟物体向后退再向前的效果)、bounceease
(实现类似物体弹跳的效果)、elasticease
(模拟弹性物体的运动效果)等。每种缓动函数都有其独特的动画表现,通过设置easingmode
属性,还可以控制缓动的方向,如easein
(动画开始时缓慢,逐渐加速)、easeout
(动画开始时快速,逐渐减速)、easeinout
(动画开始和结束时缓慢,中间快速)。
以bounceease为例,我们可以让一个按钮在点击时产生弹跳效果:
<window x:class="wpfapp1.mainwindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" title="bounceease example" height="350" width="525"> <window.resources> <storyboard x:key="buttonbouncestoryboard"> <doubleanimation storyboard.targetname="mybutton" storyboard.targetproperty="height" from="100" to="150" duration="0:0:1"> <doubleanimation.easingfunction> <bounceease bounces="3" easingmode="easeout"/> </doubleanimation.easingfunction> </doubleanimation> </storyboard> </window.resources> <grid> <button x:name="mybutton" content="click me" horizontalalignment="center" verticalalignment="center" click="mybutton_click"> <button.triggers> <eventtrigger routedevent="button.click"> <beginstoryboard storyboard="{staticresource buttonbouncestoryboard}"/> </eventtrigger> </button.triggers> </button> </grid> </window>
在这个例子中,bounceease
的bounces
属性设置为 3,表示按钮在动画结束时会弹跳 3 次,easingmode
为easeout
,意味着动画在结束阶段产生弹跳效果。
5.2 动画组(animation groups)
动画组允许在一个storyboard
中同时运行多个动画,并且可以控制它们之间的时间关系。例如,我们可以让一个图像在放大的同时旋转,创建出更丰富的动画效果。
<window x:class="wpfapp1.mainwindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" title="animation group example" height="350" width="525"> <window.resources> <storyboard x:key="imageanimationstoryboard"> <doubleanimation storyboard.targetname="myimage" storyboard.targetproperty="width" from="100" to="150" duration="0:0:1"/> <doubleanimation storyboard.targetname="myimage" storyboard.targetproperty="(uielement.rendertransform).(rotatetransform.angle)" from="0" to="360" duration="0:0:1"/> </storyboard> <rotatetransform x:key="imagerotatetransform" angle="0"/> </window.resources> <grid> <image x:name="myimage" source="yourimage.jpg" horizontalalignment="center" verticalalignment="center"> <image.rendertransform> <rotatetransform x:name="imagerotatetransform" angle="0"/> </image.rendertransform> <image.triggers> <eventtrigger routedevent="image.mouseenter"> <beginstoryboard storyboard="{staticresource imageanimationstoryboard}"/> </eventtrigger> </image.triggers> </image> </grid> </window>
在这段代码中,storyboard
包含了两个动画:一个是doubleanimation
用于放大图像的宽度,另一个也是doubleanimation
用于旋转图像。通过这种方式,当鼠标进入图像时,图像会同时进行放大和旋转动画。
5.3 动画事件(animation events)
动画事件允许开发者在动画的特定阶段执行自定义代码,比如动画开始、结束或重复时。以storyboard
的completed
事件为例,我们可以在一个动画结束后执行一些操作,如显示一个提示信息。
<window x:class="wpfapp1.mainwindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" title="animation events example" height="350" width="525"> <window.resources> <storyboard x:key="buttonfadeoutstoryboard" completed="buttonfadeoutstoryboard_completed"> <doubleanimation storyboard.targetname="mybutton" storyboard.targetproperty="opacity" from="1" to="0" duration="0:0:1"/> </storyboard> </window.resources> <grid> <button x:name="mybutton" content="click me" horizontalalignment="center" verticalalignment="center" click="mybutton_click"> <button.triggers> <eventtrigger routedevent="button.click"> <beginstoryboard storyboard="{staticresource buttonfadeoutstoryboard}"/> </eventtrigger> </button.triggers> </button> </grid> </window>
在后台代码中:
using system.windows; namespace wpfapp1 { public partial class mainwindow : window { public mainwindow() { initializecomponent(); } private void mybutton_click(object sender, routedeventargs e) { // 按钮点击逻辑 } private void buttonfadeoutstoryboard_completed(object sender, system.eventargs e) { messagebox.show("按钮已消失"); } } }
当buttonfadeoutstoryboard
动画结束时,会触发completed
事件,执行buttonfadeoutstoryboard_completed
方法,弹出一个提示框。
六、实际应用案例
6.1 打造欢迎界面动画
在很多应用程序中,欢迎界面往往会使用动画来吸引用户的注意力。我们可以使用 wpf 动画创建一个简单而炫酷的欢迎界面。例如,让应用程序的图标逐渐放大并旋转,同时显示一段欢迎文字,文字从透明渐变到不透明。
<window x:class="wpfapp1.welcomewindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" title="welcome window" height="350" width="525" windowstartuplocation="centerscreen"> <window.resources> <storyboard x:key="welcomeanimationstoryboard"> <doubleanimation storyboard.targetname="appicon" storyboard.targetproperty="width" from="50" to="150" duration="0:0:2"> <doubleanimation.easingfunction> <backease easingmode="easeout"/> </doubleanimation.easingfunction> </doubleanimation> <doubleanimation storyboard.targetname="appicon" storyboard.targetproperty="(uielement.rendertransform).(rotatetransform.angle)" from="0" to="360" duration="0:0:2"/> <doubleanimation storyboard.targetname="welcometext" storyboard.targetproperty="opacity" from="0" to="1" duration="0:0:1.5" begintime="0:0:0.5"/> </storyboard> <rotatetransform x:key="appiconrotatetransform" angle="0"/> </window.resources> <grid> <ellipse x:name="appicon" width="50" height="50" fill="blue" horizontalalignment="center" verticalalignment="center"> <ellipse.rendertransform> <rotatetransform x:name="appiconrotatetransform" angle="0"/> </ellipse.rendertransform> <ellipse.triggers> <eventtrigger routedevent="ellipse.loaded"> <beginstoryboard storyboard="{staticresource welcomeanimationstoryboard}"/> </eventtrigger> </ellipse.triggers> </ellipse> <textblock x:name="welcometext" text="欢迎使用本应用" fontsize="24" horizontalalignment="center" verticalalignment="center" opacity="0"/> </grid> </window>
在这个欢迎界面中,应用程序图标在 2 秒内逐渐放大,同时旋转 360 度,使用backease
缓动函数使放大效果更自然。欢迎文字在 0.5 秒后开始从透明渐变到不透明,持续 1.5 秒,整个动画营造出一个生动的欢迎氛围。
6.2 实现动态菜单交互效果
对于应用程序的菜单,我们可以使用动画来增强其交互性。例如,当鼠标悬停在菜单项上时,菜单项可以向右滑动并改变颜色,给用户提供直观的反馈。
<window x:class="wpfapp1.mainwindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" title="dynamic menu example" height="350" width="525"> <window.resources> <storyboard x:key="menuitemhoverstoryboard"> <doubleanimation storyboard.targetname="menuitem" storyboard.targetproperty="margin.left" from="0" to="10" duration="0:0:0.2"/> <coloranimation storyboard.targetname="menuitemtext" storyboard.targetproperty="(textblock.foreground).(solidcolorbrush.color)" from="black" to="blue" duration="0:0:0.2"/> </storyboard> <storyboard x:key="menuitemleavestoryboard"> <doubleanimation storyboard.targetname="menuitem" storyboard.targetproperty="margin.left" from="10" to="0" duration="0:0:0.2"/> <coloranimation storyboard.targetname="menuitemtext" storyboard.targetproperty="(textblock.foreground).(solidcolorbrush.color)" from="blue" to="black" duration="0:0:0.2"/> </storyboard> </window.resources> <grid> <stackpanel orientation="vertical"> <stackpanel x:name="menuitem" orientation="horizontal" margin="5" background="white"> <rectangle width="10" height="10" fill="gray"/> <textblock x:name="menuitemtext" text="文件" margin="5" foreground="black"/> <stackpanel.triggers> <eventtrigger routedevent="stackpanel.mouseenter"> <beginstoryboard storyboard="{staticresource menuitemhoverstoryboard}"/> </eventtrigger> <eventtrigger routedevent="stackpanel.mouseleave"> <beginstoryboard storyboard="{staticresource menuitemleavestoryboard}"/> </eventtrigger> </stackpanel.triggers> </stackpanel> <!-- 其他菜单项 --> </stackpanel> </grid> </window>
在这个示例中,当鼠标进入菜单项时,触发menuitemhoverstoryboard动画,菜单项向左移动 10 个单位,同时文字颜色变为蓝色;当鼠标离开时,触发menuitemleavestoryboard动画,菜单项和文字颜色恢复原状,通过这种简单的动画效果,提升了菜单的交互体验。
七、性能优化与注意事项
7.1 性能优化
在使用 wpf 动画时,性能优化是一个重要的考虑因素。以下是一些优化建议:
减少不必要的动画:避免在界面上同时运行过多的动画,尤其是复杂的动画,因为这可能会消耗大量的系统资源,导致界面卡顿。只在必要的情况下使用动画,并且确保动画的持续时间和复杂度是合理的。
使用硬件加速:wpf 支持硬件加速,通过合理设置,可以利用显卡的性能来提高动画的流畅度。例如,对于一些涉及大量图形变换的动画,可以将renderoptions.edgemode属性设置为aliased,启用硬件加速。
优化动画代码:尽量减少动画代码中的计算量,避免在动画过程中进行复杂的逻辑处理。可以将一些预计算的结果缓存起来,减少动画运行时的计算开销。
7.2 注意事项
动画兼容性:在不同的操作系统和硬件环境下,动画的表现可能会有所不同。在开发过程中,需要在多种环境下进行测试,确保动画在各种情况下都能正常运行且表现一致。
依赖属性的选择:在选择要进行动画的依赖属性时,要确保该属性的变化不会对其他功能产生负面影响。例如,某些属性的动画可能会影响控件的布局或事件处理,需要谨慎处理。
动画的可维护性:随着项目的发展,动画代码可能会变得复杂。为了提高代码的可维护性,建议将动画相关的代码进行合理的封装和组织,使用资源字典来管理动画资源,使代码结构更加清晰。
八、总结
wpf 动画特效为开发者提供了强大的工具,能够创建出各种炫酷的界面交互效果,极大地提升用户体验。通过本文对 wpf 动画基础概念、各种动画类型、高级应用技巧以及实际应用案例的深入讲解,相信读者已经对 wpf 动画有了全面的了解。在实际开发中,需要根据具体的需求和场景,灵活运用这些知识,同时注意性能优化和相关注意事项,打造出高效、美观且交互性强的应用程序界面。随着技术的不断发展,wpf 动画也在不断演进,开发者可以持续关注相关技术动态,不断探索和创新,为用户带来更出色的视觉体验。
以上就是wpf实现炫酷的界面交互效果的代码详解的详细内容,更多关于wpf实现界面交互效果的资料请关注代码网其它相关文章!
发表评论