比较 Flash 和 WPF






4.61/5 (89投票s)
Flash 与 WPF 对比。
目录
引言
最近关于 WPF/Silverlight 的讨论很多。我也一直在学习和撰写 WPF 的文章,但我觉得写一篇关于 WPF 和 Flash 的对比文章可能会很有趣。
我认为我有资格写这篇文章,因为我同时使用过这两种技术。虽然我不是这两种技术方面的专家,但我有足够的信心来写这篇教程。我觉得这可以让那些不了解 WPF 或 Flash 工作原理的人有所了解。例如,如果你是一名 Flash 开发者,不了解 WPF 是什么,我希望这篇文章能帮到你,反之亦然。
注意事项
我首先要说明的是,我只有 Flash MX 2004,也就是 Flash 7.0,它使用了第二代 Flash 脚本语言(Action Script 2,以下简称 AS2)。之后,Macromedia 被 Adobe 收购,Adobe 推出了 Creative Studio 3 (CS3),其中包含了 Flash。更重要的是,他们彻底重写了 Flash 的脚本部分,现在是 Action Script 3(以下简称 AS3)。我只知道 AS3 类似于 Java,支持继承等面向对象的功能。但我有一位非常出色的 Flash 开发者朋友向我保证,写 AS2 的内容并不会浪费时间,因为尽管 AS3 不同,但我将在本文中涵盖的方面仍然是相同的。所以,如果大家允许的话,我将继续写 AS2 的内容。毕竟,人们还在写 .NET 2.0 的文章,天哪,那太老了。现在是 .NET 3.0/3.5,跟上时代吧。不,我开玩笑的,你喜欢什么都可以。我写我喜欢的内容。仅此而已。如果其他人喜欢,那就太好了;如果不喜欢,那也没关系,我仍然会继续写我喜欢的东西。
那么,我们来比较一下 WPF 和 Flash
我认为最简单的开始方式就是逐个功能进行比较。我特意只挑选 WPF 引入的功能;否则,这将是一篇非常非常漫长且枯燥的文章。我的意思是,我不会特别讨论多线程、套接字、数据库、Remoting、XML 处理等,尽管 Flash 在这些方面都绰绰有余,除了多线程。
以下是关于 Flash/WPF 人们可能想了解的对方世界的一些事物列表
- 编码风格
- 控件
- 布局
- 资源
- 数据绑定
- 样式/模板/皮肤和主题
- 2D 图形/3D 图形
- 动画
- 应用程序结构
- API
那么,我们继续吧?我的做法是在下面的每个标题下,先给出 Flash 的观点,然后是 WPF 的观点;这样,大家就能自己看到各种优缺点了。
编码风格
Flash
在开始讨论如何在 Flash 中编写代码之前,理解 Flash 的概念性工作原理非常重要。所以,我们先从那里开始,好吗?
Flash 基于一个资源库,这些资源可以在初始阶段(工作区)创建,然后分组形成 `Button`/`Graphic` 或 `MovieClip`。创建后,这些分组的舞台对象(上面提到的三种对象之一)可以作为资源添加到库中(稍后详述)。之后,库组件可以手动(拖到舞台上)或以编程方式(依我之见,这是正确的方式)添加到舞台上。
舞台基本上是由自下而上的图层和从左到右的时间轴构成。
舞台允许用户创建以下内容:
- 单个帧:可以包含库中的对象,或普通的 2D 形状,甚至 Flash 提供的标准控件。
- 运动补间(稍后详述,请参见动画部分)
- 关键帧:通过指定对象在某个时间点的确切属性来定义对象的确切外观。
- 引导层:用于为舞台上的其他对象创建运动路径。
- 遮罩层:用于隐藏超出遮罩层边界的任何对象渲染。
- 动作:可以应用于舞台上任何图层的任何关键帧,或者附加到舞台上存在的控件或其他库对象(例如 `MovieClip` 对象)。
对于从未用过 Flash 的人来说,Flash 有其自己的语言支持,称为 Action Script。最新版本的 Flash(Adobe CS3 产品中的版本)使用 Action Script 3 (AS3)。我没有那个版本,所以我将讨论 Flash MX 2004,它使用 AS2。
那么,我们可以用 Action Script 做什么呢?嗯,结果是我们可以做很多事情。
Flash 开发 Action Script 的方式非常酷,就像从面向对象语言过来的预期一样。AS2 不是 100% 面向对象的,但它是一个非常好的开始。它过去和现在都没有多线程支持,但除此之外,我认为它拥有一个不错的类、库/运算符和关键字集合。
有用于处理 `MovieClip`、声音、视频、网络摄像头、套接字、事件处理的预构建类,还有常用的集合、if-then-else、循环、数学类。我们可以继承现有控件,创建新的控件和类等等。它实际上非常成熟,在新版 AS3 中甚至更好,AS3 将是 100% 面向对象的。
现在我们知道有这些类可用,我们可以用它们做什么?以及我们如何才能首先触发一段 Action Script 来运行?
这实际上非常非常容易。我们只需要创建一个帧或对象的 Action。下面是一个示例,我正在创建舞台的 `Frame1` 上的一个 Action。当一个关键帧有 Action 时,舞台上该帧会显示一个小“a”。
从这个小截图可以看出,我们可以创建函数,并调用现有类的方法。而第一行在做什么?
attachManager = new AttachManager();
看起来它好像在创建一个对象,对吧?是的,它确实在做这件事。我们可以在 Flash Action Script 中创建对象,这些对象可以激活外部 Action Script 文件并从中创建对象。我们要不要偷偷看一眼这个外部 Action Script 文件是什么样的?
// \\\\\\\\\\\\\\
//
// AttachManager class : Allows movie to be added to the timeline using
// scripting. The movies will be added using the linkage name within the
// library. The attach method also returns the attached MovieClip to the
// timeline. This class is instatiated from the _level0 timeline using action
// script like
//
// attachManager = new AttachManager()
//
// var movie1 = attachManager.attach('movie1', 0, 0)
//
// \\\\\\\\\\\\\\\
class AttachManager{
//instance fields
private var attachArr:Array;
private var i:Number;
//constructor
function AttachManager(){
//holding array for all objects the manager holds
attachArr = new Array();
}
//attach : Attaches the object to the timeline using the parameters
// provided
//
//PARAMETERS :
//linkage:String : A string representing the linkage name for the object
// to attach
//xpos:Number : The X-Pos to use for the object to attach
//ypos:Number : The Y-Pos to use for the object to attach
//depth : The depth to use, where the object will be attached. In action
//script depth goes from 0 downwards where as in flash depth goes from 0
// upwards
//
//RETURNS :
//MovieClip : that may be used by the flash timeline via action script
public function attach(linkage:String, xpos:Number, ypos:Number,depth):MovieClip {
//get the depth if no depth exists yet
if(depth==undefined){
depth = getCurDepth();
}
//now do the attaching to _level0, using the paraaddEventListenermeters
// porovided
var tmpClip = _level0.attachMovie(linkage,linkage,depth,{_x:xpos,_y:ypos})
tmpClip._name=linkage;
//store the object in the holding array
attachArr.push(tmpClip)
//return the newly attached MovieClip
return tmpClip;
}
//getCurDepth : Get the HighestDepth for the timeline
//
//RETURNS :
//Number : that represents the HighestDepth for the timeline
public function getCurDepth():Number {
//return the depth
return _level0.getNextHighestDepth();
}
//killClip : Removes an object from the AttachManager
//
//PARAMETERS :
//clip : the object to remove from the internal holding array
public function killClip(clip) {
//remove the clip
_level0.removeMovieClip(clip);
}
//removeAllClips : Removes all objects from the AttachManager
public function removeAllClips() {
//remove all clips in holding array
for(i=0; i < attacharr.length;)
它是一个类,就像在任何其他面向对象语言中创建的那样。通过结合组件、库资源、舞台 Action Script 和外部 Action Script(*.as 文件),我们可以创建相当强大的 Flash 应用程序。哦,如果你想认真编写 Action Script,我建议你下载 Sepy Action Script 编辑器,它可以在这里找到。
重要提示:虽然舞台很方便,但如果你只使用舞台,它很快就会被图层/对象/动画弄得杂乱无章。更好的方法是开发独立的 UI 部分,每个部分都完成特定的任务,然后将这些任务创建成小的组件(最好是 `MovieClip` 对象),这些组件可以手动或通过 Action Script(本文资源部分有讨论)添加到舞台上,从而保持舞台整洁。一个非常好的 Flash 开发者可能只会在舞台上有一个图层,上面有一个带有 Action 的关键帧,该 Action 会以编程方式加载所有其他 UI 部分到舞台上。当然,每个组件也都有自己的舞台,Flash 就是这样工作的。每个 `MovieClip` 都有自己的舞台。
所以,我希望这能让你对创建 Flash 应用程序的基本想法有一个基本的了解。
WPF
WPF 是一个真正面向对象的语言,它也借鉴了 ASP.NET 的思想。例如,有视图和代码隐藏。视图部分由称为 XAML(可扩展标记语言)的代码构成,这是一种微软专有语言,看起来有点像 XML。但它比简单的 XML 强大得多。有可能完全用 XAML 创建一个事件/动画驱动的数据应用程序,而无需任何代码隐藏。如果使用了代码隐藏文件,代码隐藏文件将是 C#(正确的选择)或 VB.NET(不,别这样)。
那么,这一切是如何工作的?两个文件?
让我们看一个简单的例子,一个带有单个按钮控件的简单窗口。仅此而已。
这将产生以下 XAML:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="UntitledProject1.Window1"
x:Name="Window"
Title="Window1"
Width="640" Height="480">
<Grid x:Name="LayoutRoot">
<Button HorizontalAlignment="Left"
Margin="101,118,0,0" x:Name="btn1"
VerticalAlignment="Top" Width="188"
Height="67" Content="Button"/>
</Grid>
</Window>
我们可以很好地从代码隐藏文件中访问 `Button` 控件(`btn1`),如下所示:
但这怎么可能呢?嗯,实际发生的是,后台生成了一个额外的代码文件,其中包含 XAML 文件中定义的所有对象,这使得代码隐藏文件能够访问 XAML 定义的控件。这个奇迹归功于部分类支持。
让我们看看这个简单示例的生成代码文件,它是一个名为 `Window1.g.cs` 的文件,在编译过程中将放在 `\Obj` 文件夹中。
#pragma checksum "..\..\Window1.xaml" \
"{406ea660-64cf-4c82-b6f0-42d48172a799}"
"FADEDA8B7804EA63C81E418EDBA03A95"
//----------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:2.0.50727.1378
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//----------------------------------------------------------------------------
using System;
using System.Windows;
using System.Windows.Automation;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Effects;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;
using System.Windows.Media.TextFormatting;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace UntitledProject1 {
/// <summary>
/// Window1
/// </summary>
public partial class Window1 : System.Windows.Window,
System.Windows.Markup.IComponentConnector {
internal UntitledProject1.Window1 Window;
internal System.Windows.Controls.Grid LayoutRoot;
internal System.Windows.Controls.Button btn1;
private bool _contentLoaded;
/// <summary>
/// InitializeComponent
/// </summary>
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
public void InitializeComponent() {
if (_contentLoaded) {
return;
}
_contentLoaded = true;
System.Uri resourceLocater
= new System.Uri("/UntitledProject1;component/window1.xaml",
System.UriKind.Relative);
#line 1 "..\..\Window1.xaml"
System.Windows.Application.LoadComponent(this, resourceLocater);
#line default
#line hidden
}
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.ComponentModel.EditorBrowsableAttribute(
System.ComponentModel.EditorBrowsableState.Never)]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute(
"Microsoft.Design",
"CA1033:InterfaceMethodsShouldBeCallableByChildTypes")]
void System.Windows.Markup.IComponentConnector.Connect(
int connectionId, object target) {
switch (connectionId)
{
case 1:
this.Window = ((UntitledProject1.Window1)(target));
return;
case 2:
this.LayoutRoot = ((System.Windows.Controls.Grid)(target));
return;
case 3:
this.btn1 = ((System.Windows.Controls.Button)(target));
return;
}
this._contentLoaded = true;
}
}
}
这是我们的代码隐藏文件(C#):
using System;
using System.IO;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Navigation;
namespace UntitledProject1
{
public partial class Window1
{
public Window1()
{
this.InitializeComponent();
// Insert code required on object creation below this point.
}
}
}
从这三个列表可以看出,控件是如何通过 XAML 和代码隐藏文件访问的。但我们如何使用这些控件呢?嗯,它们只是类,所以我们可以调用它们的方法、设置属性并监听它们的事件。由于 .NET 是 100% 面向对象的,我们可以继承这些标准类,并在需要时重写相关方法。这可以在 XAML 文件或代码隐藏文件中完成。话虽如此,控件事件通常在代码隐藏中处理,在那里可以执行自定义逻辑。
与 Flash 不同,没有舞台;有一个用于动画的时间轴功能,但仅此而已。我现在不讨论它,因为它稍后会涵盖。本节只是为了让你了解 Flash 和 WPF 的工作方式。
控件
Flash
Flash 标准自带的控件不多,如下面的截图所示。
与 .NET 3.0 提供的控件相比,这似乎不是一个很大的控件集(请记住,我们仍然可以使用 `System.Windows.Forms.Dll` 来处理对话框,甚至可以在 WPF 应用程序中托管 Windows Forms 组件),但你需要了解的是,在 Flash 中,我们可以对任何东西应用动作(Action Script),而不仅仅是控件。这与 Windows 编程模型完全不同,在 Windows 编程模型中,我们使用控件事件来引起某些事情发生。例如,我可以在主舞台上有一个关键帧,上面应用了 Action Script,或者我可以动态加载一个电影,该电影可以包含自己的动作,并且还可以包含上面显示的任何控件,所有这些控件都暴露了它们自己的事件。实际上,Flash 中的任何对象都可以转换为 `Graphic`/`MovieClip` 或 `Button`,所有这些都支持附加 Action Script。另外,请记住,任何 AS2 代码也可以创建外部 Action Script 对象。
因此,表面上看,Flash 缺乏丰富的控件层次结构似乎不是什么大问题。
但自定义控件呢?
Flash 的另一个优点(至少是 AS2/AS3 版本的 Flash)是,你也可以像 WPF/WinForms 控件一样继承这些标准控件。所以它也相当可扩展。
我安装了 Flash MX 2004,如果我检查文件夹 `C:\Program Files\Macromedia\Flash MX 2004\en\First Run\Classes\mx\controls`,所有标准组件的源代码都存储在那里,我可以看到一堆不同的类。
这些看起来像什么?我们来看一个怎么样?选择一个漂亮的简单控件,比如一个按钮。嗯,这个 AS2 代码如下所示:
我知道这有很多代码,但我想看看一个标准的 Flash 控件是如何创建的,这可能会很有趣。
//****************************************************************************
//Copyright (C) 2003 Macromedia, Inc. All Rights Reserved.
//The following is Sample Code and is subject to all restrictions on
//such code as contained in the End User License Agreement accompanying
//this product.
//****************************************************************************
import mx.controls.SimpleButton;
import mx.core.UIObject;
import mx.core.UIComponent;
/**
* @tiptext click event
* @helpid 3168
*/
[Event("click")]
[TagName("Button")]
[IconFile("Button.png")]
/**
* Button class
* - extends SimpleButton
* - adds label and text with layout
* - adds ability to resize without distorting the skin
* @tiptext Button provides core button functionality. Extends SimpleButton
* @helpid 3043
*/
class mx.controls.Button extends SimpleButton
{
/**
* @private
* SymbolName for object
*/
static var symbolName:String = "Button";
/**
* @private
* Class used in createClassObject
*/
static var symbolOwner = mx.controls.Button;
var className:String = "Button";
function Button()
{
}
#include "../core/ComponentVersion.as"
/**
* number used to offset the label and/or icon when button is pressed
*/
var btnOffset:Number = 0;
/**
*@private
* Color used to set the theme color
*/
var _color = "buttonColor";
/**
*@private
* Text that appears in the label if no value is specified
*/
var __label:String = "default value";
/**
*@private
* default label placement
*/
var __labelPlacement:String = "right";
/**
//*@private
* store the linkage name of the icon at initalization
*/
var initIcon;
/**
* @private
* button state skin variables
*/
var falseUpSkin:String = "ButtonSkin";
var falseDownSkin:String = "ButtonSkin";
var falseOverSkin:String = "ButtonSkin"
var falseDisabledSkin:String = "ButtonSkin";
var trueUpSkin:String = "ButtonSkin";
var trueDownSkin:String = "ButtonSkin";
var trueOverSkin:String = "ButtonSkin";
var trueDisabledSkin:String = "ButtonSkin";
var falseUpIcon:String = "";
var falseDownIcon:String = "";
var falseOverIcon:String = "";
var falseDisabledIcon:String = "";
var trueUpIcon:String = "";
var trueDownIcon:String = "";
var trueOverIcon:String = "";
var trueDisabledIcon:String = "";
/**
* @private
* list of clip parameters to check at init
*/
var clipParameters:Object = { labelPlacement:1, icon:1, toggle:1,
selected:1, label:1 };
static var mergedClipParameters:Boolean
= UIObject.mergeClipParameters(mx.controls.Button.prototype.clipParameters,
SimpleButton.prototype.clipParameters);
var labelPath:Object;
var hitArea_mc:MovieClip;
var _iconLinkageName:String;
var centerContent : Boolean = true;
var borderW : Number = 1;// buffer value for border
/**
* @private
* init variables. Components should implement this method and call super.init() to
* ensure this method gets called. The width, height and clip parameters will not
* be properly set until after this is called.
*/
function init(Void):Void
{
super.init();
}
/**
* @private
*
*/
function draw()
{
super.draw();
if (initIcon != undefined)
_setIcon(initIcon);
delete initIcon;
}
/**
* This method calls SimpleButton's onRelease()
*/
function onRelease(Void):Void
{
super.onRelease();
}
/**
* @private
* create children objects. Components implement this method to create the
* subobjects in the component. Recommended way is to make text objects
* invisible and make them visible when the draw() method is called to
* avoid flicker on the screen.
*/
function createChildren(Void):Void
{
super.createChildren();
}
/**
* @private
* sets the skin state based on tag and linkage name
*/
function setSkin(tag:Number,linkageName:String, initobj:Object):MovieClip
{
return super.setSkin(tag, linkageName, initobj);
}
/**
* @private
* sets the old skin's visibility to false and sets the new skin's
*visibility to true
*/
function viewSkin(varName:String):Void
{
var skinStyle = getState() ? "true" : "false";
skinStyle += enabled ? phase : "disabled";
super.viewSkin(varName,{styleName:this,borderStyle:skinStyle});
}
/**
* @private
* Watch for a style change.
*/
function invalidateStyle(c:String):Void
{
labelPath.invalidateStyle(c);
super.invalidateStyle(c);
}
/**
* @private
* sets the color to each one of the states
*/
function setColor(c:Number):Void
{
for (var i=0;i<8;i++)
{
this[idNames[i]].redraw(true);
}
}
/**
* @private
* this is called whenever the enabled state changes.
*/
function setEnabled(enable:Boolean):Void
{
labelPath.enabled = enable;
super.setEnabled(enable);
}
/**
* @private
* sets same size of each of the states
*/
function calcSize(tag:Number, ref:Object):Void
{
if ((__width == undefined) || (__height == undefined)) return;
if(tag < 7 )
{
ref.setSize(__width,__height,true);
}
}
/**
* @private
* Each component should implement this method and lay out
* its children based on the .width and .height properties
*/
function size(Void):Void
{
setState(getState());
setHitArea(__width,__height);
for (var i = 0; i < 8; i++)
{
var ref = idNames[i];
if (typeof(this[ref]) == "MovieClip")
{
this[ref].setSize(__width, __height, true);
}
}
super.size();
}
/**
* sets the label placement of left,right,top, or bottom
* @tiptext Gets or sets the label placement relative to the icon
* @helpid 3044
*/
[Inspectable(enumeration="left,right,top,bottom"defaultValue="right")]
function set labelPlacement (val:String)
{
__labelPlacement = val;
invalidate();
}
/**
* returns the label placement of left,right,top, or bottom
* @tiptext Gets or sets the label placement relative to the icon
* @helpid 3045
*/
function get labelPlacement():String
{
return __labelPlacement;
}
/**
* @private
* use to get the label placement of left,right,top, or bottom
*/
function getLabelPlacement(Void):String
{
return __labelPlacement;
}
/**
* @private
* use to set the label placement to left,right,top, or bottom
*/
function setLabelPlacement(val:String):Void
{
__labelPlacement = val;
invalidate();
}
/**
* @private
* use to get the btnOffset value
*/
function getBtnOffset(Void):Number
{
if(getState())
{
var n = btnOffset;
}
else
{
if(phase == "down")
{
var n = btnOffset;
}
else
{
var n = 0;
}
}
return n;
}
/**
* @private
* Controls the layout of the icon and the label within the button.
* note that layout hinges on a variable, "centerContents", which is
*set to true in Button.
* but false in check and radio.
*/
function setView(offset:Number):Void
{
var n = offset ? btnOffset : 0;
var val = getLabelPlacement();
var iconW : Number = 0;
var iconH : Number = 0;
var labelW : Number = 0;
var labelH : Number = 0;
var labelX : Number = 0;
var labelY : Number = 0;
var lp = labelPath;
var ic = iconName;
// measure text size
var textW = lp.textWidth;
var textH = lp.textHeight;
var viewW = __width-borderW-borderW;
var viewH = __height-borderW-borderW;
lp._visible = true;
if (ic!=undefined) {
iconW = ic._width;
iconH = ic._height;
}
if (val == "left" || val == "right")
{
if (lp!=undefined)
{
lp._width = labelW = Math.min(viewW-iconW, textW+5);
lp._height = labelH = Math.min(viewH, textH+5);
}
if (val == "right")
{
labelX = iconW;
if (centerContent) {
labelX += (viewW - labelW - iconW)/2;
}
ic._x = labelX - iconW;
}
else
{
labelX = viewW - labelW - iconW;
if (centerContent) {
labelX = labelX / 2;
}
ic._x = labelX + labelW;
}
ic._y = labelY = 0;
if (centerContent) {
ic._y = (viewH - iconH )/2;
labelY = (viewH - labelH )/2
}
if (!centerContent)
ic._y += Math.max(0, (labelH-iconH)/2);
}
else
{
if (lp!=undefined) {
lp._width = labelW = Math.min(viewW, textW+5);
lp._height = labelH = Math.min(viewH-iconH, textH+5);
}
labelX = (viewW - labelW )/2;
ic._x = (viewW - iconW )/2;
if (val == "top")
{
labelY = viewH - labelH - iconH;
if (centerContent) {
labelY = labelY / 2;
}
ic._y = labelY + labelH;
}
else
{
labelY = iconH;
if (centerContent) {
labelY += (viewH - labelH - iconH)/2;
}
ic._y = labelY - iconH;
}
}
var buff = borderW + n;
lp._x = labelX + buff;
lp._y = labelY + buff;
ic._x += buff;
ic._y += buff;
}
/**
* sets the associated label text
* @tiptext Gets or sets the Button label
* @helpid 3046
*/
[Inspectable(defaultValue="Button")]
function set label(lbl:String)
{
setLabel(lbl);
}
/**
* @private
* sets the associated label text
*/
function setLabel(label:String):Void
{
if (label=="")
{
labelPath.removeTextField();
refresh();
return;
}
if (labelPath == undefined)
{
var lp = createLabel("labelPath", 200, label);
lp._width = lp.textWidth + 5;
lp._height = lp.textHeight +5;
lp.visible = false;
}
else
{
labelPath.text = label;
refresh();
}
}
/**
* @private
* gets the associated label text
*/
function getLabel(Void):String
{
return labelPath.text;
}
/**
* gets the associated label text
* @tiptext Gets or sets the Button label
* @helpid 3047
*/
function get label():String
{
return labelPath.text;
}
function _getIcon(Void):String
{
return _iconLinkageName;
}
/**
* sets the associated icon
* use setIcon() to set the icon
* @tiptext Gets or sets the linkage identifier of the Button's icon
* @helpid 3404
*/
function get icon():String
{
if (initializing)
return initIcon;
return _iconLinkageName;
}
/**
*@private
* sets the icon for the falseUp, falseDown and trueUp states
* use setIcon() to set the icon
*/
function _setIcon(linkage):Void
{
if (initializing)
{
if (linkage == "" ) return;
initIcon = linkage;
}
else
{
if ( linkage == ""){removeIcons();return;}
super.changeIcon(0,linkage);
super.changeIcon(1,linkage);
super.changeIcon(4,linkage);
super.changeIcon(5,linkage);
_iconLinkageName = linkage;
refresh();
}
}
/**
* @sets the icon for all states of the button
* @tiptext Gets or sets the linkage identifier of the Button's icon
* @helpid 3048
*/
[Inspectable(defaultValue="")]
function set icon(linkage)
{
_setIcon(linkage);
}
/**
* @private
* @method to set the hit area dimensions
*/
function setHitArea(w:Number,h:Number)
{
if (hitArea_mc == undefined)
createEmptyObject("hitArea_mc",100); //reserved depth for hit area
var ha = hitArea_mc;
ha.clear();
ha.beginFill(0xff0000);
ha.drawRect(0,0,w,h);
ha.endFill();
ha.setVisible(false);
}
<bindable>
[ChangeEvent("click")]
var _inherited_selected : Boolean;
}
这看起来相当高级,不是吗?看起来很像 Java/C#,我会这么说。花括号/属性,嗯。
在 Flash 的后续版本中,现在还可以导入第三方开发的、使用 Action Script 电影剪辑等的新控件。
WPF
如果我们看 WPF 提供的标准控件(Expression Blend 的截图)如下所示:
我们可以立即看到控件多得多。这些控件也可以被继承,这使得 WPF 非常强大。然而,我们仍然只能根据用户与预先存在的控件事件的交互或非 UI 类中生成的事件来执行操作。请记住,在 Flash 中,任何帧或任何对象都有可能运行 Action Script。因此,尽管 WPF 中的控件更多,但 Flash 中可能存在许多令人惊叹的控件,如果我们考虑一个带有 Action 的 `MovieClip` 是一个用户控件的话。我想是的,对吧?
资源
Flash
资源在 Flash 中非常非常重要,资源被放置在一个库中。库可以包含各种东西,例如声音、图形、`MovieClip`、补间动画等。一旦对象存储在库中并被命名(首先必须允许将其存储在库中),就可以将其拖到舞台上并在任何舞台图层上使用。但这并不是库所允许我们做的全部。库还扮演着另一个非常重要的角色,那就是链接。
链接是一个关键话题,所以请注意。链接的作用是允许 Action Script 以编程方式操作库中的对象。这将包括将对象带到舞台/从舞台移除,调用对象的 方法,订阅对象的事件。它基本上将所有库对象函数暴露给 Action Script。
事实上,任何好的 Flash 开发者都应该这样做。这都是关于链接的。通过创建小的组件(实际上是 `MovieClip`)并在需要时才将其实例化(添加到舞台),Flash 就能利用某种 JIT(即时)模型。我认为这相当于在 C# 中声明一个新对象,如 `SomeObject so = new SomeObject();
。我们要看一个例子吗?
下图说明了这一点。它显示了库中有一个 `MovieClip` 对象,其链接名称为“`moviePhoneOFF`”,这允许通过 Action Script 创建和操作 `MovieClip`,就像我在本例中所做的那样。
WPF
资源也是 WPF 的重要组成部分,但它们与 Flash 谈论资源的方式相当不同。在讨论 WPF 中的资源时,我们需要考虑两件事;首先,在文件级别,然后是应用程序级别。让我们先谈谈文件级别。
为了让下一部分内容有意义,你可能需要使用 Visual Studio 2005/2008。
你可以使用文件属性来指示项目系统在文件上执行哪些操作。
生成操作属性
`生成操作`属性指示 Visual Studio 在执行生成时对文件做什么。`生成操作`可以有以下几个值之一:
- 无 - 文件不包含在项目输出组中,也不在生成过程中进行编译。例如,包含文档的文本文件,如 Readme 文件。
- 编译 - 文件被编译到生成输出中。此设置用于代码文件。
- 内容 - 文件不编译,但包含在内容输出组中。例如,这是 `.htm` 或其他类型 Web 文件的默认值。
- 嵌入资源 - 此文件将作为 DLL 或可执行文件嵌入到主项目生成输出中。通常用于资源文件。指定“嵌入资源”会将资源放在程序集 (assembly) 的 `.mresource` 部分。
- 页面 - 生成操作“页面”会生成一个高效的 BAML 文件。
- 资源 - 生成操作“资源”会生成一个嵌入的 XAML 文件。
`生成操作`的默认值取决于添加到项目的文件扩展名。例如,如果你将一个 Visual Basic 项目添加到解决方案资源管理器中,`生成操作`的默认值是编译,因为 `.vb` 扩展名表示可以编译的代码文件。文件名和扩展名显示在解决方案资源管理器中。
下面是 Visual Studio WPF 项目中资源文件 `Dictionary1.xaml` 的示例:
这就是我们指定生成操作的方式,但这只是故事的一半。我们仍然需要首先创建资源文件。我们可以使用资源文件来添加字符串、图像和图标。这与 .NET 2.0 相同。但对于动态加载的 XAML 资源文件呢?嗯,这些也称为资源,但是一种特殊的资源,称为 `ResourceDictionary`,它包含标记,导入后可以在 `ResourceDictionary` 的使用者中使用。
那么,我们如何创建一个 `ResourceDictionary` 呢?嗯,在 XAML 中,我们会这样做:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
//makrup resources to be defined here
</ResourceDictionary>
这将允许另一个 XAML/代码隐藏文件使用这些资源。所以,如果你来自 Flash 背景,你可以认为这相当于链接步骤。我们创建一个 `MergedDictionary`,其中将包含 XAML 资源文件。完成此步骤后,就可以在代码中使用 `MergedDictionary` 中的资源了。让我们看一个定义 `MergedDictionary` 的示例:
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Dictionary1.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
布局
Flash
Flash 实际上没有任何布局操作,如 Dock 或 Anchor。它本身没有任何容器控件。任何对象都简单地放置在画布上(实际上是舞台上)。所以这方面没什么可说的。
WPF
另一方面,WPF 有很多专门处理布局的控件。这些控件都是某种形式的 Panel。有:
StackPanel
:`StackPanel` 通过将子元素一个接一个地堆叠起来进行布局。元素按它们在 XAML 文件中出现的顺序“堆叠”(在 XML 术语中为文档顺序)。项目可以垂直堆叠(默认),也可以水平堆叠。
<StackPanel>
<TextBlock FontSize="16" Foreground="#58290A">
Items inside a StackPanel</TextBlock>
<Button>Item 2</Button>
<Border BorderBrush="#feca00" BorderThickness="2">
<TextBlock>Item 3</TextBlock>
</Border>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock FontSize="16"
Foreground="#58290A">
Items inside a StackPanel</TextBlock>
<Button>Item 2</Button>
<Border BorderBrush="#feca00" BorderThickness="2">
<TextBlock>Item 3</TextBlock>
</Border>
</StackPanel>
WrapPanel
:`WrapPanel` 从左到右布局项目。当一行项目填满了可用的水平空间后,面板会将下一项换行到下一行(类似于文本的布局方式)。
<WrapPanel>
<TextBlock FontSize="16"
Foreground="#58290A">
Items inside a WrapPanel</TextBlock>
<Button>Item 2</Button>
<Border BorderBrush="#feca00" BorderThickness="2">
<TextBlock>Item 3</TextBlock>
</Border>
</WrapPanel>
DockPanel
:`DockPanel` 允许元素停靠在容器的边缘,类似于 Windows Forms 的停靠方式。项目按它们在 XAML 文件中出现的顺序(文档顺序)停靠。最后一个 `Border` 元素填满了所有剩余空间,因为它没有为它指定 `DockPanel.Dock` 属性。
<DockPanel>
<TextBlock FontSize="16" DockPanel.Dock="Top"
Foreground="#58290A">
Items inside a DockPanel</TextBlock>
<Button DockPanel.Dock="Left">Item 2</Button>
<Border BorderBrush="#feca00" BorderThickness="2">
<TextBlock>Item 3</TextBlock>
</Border>
</DockPanel>
Canvas
:`Canvas` 面板类似于旧的富客户端布局工作方式,你可以通过设置 `Top` 和 `Left` 属性来控制事物的绝对位置。此外,你可以设置 `Right` 来代替设置 `Left` 来定位项目,或者设置 `Bottom` 来代替设置 `Top`。如果你同时指定了 `Left` 和 `Right`,则 `Right` 值将被忽略,元素不会改变大小以使这两个值都正确,同样 `Top` 优先于 `Bottom`。在 XAML 文件中较早声明的元素可能会出现在较晚声明的元素之后(如果它们的位置重叠)。
<Canvas>
<TextBlock FontSize="16"
Canvas.Top="10" Canvas.Left="20"
Foreground="#58290A">
Items inside a Canvas</TextBlock>
<Button Canvas.Bottom="25"
Canvas.Right="50">Item 2</Button>
<Border BorderBrush="#feca00" BorderThickness="2"
Canvas.Top="20" Canvas.Left="50">
<TextBlock>Item 3</TextBlock>
</Border>
</Canvas>
Grid
:`Grid` 面板是一个极其灵活的面板,可以用来实现其他面板控件所能完成的几乎所有功能(尽管通常不如它们方便)。`Grid` 面板允许你使用 XAML 定义网格的行和列,然后使用特定于网格的属性将控件放置在网格的列中。元素可以跨越多行或多列。网格将自动使所有行和列大小相同(基于内容的大小),但你可以使用星号表示法指定行和列的比例大小,或指定绝对宽度或高度。星号表示法可以在下面的示例代码中看到,其中一列的宽度是另一列的两倍,方法是将 `Width` 属性设置为“2*”。下面的示例还显示了其中一行的高度设置为绝对值。当调整包含网格的窗体大小时,这些差异更容易显现出来,因为网格默认会扩展以填满可用空间。
<Grid Margin="10" ShowGridLines="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="25" />
<RowDefinition />
<RowDefinition Height="2*"/>
</Grid.RowDefinitions>
<TextBlock FontSize="16"
Foreground="#58290A"
Grid.Column="0" Grid.Row="0"
Grid.ColumnSpan="2"
>
Items inside a Grid</TextBlock>
<Button Grid.Column="0" Grid.Row="1">
Item 2</Button>
<Border BorderBrush="#feca00" BorderThickness="2"
Grid.Column="1" Grid.Row="2">
<TextBlock>Item 3</TextBlock>
</Border>
</Grid>
数据绑定
Flash
在 Flash MX 2004 及更高版本中,添加了一整套新的数据访问控件;如下所示:
通过使用这些新控件,Flash 能够执行数据绑定;它还具有通常想要的用于绑定数据的控件,例如:
DataGrid
ComboBox
CheckBox
文本框
列表
如你所见,Flash 支持 `DataSet`、`XMLConnector` 和 `WebServiceConnector`。这些大概是最典型的组件。Flash 的奇怪之处在于它需要与承载 Flash 对象的页面(请记住,Flash 是客户端,数据库是服务器端)进行通信才能从底层数据库获取数据。通常,Flash 会使用 `DataSet` 绑定到 `DataGrid`。`DataSet` 将由一个 `XMLConnector` 填充,该连接器包含 XML 数据,该数据通过 Flash 与其托管页面通信来填充。例如,这可以是 PHP 或 ASP 页面。Flash 也可以直接与 Web 服务通信并绑定。以下三个链接详细讨论了每种选项,如果你感兴趣的话。
- 使用 PHP 创建可绑定的 DataSet,连接到 MySQL:http://www.flash-db.com/Tutorials/databind/CatalogTutorial.php?page=5
- 使用 ASP (Classic) 创建可绑定的 DataSet:http://www.adobe.com/devnet/flash/articles/flashpro_asp.html
- 与 Web 服务通信并绑定:http://livedocs.adobe.com/flash/mx2004/main_7_2/wwhelp/wwhimpl/js/html/wwhelp.htm
Flash 的文档也非常好,你只需要搜索一下。本文底部的“参考资料”部分包含指向 Flash 文档的链接。
WPF
我不得不说,我认为 WPF 在数据绑定方面占优势。WPF 中的数据绑定功能简直是令人难以置信。你可以从以下来源绑定属性:
- 将控件属性绑定到另一个控件的属性
- 将控件绑定到 Web 服务结果
- 将控件绑定到 XML 数据源(RSS/XML 文档等)
- 将控件绑定到 `DataSet`
- 将控件绑定到语言集成查询 (LINQ 项目) 的结果;LINQ 本身就非常有趣,并且非常值得单独研究;看一眼你就会惊叹不已。
- 绑定到任意 .NET CLR 对象(例如,一个 C# `Person` 类)
这简直是疯狂的。老实说,这可能超出了这篇对比文章的范围。但是,我建议如果你想了解更多关于 WPF 绑定工作方式的信息,可以阅读以下文章:
样式/模板/皮肤和主题
Flash
Flash 没有任何关于这些 WPF 技术的概念。虽然一种可能的解决方案是使用 XML 文件来指定样式、位置、皮肤颜色等。Flash 具有出色的 XML 支持,所以我会走这条路,也见过别人这样使用。
WPF
Windows Presentation Foundation (WPF) 的样式和模板化是指一套功能(样式、模板、触发器和故事板),它允许应用程序、文档或用户界面 (UI) 设计者创建视觉上引人入胜的应用程序,并标准化其产品的特定外观。作者或设计者可以根据应用程序进行广泛的自定义外观,但需要强大的样式和模板化模型才能维护和共享外观。Windows Presentation Foundation (WPF) 提供了这种模型。
样式
你可以将样式视为一种方便的应用属性值的方式。让我们看一个小的示例,它样式化了一个标准的按钮:
<Style TargetType="Button">
<!--Set to true to not get any properties from the themes.-->
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid>
<Ellipse Fill="{TemplateBinding Background}"/>
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
当应用于 `Button` 时,结果如下:
模板
对于大多数控件,都有外观和行为。以按钮为例:外观是你可以按下的凸起区域,行为是响应点击而引发的 Click 事件。
有时,可能有一个控件提供了你需要的功能,但没有你想要的外观。到目前为止,我们已经看到可以使用样式设置器来设置属性值以影响控件的外观。然而,要更改控件的结构或设置组成控件的组件的属性值,你需要使用 `ControlTemplate`。
在 WPF 中,控件的 `ControlTemplate` 定义了控件的外观。你可以通过为控件定义新的 `ControlTemplate` 来更改控件的结构和外观。在许多情况下,这足以让你不必编写自己的自定义控件。如果你没有为你的控件定义自己的 `ControlTemplate`,你将获得与系统主题匹配的默认模板,这就是 `Button` 控件获得默认外观的原因。
需要记住的一点是,一旦你为你的控件创建了 `ControlTemplate`,你就是替换了整个 `ControlTemplate`。例如,你可能会这样定义你的 `Button` 的 `ControlTemplate`:
<Style TargetType="ListBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBox">
<Border CornerRadius="5"
Background="{TemplateBinding ListBox.Background}">
<ScrollViewer HorizontalScrollBarVisibility="Auto">
<StackPanel Orientation="Horizontal"
VerticalAlignment="Center"
HorizontalAlignment="Center"
IsItemsHost="True"/>
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
此示例为 `ListBox` 定义了一个 `ControlTemplate`。
皮肤和主题
我在这里要有点厚颜无耻,直接引用一篇完整的文章,因为它解释得更好。这篇文章是 Josh Smith 写的(不出所料),链接就在这里。
2D 图形/3D 图形
Flash
Flash 本质上是一个基于矢量的动画环境,但也有 3D 的选项。最值得注意的是,Swift3D 允许用户创建一个 3D 模型并直接导出到 Flash。
WPF
WPF 对矢量和 3D 都有良好的支持。它在框架中内置了对 3D 的原生支持。例如,让我们看一个 3D 按钮样式。代码如下:
<!-- 3D Media Buttons -->
<Style x:Key="btn3DStyle" TargetType="{x:Type Button}">
<Style.Resources>
<Storyboard x:Key="Spin">
<DoubleAnimation
Storyboard.TargetName="CubeRotation"
Storyboard.TargetProperty="Angle"
BeginTime="0:0:0"
Duration="0:0:1" From="0"
To="360" DecelerationRatio="0.5"
AccelerationRatio="0.5" />
<DoubleAnimation
Storyboard.TargetName="CubeRotation"
Storyboard.TargetProperty="Angle"
BeginTime="0:0:1"
Duration="0:0:1" From="360"
To="0" DecelerationRatio="0.5"
AccelerationRatio="0.5" />
<DoubleAnimation
Storyboard.TargetName="CubeScale"
Storyboard.TargetProperty="ScaleX"
BeginTime="0:0:0"
Duration="0:0:1"
From="0.5" To="0.75" />
<DoubleAnimation
Storyboard.TargetName="CubeScale"
Storyboard.TargetProperty="ScaleX"
BeginTime="0:0:1"
Duration="0:0:1" From="0.75"
To="1.0" />
<DoubleAnimation
Storyboard.TargetName="CubeScale"
Storyboard.TargetProperty="ScaleY"
BeginTime="0:0:0"
Duration="0:0:1" From="0.5"
To="0.75" />
<DoubleAnimation
Storyboard.TargetName="CubeScale"
Storyboard.TargetProperty="ScaleY"
BeginTime="0:0:1"
Duration="0:0:1" From="0.75"
To="1.0" />
<DoubleAnimation
Storyboard.TargetName="CubeScale"
Storyboard.TargetProperty="ScaleZ"
BeginTime="0:0:0"
Duration="0:0:1" From="0.5"
To="0.75" />
<DoubleAnimation
Storyboard.TargetName="CubeScale"
Storyboard.TargetProperty="ScaleZ"
BeginTime="0:0:1"
Duration="0:0:1" From="0.75"
To="1.0" />
</Storyboard>
</Style.Resources>
<Setter Property="Width" Value="100"/>
<Setter Property="Height" Value="100"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<ControlTemplate.Triggers>
<Trigger Property="Button.IsMouseOver" Value="true">
<Trigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource Spin}"/>
</Trigger.EnterActions>
</Trigger>
</ControlTemplate.Triggers>
<Viewport3D>
<Viewport3D.Camera>
<PerspectiveCamera Position="4,4,4"
LookDirection="-1,-1,-1" />
</Viewport3D.Camera>
<Viewport3D.Children>
<ModelVisual3D>
<ModelVisual3D.Content>
<DirectionalLight Direction="-0.3,-0.4,-0.5" />
</ModelVisual3D.Content>
</ModelVisual3D>
<ModelVisual3D x:Name="Cube">
<ModelVisual3D.Transform>
<Transform3DGroup>
<RotateTransform3D>
<RotateTransform3D.Rotation>
<AxisAngleRotation3D x:Name="CubeRotation"
Axis="1,2,3" Angle="0" />
</RotateTransform3D.Rotation>
</RotateTransform3D>
<ScaleTransform3D x:Name="CubeScale"
ScaleX="1" ScaleY="1"
ScaleZ="1" CenterX="0"
CenterY="0" CenterZ="0" />
</Transform3DGroup>
</ModelVisual3D.Transform>
<ModelVisual3D.Content>
<GeometryModel3D x:Name="OB_Cube">
<GeometryModel3D.Material>
<DiffuseMaterial>
<DiffuseMaterial.Brush>
<VisualBrush ViewportUnits="Absolute"
Transform="1,0,0,-1,0,1">
<VisualBrush.Visual>
<Border
Background="{Binding Path=
Background, RelativeSource=
'{RelativeSource TemplatedParent}'}">
<Label
Content="{Binding Path=Content,
RelativeSource='{RelativeSource
TemplatedParent}'}" />
</Border>
</VisualBrush.Visual>
</VisualBrush>
</DiffuseMaterial.Brush>
</DiffuseMaterial>
</GeometryModel3D.Material>
<GeometryModel3D.Geometry>
<MeshGeometry3D x:Name="ME_Cube"
Positions="1,1,-1 1,-1,-1 -1,-1,-1 -1,1,
-1 1,1,1 -1,1,1 -1,-1,1 1,-1,1 1,1,-1 1,1,
1 1,-1,1 1,-1,-1 1,-1,-1 1,-1,1 -1,-1,
1 -1,-1,-1 -1,-1,-1 -1,-1,1 -1,1,1 -1,1,
-1 1,1,1 1,1,-1 -1,1,-1 -1,1,1"
TriangleIndices="0 1 2 0 2 3 4 5 6 4 6
7 8 9 10 8 10 11 12 13 14 12 14 15 16 17
18 16 18 19 20 21 22 20 22 23"
TextureCoordinates="0,1 0,0 1,0 1,1 1,1 -0,
1 0,-0 1,0 1,1 -0,1 0,-0 1,0 1,0 1,1 -0,1 0,
-0 -0,0 1,-0 1,1 0,1 1,-0 1,1 0,1 -0,0"/>
</GeometryModel3D.Geometry>
</GeometryModel3D>
</ModelVisual3D.Content>
</ModelVisual3D>
</Viewport3D.Children>
</Viewport3D>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
其结果是一个普通的按钮,在其表面显示视频,就像一个 3D 立方体,如下图所示:
动画
Flash
Flash 允许用户创建几乎无限的动画。我们可以使用运动路径,创建补间动画,补间动画使用关键帧来让我们指定控制对象外观的某些帧,中间部分会自动插值。在关键帧处,我们可以指定大小、颜色、位置、透明度、旋转等任何属性。
在我看来,就动画而言,Flash 完胜;毫无疑问,遥遥领先。它是教父、兄弟、姐妹、母亲、远房表亲等等,集于一身。它太棒了。这是为什么呢?原因有两个。
原因 1:Flash 中的舞台
回想一下,舞台是自下而上的图层和从左到右的时间轴。你可能会说,不是那么棒。但它允许用户在舞台上的不同时间点拥有同一图层上的不同对象。例如,假设我们有以下内容:
一个图层,在 `Frame1` 有一个正方形,在 `Frame2` 有一个圆形。
这现在看起来很酷了吗?你说不。但它确实很酷。原因如下。我们可以在给定帧的图层上拥有任何我们想要的东西。存在于某一帧的对象然后可以被动画化。如果我们可以在任何我们想要的帧引入新项目,考虑到所有图层,然后考虑到每个 `MovieClip` 都可以有这样的安排,并且任何数量的 `MovieClip` 都可以手动或以编程方式引入主舞台,我们突然就拥有了一个非常强大的动画环境。如果这还不够,我们还可以通过一个疯狂的家伙编写的第三方 Action Script 来实现所有这些,该脚本允许用户通过 Action Script 控制补间动画(Flash 的基本动画对象)。
只需要在主舞台 Action Script 中引用补间动画库,如下所示:
#include "lmc_tween.as"
然后,每个 `MovieClip` 对象都可以通过 Action Script 进行控制,例如使用 `alphaTo` 方法。
Flash Player 6
用法
my_mc.alphaTo(alpha, seconds, animtype, delay, callback, extra1, extra2)
参数
`alpha` 是 `MovieClip` `_alpha` 属性的结束值;所有其他参数与 `tween()` 方法相同。
Returns
无。
一个例子可能是(这在 `FlashDemo1.zip` 的 `BlockManager.as` 中使用):
tmpLoc.alphaTo(100,.1,'linear',i/20,{func:'playClip',scope:this,args:[tmpLoc]})
第三方补间动画库可作为 `.MXP` 文件使用,这是一个 Macromedia 扩展文件,官方可在作者的网站上获取,该网站是 http://laco.wz.cz/tween/?page=download - 该网站还提供 API 文档。
WPF
WPF 确实支持动画。但在我看来,这与 Flash 没法比。原因是它没有舞台的概念;有一个时间轴,但它只用于 `DoubleAnimationUsingKeyFrame`。而 Flash 在所有地方都使用时间,你甚至可以在特定时间替换图层上显示的内容。
为了进一步理解我的意思,以及为什么 Flash 更好,我们需要了解 WPF 应用程序是如何创建的。它们是使用 XAML(一种树形结构)或使用代码隐藏过程式代码创建的。那么,为什么这不像 Flash 那样灵活呢?嗯,在这两种情况下,页面的内容都不是那么动态。对于 XAML,树是相当静态的;设计时有的,运行时也会有。好吧,我们可以添加新的控件,并在代码隐藏中对其进行动画处理,这实际上是一个部分解决方案,但对我来说似乎很笨拙。我们也可以有不同的页面一个接一个地加载,或者我们可以简单地销毁当前的 XAML 树并从磁盘加载一个新的 XAML 文档树,这会提供一些灵活性。
我只是觉得 Flash 更好。图层上有任何你想要的东西在任何帧上,时间在所有阶段的所有地方都被使用,并且能够随意将项目带入和移出舞台。我的意思是,看看 `FlashDemo3.zip`,你觉得在 WPF 中做起来有多容易,更不用说 Silverlight 了,它已经是 WPF 的一个精简版本。
总之,要使用 WPF 中的动画,我们可以使用 XAML 或过程式代码。让我们看看,好吗?
WPF 中有两种选择:你可以使用 `DoubleAnimation`,或使用 `DoubleAnimationUsingKeyFrame`。
DoubleAnimation
XAML
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
WindowTitle="Fading Rectangle Example">
<StackPanel Margin="10">
<Rectangle
Name="MyRectangle"
Width="100"
Height="100"
Fill="Blue">
<Rectangle.Triggers>
<!-- Animates the rectangle's opacity. -->
<EventTrigger RoutedEvent="Rectangle.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="MyRectangle"
Storyboard.TargetProperty="Opacity"
From="1.0" To="0.0" Duration="0:0:5"
AutoReverse="True" RepeatBehavior="Forever" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Rectangle.Triggers>
</Rectangle>
</StackPanel>
</Page>
C#
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Shapes;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace SDKSample
{
public class RectangleOpacityFadeExample : Page
{
private Storyboard myStoryboard;
public RectangleOpacityFadeExample()
{
NameScope.SetNameScope(this, new NameScope());
this.WindowTitle = "Fading Rectangle Example";
StackPanel myPanel = new StackPanel();
myPanel.Margin = new Thickness(10);
Rectangle myRectangle = new Rectangle();
myRectangle.Name = "myRectangle";
this.RegisterName(myRectangle.Name, myRectangle);
myRectangle.Width = 100;
myRectangle.Height = 100;
myRectangle.Fill = Brushes.Blue;
DoubleAnimation myDoubleAnimation = new DoubleAnimation();
myDoubleAnimation.From = 1.0;
myDoubleAnimation.To = 0.0;
myDoubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(5));
myDoubleAnimation.AutoReverse = true;
myDoubleAnimation.RepeatBehavior = RepeatBehavior.Forever;
myStoryboard = new Storyboard();
myStoryboard.Children.Add(myDoubleAnimation);
Storyboard.SetTargetName(myDoubleAnimation, myRectangle.Name);
Storyboard.SetTargetProperty(myDoubleAnimation,
new PropertyPath(Rectangle.OpacityProperty));
// Use the Loaded event to start the Storyboard.
myRectangle.Loaded += new RoutedEventHandler(myRectangleLoaded);
myPanel.Children.Add(myRectangle);
this.Content = myPanel;
}
public void myRectangleLoaded(object sender, RoutedEventArgs e)
{
myStoryboard.Begin(this);
}
}
}
DoubleAnimationUsingKeyFrames
就像普通动画一样,但这次我们有了关键帧,我们可以指定对象在时间 x 时的外观。
<!-- Using Paced Values. Rectangle moves between key frames at
uniform rate except for first key frame
because using a Paced value on the first KeyFrame in a
collection of frames gives a time of zero. -->
<Rectangle Height="50"
Width="50" Fill="Orange">
<Rectangle.RenderTransform>
<TranslateTransform x:Name="TranslateTransform04"
X="10" Y="270" />
</Rectangle.RenderTransform>
<Rectangle.Triggers>
<EventTrigger RoutedEvent="Rectangle.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimationUsingKeyFrames
Storyboard.TargetName="TranslateTransform04"
Storyboard.TargetProperty="X"
Duration="0:0:10"
RepeatBehavior="Forever">
<!-- KeyTime properties are expressed with values of Paced.
Paced values are used when a constant rate is desired.
The time allocated to a key frame with a KeyTime of
"Paced"
is determined by the time allocated to the other key
frames of the animation. This time is calculated to
attempt to give a "paced" or "constant velocity"
for the animation. -->
<LinearDoubleKeyFrame Value="100" KeyTime="Paced" />
<LinearDoubleKeyFrame Value="200" KeyTime="Paced" />
<LinearDoubleKeyFrame Value="500" KeyTime="Paced" />
<LinearDoubleKeyFrame Value="600" KeyTime="Paced" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Rectangle.Triggers>
</Rectangle>
自定义控件
Flash
如我们已经看到的,Flash 支持通过继承标准控件来创建自定义控件。创建完全新控件并不是 Flash 的方式;一个人可能会制作一个 `MovieClip` 来完成任务。这就是控件。这里没什么更多可说的了。
WPF
由于 WPF 使用 .NET 3.0 Framework,我们当然可以继承 `UserControl` 或 `Control` 来创建新控件。我们也可以简单地继承并重写现有控件的方法。这都是标准的面向对象的东西。但一切皆有可能。一个有趣的控件展示在这里,Christian Graus 和 Nishant Sivakumar 继承了 `AnimationTimeline` 类,并使其能够动画化 `GridLength`。
应用程序结构
正如我所说,我在这两个领域都不是专家,但这就是我开发每种世界的东西的方式:
Flash
创建执行特定任务的小部分。将它们制成 `MovieClip` 对象,并具有正确的库链接并导出用于 Action Scripting。创建外部 Action Script 对象,尝试使用面向对象的设计方法。考虑类。使用 Sepi 编辑器,它很好。保持舞台整洁。
WPF
考虑你要做什么,使用最适合的方法来完成工作。如果是面向 XAML 的,就使用 XAML;否则就使用代码。确实使用 ValueConvertors、资源文件/MergedDictionaries、Templates、Styles,并且不要仅仅因为你之前在 .NET 2.0 中这样做就继承一切。
在 WPF 中,继承的需求并不总是那么高;你通常可以通过 Style 或 Template 来实现你想要的功能。哦,而且不要因为你能做到就过度使用动画。同样,保持类清晰定义并特定于它们应该执行的任务。
API
Flash
Flash 确实有一套相当广泛的类。让我们看下图:
我们可以看到 Flash 提供了几个包,我们甚至还有一个 Client/Server 包来处理 XML,我们还可以使用套接字。对于一个小插件来说,这真的很酷。
WPF
那么,从哪里开始呢?WPF 依赖于 .NET 3.0 Framework,WPF 是其中的一部分。 .NET 3.0 Framework 非常庞大,拥有成千上万的类、接口、结构等。
下面是 .NET 3.0 Framework 中与 WPF 相关的主要包的截图。使用这些类,程序员能够完成上面提到的所有主题,当然,由于它们只是类,我们可以继承它们并将它们用于我们的目的。Macromedia(嗯,Adobe 我猜)也对 AS3 做了同样的事情(实际上在 AS2 中也可以继承其他类)。AS3 是一个完全重写,并且语法非常像 Java,它还允许使用完整的面向对象技术 - 不仅仅是继承,还有方法重写、多态性等。但本文(仅仅因为我拥有 Flash MX 2004)都是关于 AS2 的。
总之,下面是 WPF .NET Framework 的主要包的截图。但请注意,未来版本可能会增加。请记住,这是 WPF 的第一个版本。并且要记住,这些单个包中的大多数都包含 10-100 个不同的类。这是一个庞大的框架。
如果我们比较 .NET 3.0 Media 包和它的 Flash 等价物呢?
.NET 3.0 的 Media 类比 Flash 中的要多得多。但我见过一些相当酷的 Flash 应用。而且别忘了,Silverlight (WPF/e) 实际上是 Flash 的等价物,它不会拥有 WPF 的全部功能。它将在一个安全的沙箱中运行,并且将提供完整的 WPF API 的一个子集。所以这个完整的类列表可能不适用于 Silverlight。
我个人对此的看法是,有时少即是多。Flash 可能不是万能的,但它拥有的东西,它做得非常好。然而,也有人认为 .NET 3.0 只是 .NET 2.0 的扩展,所以我们也可以使用所有好的 .NET 2.0 功能,这确实可以。哦,还有 LINQ 和 Generics,我们可以在 WPF 中使用,但在 Flash 中不能。嗯,争论还在继续。
演示项目
Flash 演示
我在本文顶部包含了三个 zip 文件,它们都展示了不同类型的 Flash 应用程序。`FlashDemo1.zip` 和 `FlashDemo3.zip` 主要包含动画和一些 AS2 外部文件,`FlashDemo2.zip` 更像一个应用程序,可能更符合 WPF 应用程序的开发方式。我没有在此包含 WPF 应用程序,因为在 www.codeproject.com 上可以找到大量已完成的 WPF 项目,或者在 www.google.com 上搜索。
FlashDemo1.zip
只需解压缩 zip 文件,然后打开 `Video.exe`,我已经为你制作了一个漂亮的执行文件。
zip 文件还包含所有用于查看和运行 Flash 应用程序的文件,如果你愿意的话。这个演示是关于如何使用外部 AS2 文件和使用补间动画(WPF 中的动画),以及一个配有音轨的短片。
FlashDemo2.zip
只需解压缩 zip 文件,然后打开 `MMDA_Assign2.exe`,我已经为你制作了一个漂亮的执行文件。zip 文件还包含所有用于查看和运行 Flash 应用程序的文件,如果你愿意的话。
这是一个手机演示视频,提供了电话簿、模拟通话、弹出式帮助和网络摄像头使用。
这张图片可能对你也有用,以便看到所有演示的功能。
FlashDemo3.zip
这是一个我为我曾经拥有和经营的一个硬核科技唱片公司 Surgeon 16 Recordings 所做的动画 Flash 网站。我的名字是 Dr Machette;以防万一有人感兴趣,我的唱片公司有一个 discogs 链接这里,还有我自己;这个连接是在一个叫做 SpecialForces 的德国厂牌。
只需解压缩 zip 文件,然后在你喜欢的浏览器中打开 `S16_Launcher.html`。zip 文件还包含所有用于查看和运行 Flash 应用程序的文件,如果你愿意的话。
我没有包含音乐文件,否则下载会非常大。
这个实际上导航到了五个区域;你可以使用底部显示的导航按钮导航到每个区域。这是我第一个项目,使用了大量的动画,所以舞台非常繁忙。这是一个可以使用库和链接(我们前面讨论过的)来更整洁地完成的例子。
WPF 演示
我没有为这篇文章创建新的 WPF 演示。但我过去写过一些,你可以在我的文章的 Windows Presentation 部分找到它们。
就是这样
虽然本文中的代码量不多,但我写得很开心,希望对某人有所帮助。
你有什么看法?
我想请求一下,如果你喜欢这篇文章,请投它一票,并留下一些评论,这样我就知道这篇文章的水平是否合适,以及它是否包含了人们需要了解的内容。
结论
我希望这篇文章涵盖了 Flash 和 WPF 的主要方面。对我来说,我喜欢两者,原因不同,但如果我要做一个纯动画应用程序,我会使用 Flash,如果要做一个更数据驱动的应用程序,我会使用 WPF。我接下来会尝试 Silverlight,所以到时候我会告诉你进展如何。我将从一个简单的动画应用程序开始,然后逐步深入。总之,如果你能读到这里,做得好。
参考资料
历史
- v1.0 - 2007/09/13:初始发布。