65.9K
CodeProject 正在变化。 阅读更多。
Home

比较 Flash 和 WPF

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.61/5 (89投票s)

2007年9月23日

CPOL

33分钟阅读

viewsIcon

296182

downloadIcon

2863

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 标准自带的控件不多,如下面的截图所示。

flashcomponents.png

与 .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`,所有标准组件的源代码都存储在那里,我可以看到一堆不同的类。

flashcontrols.png

这些看起来像什么?我们来看一个怎么样?选择一个漂亮的简单控件,比如一个按钮。嗯,这个 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 服务通信并绑定。以下三个链接详细讨论了每种选项,如果你感兴趣的话。

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:初始发布。
© . All rights reserved.