辅助功能*

Charles Ward

Charles Ward

Adobe

出版日期:
2008 年 2 月 28 日
用户级别:
中/高级
产品:
Adobe AIR

拖动、复制和粘贴数据

Scrappy 范例应用程序展示 Adobe AIR 中拖放和复制粘贴 API 的以下功能:

  • 准备要通过拖放或复制粘贴操作进行传输的数据和对象。
  • 开始拖动操作。
  • 接收放置的信息或对象。
  • 使用 Adobe AIR 和 Flash 对象呈现标准数据格式。
  • 写入系统剪贴板。
  • 从系统剪贴板读取。
  • 使用标准快捷键进行复制和粘贴。

Scrappy 应用程序

图 1。本 Scrappy 范例应用程序演示 Adobe AIR 中的拖动、复制和粘贴功能。

注意: 本范例应用程序按原样提供, 用于教学目的。

要求

若要充分利用本篇文章, 您需要以下软件和文件:

Adobe AIR

Adobe Flash CS3 Professional

为 Flash CS3 Professional 提供的 Adobe AIR 更新

范例文件:

本范例应用程序包括以下文件和类:

  • Scrappy-app.xml: AIR 应用程序描述符文件
  • Scrappy.as: 应用程序主文件 (ActionScript 格式) ;本文对有关代码进行了详细的论述
  • Scrappy.fla: 与 Adobe Flash CS3 配合使用的 Flash 文档
  • Scrap.as: 用于显示被拖动或被粘贴信息的基类
  • TextScrap.as: 扩展 Scrap, 用于显示文本
  • BitmapScrap.as: 扩展 Scrap, 用于显示图像
  • HTMLScrap.as: 扩展 Scrap, 用于显示 HTML 和 XML 文档
  • FileScrap.as: 扩展 Scrap, 用于加载和显示文件
  • about.html: 介绍本范例应用程序, 并提供要向主窗口中拖动的一些项目
  • AIRAliases.js: 为 AIR 和 Flash 类定义较短的 JavaScript 别名。由 about.html 使用
  • FlexLoader.html: 用于在不触发安全性异常的情况下加载 SWF 非应用程序文件
  • file.tiff: 要拖动的范例文件。由 about.html 使用
  • AIR 图标文件范例

必备知识

应具备使用 Flash CS3 构建应用程序的一般经验。有关使用此快速入门指南的详细信息, 请参阅用 Flash 构建快速入门范例应用程序

测试应用程序

若要使用 Scrappy, 请将文件、位图、URL 和文本拖至窗口中。此应用程序接受 AIR 所支持的任何标准格式的拖动项目。还可以向窗口中粘贴 (Ctrl+V 或 Insert) 项目, 以及剪切 (Ctrl+X)、复制 (Ctrl+C)、替换 (Ctrl+V 或 Insert) 和删除 (Del) 现有项目。

注意: 并非所有应用程序都以 AIR 所支持的格式向剪贴板张贴位图数据。

了解代码

对于此应用程序所使用的全部 ActionScript 类, 本文将不一一介绍。有关详细信息, 请参阅《ActionScript 3.0 语言参考》

创建拖动目标

Scrappy 使用一个全屏子画面作为窗口背景以及拖放操作的目标。若要使 Sprite 或其它交互对象可以作为拖动动作的接收者, 必须侦听从该对象调度的 NativeDragEvent。

Scrappy 类使用以下代码创建窗口、放置目标子画面和事件侦听器:

public var dragTarget:Sprite = new Sprite();
public var about:NativeWindow;
 
public function Scrappy():void{
    super();
    addEventListener(Event.ADDED_TO_STAGE, onStaged);
    dragTarget.focusRect = false;
    addChild(dragTarget);
}

private function onStaged(event:Event):void{
    stage.align = StageAlign.TOP_LEFT;
    stage.scaleMode = StageScaleMode.NO_SCALE;
    stage.stageFocusRect = false;
    drawBackground();
    addMenu();

    //拖放侦听器
    dragTarget.addEventListener(NativeDragEvent.NATIVE_DRAG_ENTER,onDragIn);
    dragTarget.addEventListener(NativeDragEvent.NATIVE_DRAG_OVER,onDragOver);
    dragTarget.addEventListener(NativeDragEvent.NATIVE_DRAG_DROP,onDrop);
    dragTarget.addEventListener(NativeDragEvent.NATIVE_DRAG_EXIT,onDragExit);
 
    //UI 侦听器
    stage.addEventListener(MouseEvent.MOUSE_DOWN,onMouseDown);
    stage.addEventListener(KeyboardEvent.KEY_DOWN,onKeyDown);
    stage.nativeWindow.addEventListener(NativeWindowBoundsEvent.RESIZE, onResize);
}

拖动目标的重要事件有:

  • nativeDragEnter, 通知您拖动动作何时进入显示对象区域内
  • nativeDragExit, 通知您拖动动作何时离开显示对象区域
  • nativeDragDrop, 通知您在显示对象上已发生放置

如果需要跟踪鼠标在目标上的位置, 则还可以侦听 nativeDragOver 事件代替 nativeDragEnter 事件。每当鼠标移动时, 目标就调度 nativeDragOver 事件 (并且间隔很短)。Scrappy 使用 nativeDragOver 事件不断更新放置操作以取代默认操作。

舞台上的 keyDown 事件用于检查是否有粘贴命令。将事件侦听器附加至舞台, 因为子画面只有在具有焦点时才接收键盘事件。

将数据放置到目标上

若要使用户可以将数据放置到目标上, 通常需要:

  1. 检查数据的格式。
  2. NativeDragManager.acceptDrop() 接受拖动动作。
  3. 处理 nativeDragDrop 事件。

检查所拖动的数据

若要检查被拖入目标中的数据, 请访问 NativeDragEvent 对象的 clipboard 属性。Scrappy 类为 nativeDragEnter 事件定义了以下处理函数:

private function onDragIn(event:NativeDragEvent):void{
    var transferable:Clipboard = event.clipboard;


    if(transferable.hasFormat(ClipboardFormats.BITMAP_FORMAT) ||
       transferable.hasFormat(ClipboardFormats.FILE_LIST_FORMAT) ||
       transferable.hasFormat(ClipboardFormats.TEXT_FORMAT) ||
       transferable.hasFormat(ClipboardFormats.URL_FORMAT) ||
       transferable.hasFormat(Scrap.SCRAP_FORMAT)){
            NativeDragManager.dropAction = NativeDragActions.MOVE;
            NativeDragManager.acceptDragDrop(dragTarget);
    } else {trace("Unrecognized format");}
}

Scrappy 应用程序可以接受四种标准格式中的每一种。它还定义了一种自定义格式, 以字符串常量 Scrap.SCRAP_FORMAT 指定, 该常量包含对 Scrappy 应用程序自身内创建的 Scrap 对象的引用。

接受动作

调用 NativeDragManager.acceptDrop() 使传入的交互对象可以接收放置动作。NativeDragManager 改变显示光标, 表示可以放置。如果用户在位于同一个交互对象上时 (以及 nativeDragExit 事件发生之前) 释放鼠标按键, 则指定的显示对象将调度一个 nativeDragDrop 事件。

设置放置操作

如果不设置放置操作, 则由拖动发起方对象所允许的操作以及用户所按下的任何功能键来确定默认操作。例如, 如果拖动发起方允许所有三种可能的操作 — 复制、移动和链接, 则复制为默认操作。用户可以通过按 Ctrl、Command 或 Shift 键修改该操作。

如果在放置事件之前设置放置操作, 则使用指定操作替代默认操作, 且用户无法用功能键选择其它操作。由于用户在窗口内拖动时更有可能希望移动对象而非复制对象, 因此在应用程序内拖动对象时 Scrappy 设置移动的放置操作。从操作系统或从其它应用程序将对象拖动至 Scrappy 中时将遵守默认行为。

接受放置

对符合条件的交互对象发生放置之后, 可以通过 nativeDragDrop 事件对象的 clipboard 属性访问数据。

Scrappy 将 Clipboard 对象从 nativeDragDrop 事件传递至 Scrap 类的工厂方法, 该方法根据数据格式创建新的 Scrap 对象, 并向舞台添加返回的 Scrap 对象。

private function onDrop(event:NativeDragEvent):void{
    var scrap:Scrap =
        Scrap.createScrap(event.clipboard, event.stageX, event.stageY, event.dropAction);
    dragTarget.addChild(scrap);
}

用 Scrap 类显示信息

scrap 包中的类作为放置或粘贴至 Scrappy 窗口中的信息的容器。Scrap 基类扩展 Sprite, 添加了用于处理用户与 scrap 对象交互的函数, 这些交互包括拖出窗口、删除和复制。Scrap 类还实现了一个用于创建新 scrap 对象的静态工厂方法。

Scrap 子类

Scrap 子类扩展 Scrap 基类, 用于处理可以传输的数据的标准格式。为了呈现数据, 这些类向 scrap 添加子显示对象, 如 TextField 或 HTMLControl。将 scrap 拖出应用程序时, 这些类还以正确格式向 Clipboard 对象添加数据。这些 Scrap 子类有:

  • TextScrap: 使用 TextField 显示文本数据。
  • BitmapScrap: 使用 Bitmap 对象显示位图数据。
  • HTMLScrap: 使用 HTMLControl 加载并显示 URL (包括文件 URL)。
  • FileScrap: 加载并显示文件内容, 或显示文件名和图标。对于 Scrappy 可以识别的少数文件类型, FileScrap 构造函数加载数据, 并创建一种其它类型的 Scrap 对象以呈现数据。

Scrap 工厂

scrap 工厂类 Scrap.createScrap() 获取 Clipboard 对象, 检查可用的数据格式, 然后创建适当类型的 scrap 对象以显示数据。

public static function createScrap(data:Clipboard, placeX:int, placeY:int, action:String):Scrap
{
    var scrap:Scrap;
    trace(data.formats.length + " formats found: " + " " + data.formats);
    if(data.hasFormat(Scrap.SCRAP_FORMAT)){
        scrap =
            data.getData(Scrap.SCRAP_FORMAT, ClipboardTransferMode.ORIGINAL_ONLY) as Scrap;
            if((action == NativeDragActions.COPY) || (scrap == null)){
                var clipping:Clipboard = new Clipboard();
                for each (var format:String in data.formats){
                    if(format != Scrap.SCRAP_FORMAT){
                        clipping.setData(format, data.getData(format), false);
                    }
                }
                scrap = Scrap.createScrap(clipping, placeX, placeY, NativeDragActions.COPY);
            }
    } else if(data.hasFormat(ClipboardFormats.BITMAP_FORMAT)){
        scrap = new BitmapScrap(BitmapData(data.getData(ClipboardFormats.BITMAP_FORMAT)));
    } else if(data.hasFormat(ClipboardFormats.FILE_LIST_FORMAT)){
        var dropfiles:Array = data.getData(ClipboardFormats.FILE_LIST_FORMAT) as Array;
        for each (var file:File in dropfiles)
        {
            scrap = new FileScrap(file);
        }
    } else if(data.hasFormat(ClipboardFormats.TEXT_FORMAT)){
        scrap = new TextScrap(String(data.getData(ClipboardFormats.TEXT_FORMAT)));
    } else if(data.hasFormat(ClipboardFormats.URL_FORMAT)){
        scrap = new HTMLScrap(String(data.getData(ClipboardFormats.URL_FORMAT)));
    }
    scrap.x = placeX + offset.x + highlightOffset;
    scrap.y = placeY + offset.y + highlightOffset;
    return scrap;
}

如果 Clipboard 对象中包含由 Scrap.SCRAP_FORMAT 字符串常量指定的格式, 则拖动必定来自 Scrappy 应用程序本身 (或来自使用“SCRAP”格式名的其它 AIR 应用程序)。Scrappy 尝试从剪贴板检索 Scrap 对象。如果剪贴板上的 Scrap 对象为空 (例如可能在复制和粘贴操作之间已将其删除) 或放置操作为复制, 则 Scrappy 转而制作对象的副本。制作副本的过程是创建 Clipboard 对象, 并向新的对象复制除“SCRAP”格式之外的所有数据。随后 createScrap() 方法以新的 Clipboard 调用自身, 从而使用原始数据创建 scrap 的副本。

拖动数据

若要使用户可以拖动来自 AIR 应用程序内的数据或对象, 必须:

  1. 响应 mouseDownmouseMove 事件。
  2. 创建包含要拖动的数据或对象的 Clipboard 对象。
  3. 如有需要, 还可以创建拖动代理图像。
  4. 通过调用 NativeDragManager.doDrag(), 从 mouseDownmouseMove 事件处理函数开始拖动操作。

注意: 若要在应用程序内拖动 Sprite 对象, 可以使用 Sprite 类的 startDrag() 方法替代 NativeDragManager。Sprite 式的拖动不允许将子画面拖至其它应用程序, 但这对于改变对象在窗口内的位置反而更好。

开始拖动

Scrap 类中定义了针对在 Scrappy 应用程序中拖动对象所使用的方法和事件处理函数。当 Scrap 对象检测到 mouseDown 事件时, 将调用以下事件处理函数:

private function onMouseDown(event:MouseEvent):void{
    var transferObject:Clipboard = addTransferableData();
 
    var allowedActions:NativeDragOptions = new NativeDragOptions();
    allowedActions.allowLink = false;
 
    var proxyImage:BitmapData = getImage();
    Scrap.offset.x = -mouseX;
    Scrap.offset.y = -mouseY;
 
    NativeDragManager.doDrag(this, transferObject, getImage(), offset, allowedActions);
 
    grabFocus();
    event.stopPropagation();
}

此事件处理函数执行以下操作:

  • 创建 Clipboard 对象
  • 创建 NativeDragOptions 对象, 用于指定允许的操作
  • 为拖动代理图像创建 BitmapData 对象
  • 设置 Point 对象的坐标, 以防拖动开始的时候代理图像出现跳动
  • 开始拖动
  • 管理窗口内的焦点

创建 Clipboard 对象

通过调用 Scrap 的函数 addTransferableData() 获取 Clipboard 对象, mouseDown 事件处理函数为拖动做好准备。addTransferableData() 函数创建 Clipboard 对象, 并通过使用自定义格式名添加对当前 Scrap 对象的引用:

protected function addTransferableData():Clipboard{
    var transfer:Clipboard = new Clipboard();
    transfer.setData("SCRAP",this,true);
    return transfer;
}

在每个 Scrap 子类中都覆盖 addTransferableData() 函数, 用于在除了引用格式之外以适合该子类的格式添加数据。例如, BitmapScrap 类用以下定义覆盖 addTransferableData() 函数:

protected override function addTransferableData():Clipboard{
    var transfer:Clipboard = super.addTransferableData();
    transfer.setData(ClipboardFormats.BITMAP_FORMAT,picture.bitmapData,true);
    return transfer;
}

指定允许的操作

创建 Clipboard 对象之后, 处理函数创建 NativeDragOptions 对象, 用于定义对剪贴板数据所允许的操作。默认情况下, 新构造的 NativeDragOptions 对象允许所有操作。由于链接操作对 Scrappy 没有意义, 因此将 allowLink 属性设置为 false。不更改控制复制和移动操作的属性。

创建拖动代理图像

Scrappy 拍摄所拖动对象的快照, 方法是向 BitmapData 对象绘制该对象。为此目的, Scrap 类定义了 getImage() 函数:

public function getImage():BitmapData{
    var image:BitmapData;
    try {
        image = new BitmapData(width + 2 * highlightOffset, 
                               height + 2 * highlightOffset,
                               true, 0x00ffffff);
        image.draw(this, new Matrix(1,0,0,1,highlightOffset,highlightOffset));
    } catch (e:ArgumentError){
        image = null;
    }
    return image;
}

该函数创建的 BitmapData 对象略大于 Scrap 对象的当前大小 (为了考虑加亮显示矩形的大小)。该函数将 BitmapData 构造函数的 transparent 参数设置为 true, fillColor 参数设置为 ARGB 格式的颜色值 0x00ffffff, 此值的 alpha 字节被设置为零, 以使拖动代理图像的背景透明。

默认情况下, 拖动代理图像的原点位于光标的热点处。在拖动开始时, 尤其是使用类似被拖动对象的图像时, 这会使图像出现跳动。若要补偿, 可以提供偏移坐标, 从而将图像相对于光标重新定位。MouseEvent 对象在 mouseXmouseY 属性中提供鼠标位置相对于对象原点的坐标。用这些坐标补偿拖动代理图像可以将所拖动对象与代理图像对齐, 以便不出现跳动:

Scrap.offset.x = -mouseX;
Scrap.offset.y = -mouseY;

遗憾的是, 在放置对象时不报告偏移量值。这可能导致放置对象时在反方向上出现跳动。Scrap 类在静态 Point 对象中保存偏移量值, 并使用这些值调整所放置对象的 x 和 y 坐标。

开始拖动操作

调用 doDrag() 将开始拖动动作中由 NativeDragManager 控制的部分:

NativeDragManager.doDrag(dragInitiator:InteractiveObject,
                         clipboard:Clipboard,
                         dragImage:BitmapData = null,
                         offset:Point = null,
                         allowedActions:NativeDragOptions = null)

该方法的 dragInitiator 参数指定调度 nativeDragStartnativeDragUpdatenativeDragComplete 事件的交互对象。Scrap 对象将 dragInitiator 设置为自身 (this), 但也可以使用任何交互对象。在许多情况下, 从容器组件发起拖动要比从被拖动项目本身发起拖动更有意义。

clipboard 参数为包含要传输的信息的 Clipboard 对象。调用 doDrag() 后, 就只能在 NativeDragEvents 的事件处理函数中访问该对象。

焦点管理

管理焦点并非拖动操作的一部分。但是, 有必要让您可以选择要复制、剪切、粘贴和删除的对象。

为了管理焦点, 处理函数用 grabFocus() 函数在对象上设置舞台焦点, 然后停止事件传播, 以防事件向上冒泡至父显示容器。 (父显示容器通过清除焦点响应 mouseDown 事件, 因此必须在此停止事件传播, 否则对象始终无法获得焦点。)

完成放置

发生放置时, 作为发起方传递至 NativeDragManager.doDrag() 的对象将调度 nativeDragComplete 事件。发起方设置了众多受到允许的操作, 事件的 dropAction 属性中报告由用户或放置目标从中选择的操作。

复制和粘贴数据

与拖放相比, 复制和粘贴属于简单操作。静态属性 Clipboard.generalClipboard 代表操作系统剪贴板。用 Clipboard.generalClipboard.setData() 可以直接向剪贴板写入数据, 而用 Clipboard.generalClipboard.getData() 可以读取数据。

在编辑菜单上选择相关命令时将调用复制和粘贴命令。对于菜单命令定义了等效键, 因此也可以使用标准的快捷键。

粘贴

Scrappy 在主窗口上实现粘贴命令。作为对粘贴命令的响应, 应用程序将剪贴板数据传递至工厂方法 Scrap.createScrap(), 此方法也用于为放置操作创建 scrap。

public function doPaste():void{
    var scrap:Scrap = Scrap.createScrap(Clipboard.generalClipboard,
    stage.stageWidth/4,stage.stageHeight/4);
    addChild(scrap);
}

所粘贴的 scrap 始终放置在舞台上的同一点。Scrap 基类定义一个类似的命令 doReplace()。此方法除了粘贴剪贴板内容之外, 还删除所选的对象。

复制

Scrap 基类实现复制。该函数调用 addTransferable() 函数, 用于从 Scrap 对象获取数据 (此函数还用于拖放)。无法直接将 Clipboard 对象赋给 Clipboard.generalClipboard 属性, 因此必须单独复制每种格式:

public function doCopy():void{
    var transferObject:Clipboard = addTransferableData();
    for each( var format:String in Clipboard.generalClipboard.formats){
        Clipboard.generalClipboard.setData(format, transferObject.getData(format),true);
    }
}

剪切

剪切就是将 scrap 对象复制到剪贴板, 然后将其删除。

关于作者

Charles Ward 是一名技术作者, 它喜欢钻研新技术。在之前的 (专业) 职业生涯中, 他曾参与创建开拓性的计算机游戏, 如 Falcon 3.0 和 4.0 以及 Star Trek: A Final Unity。在闲暇时间, Charles 喜欢自由潜水以及和他的孩子们玩耍。