macOS 开发 - 讨论 drag-drop

文章来源:苹果官方文档

介绍拖放

Cocoa 让我们能够实现时髦高雅的拖放功能,在应用内部或者在应用之间。这个编程主题讲解了如何用少数几个方法来实现拖放。

本文结构

在此处和 dragging 协议讲解里的文字里,术语 dragging session 是指整个过程,包括一张图片的选择、拖、放和目的地的接收或驳回。一个 dragging 操作就是目的地在图片被释放的时候接收了图片的行为。dragging 源是“拥有”被拖的图片的对象;它是进行 dragging session 的方法的参数。
dragging 是一个可视化的操作。要成为 dragging 操作的源或目的地,这个对象必须表示为屏幕的一部分真实区域;因此,只有 window 和 view 对象可以成为拖的源和目的地。(注意源视图和没必要和上面定义的 dragging 源是相同的对象。)NSWindow 和 NSView 提供方法来管理拖一个对象的用户界面。只需要实现 NSDraggingSource 或 NSDraggingDestination 协议中的少数几个方法,具体实现哪个要看你的 window 或 view 子类是源还是目的地。

dragging 协议在这些章节中讲解:

  • Dragging 源
  • Dragging 目的地

如何接收一次 drag 在这些章节中讲解:

  • 接收 drag 操作
  • Dragging 文件

关于拖放经常被问到的问题在这个章节中说明:

  • 经常被问到的问题

Dragging 源

一个 dragging session 通过用户在 window 或 view 里点击和移动鼠标来初始化。NSView 和 NSWindow 实现了方法 dragImage:at:offset:event:pasteboard:source:slideBack: 来管理 dragging session。在 NSView 或 NSWindow 子类里的 mouseDown: 或 mouseDragged: 方法里调用这个方法。提供一个图片以在拖的过程中显示,一个 pasteboard 保管了数据,还有一个对象作为数据的“拥有者”或 dragging 源。在 dragging session 中,dragging 源被发送由 NSDraggingSource 协议定义的消息来执行所有必要的操作,如下所述。

注意:NSView 也实现了 convenience 方法 dragFile:fromRect:slideBack:event:,用于被拖的对象是文件的时候。NSView 然后自己管理图片、pasteboard 和 源消息。

拖操作

NSDraggingSource 方法中只有一个需要被实现:draggingSourceOperationMaskForLocal:。这个方法声明了源可以被实施的操作类型。表格 1 列出了可用的拖操作。(在 Java 中,常量被定义在 NSDraggingInfo 命名空间,以 NS 为前缀。)方法应该返回一个允许的类型按位或组合或 NSDragOperationNone,在没有操作被允许的情况下。

表格 1 可用的拖操作

dragging 操作 含义
NSDragOperationCopy 数据由可以被拷贝的图像表示
NSDragOperationLink 数据可以被分享
NSDragOperationGeneric 操作可以被目的地定义
NSDragOperationPrivate 操作被私人地在源和目的地之间达成。
NSDragOperationMove 数据可以被移动
NSDragOperationDelete 数据可以被删除
NSDragOperationEvery 以上全部
NSDragOperationAll 弃用。用 NSDragOperationEvery 替代。
NSDragOperationNone 一个拖操作都不给。

如果这个拖是全部在自己的应用中进行的或者是在自己的和另一个应用中间进行,允许的操作可能会有不同。传递给 draggingSourceOperationMaskForLocal: 的标记指明了是邻近或内部的拖。

用户可以按下辅助按键来进一步选择要执行哪个操作。如果 control、option 或 command 键被按下,源的操作 mask 被过滤到只包含表格 2 中给定的操作。要阻止辅助键修改 mask,你的 dragging 源应该实现 ignoreModifierKeysWhileDragging 然后返回 YES。

表格 2 modifier 键选择的拖操作

辅助按键 dragging 操作
Control NSDragOperationLink
Option NSDragOperationCopy
Command NSDragOperationGeneric

拖消息

在拖的过程中,源对象被发送一系列消息来通知它拖操作的状态。在拖最开始的时候,源被发送 draggedImage:beganAt: 消息。每次被拖的对象移动了,源就被发送一个 draggedImage:movedTo: 消息。最终,当用户已经释放了鼠标按钮并且目的地执行了放操作或拒收了,源会被发送 draggedImage:endedAt:operation: 消息。operation 参数是目的地执行的拖操作,或是 NSDragOperationNone 如果拖失败了的话。(在 Java 里,这些方法的名字为 startedDraggingImage、movedDraggingImage 和 finishedDraggingImage。)

dragging 源通常不需要实现这些方法里的每一个。但如果你要支持 NSDragOperationMove 或 NSDragOperationDelete 操作,需要实现 draggedImage:endedAt:operation: 来从源中移除被拖的数据。(注意一个 NSDragOperationDelete 操作在拖动任意对象到dock中的垃圾桶图标的时候被触发。)

被拖的图片

一个 dragging session 中被拖的图片只是简单地表示 pasteboard 上的数据。尽管 dragging 目的地可以访问图片,它主要还是关心图片表示的 pasteboard 中的数据 —— 目的地最终执行的 dragging 操作还是基于 pasteboard 数据,而不是图片本身。

当 dragging session 由 NSView 方法 dragFile:fromRect:slideBack:event: 启动的时候,NSView 使用该文件的 Finder 图标作为图片。对于你自定义的拖,需要配一个合适的图片??赡馨ㄏ允镜氖莅胪该鞯慕赝迹热绫谎≈械奈淖植糠?,或是数据的符号化表示,比如拖电子表格数据的时候用一个表格图标。

Dragging 目的地

要接收拖操作,必须注册你的 window 或 视图会接收的 pasteboard 类型,通过发送 registerForDraggedTypes: 消息,在 NSWindow 和 NSView 中都定义了,然后实现几个 NSDraggingDestination 协议中的方法。dragging session 中,候选目的地只有在注册了可以匹配被拖的 pasteboard 数据类型的时候才会接收 NSDraggingDestination。目的地在图片进入、内部移动以及退出或释放内部边界的时候接收这些消息。
尽管 NSDraggingDestination 被声明为一个 informal 协议,你创建用来实现协议的 NSWindow 和 NSView 子类只需要实现相关的方法即可。(NSWindow 和 NSView 类为全部这些方法都提供了私有实现。)一个 window 对象或它的 delegate 可以实现这些方法;但是,delegate 的实现级别更高,如果两个地方都有实现的话。

目的地消息的sender

NSDraggingDestination 方法的每一个都只有一个参数:sender,调用方法的对象。在 NSDraggingDestination 方法的实现中,目的地可以发送 NSDraggingInfo 协议消息给 sender 来获取当前 dragging session 上的更多信息,比如查询 dragging pasteboard 或者源的操作 mask。在 Java 里,sender 是一个 NSDragDestination 对象,实现了 NSDraggingInfo 接口。

Dragging Pasteboard

尽管一个标准的 dragging pasteboard(用 [NSPasteboard pasteboardWithName:NSDragPboard] 获得)被提供为获得 dragging 数据的一种便捷方式,但无法保证这会是一次跨进程拖使用的 pasteboard。因此,要确?;竦谜返?pasteboard,你的代码应该使用 [sender draggingPasteboard]。

目的地消息的顺序

六个 NSDraggingDestination 方法被调用在一个确定的顺序中:

  • 当图片被拖进目的地的边界时,目的地被发送一个 draggingEntered: 方法。这个方法会返回一个值,指明目的地将要执行的 dragging 操作。
  • 图片留在目的地的时候,一系列 draggingUpdated: 消息被发送。方法会返回一个值,指明目的地将要执行的 dragging 操作。
  • 如果图片被拖出目的地,draggingExited: 被发送,并且 NSDraggingDestination 消息队列会停止。如果它再次进入,队列会重新开始(用一个新的 draggingEntered: 消息)。
  • 如果图片被释放了,它会滑回源(然后打断队列)或一个 prepareForDragOperation: 消息被发送到目的地,根据最近最近一次调用 draggingEntered: 或 draggingUpdated: 的返回值。
  • 如果 prepareForDragOperation: 消息返回了 YES,一个 performDragOperation: 消息被发送。
  • 最终,如果 performDragOperation: 返回了 YES,concludeDragOperation: 被发送。

接收拖操作

这篇文档展示了一段示例代码,可以让一个 view(或 window)接收几种数据类型的 dragging session,基于被拖的类型执行不同的拖操作。样例实现可以接收一种颜色或是一个文件。被拖的颜色会被拷贝,文件会被链接或拷贝。
在一个 view 可以接收拖操作之前,需要注册可以接收的数据类型,通过调用它的 registerForDraggedTypes:,像这样:

[self registerForDraggedTypes:[NSArray arrayWithObjects:
            NSColorPboardType, NSFilenamesPboardType, nil]];

现在,不管什么时间只要一个由这些数据类型之一的数据类型 dragging session 进入了 view 的边界,view 就会被发送一系列 NSDraggingDestination 消息。下面的代码是一个得到 draggingEntered: 发送的初始化简单例子。方法从 sender 获得了 dragging pasteboard 和可用的拖操作。如果 pasteboard 包含颜色数据并且源对象许可了拖动,方法会返回 NSDragOperationGeneric,指示目的地对象许可 pasteboard 上的颜色数据的拖动。如果 pasteboard 包含了一个文件名并且源许可了链接,方法会返回 NSDragOperationLink,指示目的地许可链接。如果源不允许链接,目的地也会检查是不是有拷贝操作可以作替代,如果是就返回 NSDragOperationCopy。如果这些测试都失败了,方法返回 NSDragOperationNone。

- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender {
    NSPasteboard *pboard;
    NSDragOperation sourceDragMask;
 
    sourceDragMask = [sender draggingSourceOperationMask];
    pboard = [sender draggingPasteboard];
 
    if ( [[pboard types] containsObject:NSColorPboardType] ) {
        if (sourceDragMask & NSDragOperationGeneric) {
            return NSDragOperationGeneric;
        }
    }
    if ( [[pboard types] containsObject:NSFilenamesPboardType] ) {
        if (sourceDragMask & NSDragOperationLink) {
            return NSDragOperationLink;
        } else if (sourceDragMask & NSDragOperationCopy) {
            return NSDragOperationCopy;
        }
    }
    return NSDragOperationNone;
}

随着 dragging session 的继续,目的地被发送 draggingUpdated: 消息。你只需要在目的地需要了解被拖拽图片当前位置的时候实现它,可能是改变 dragging 操作或更新你正在提供的某个视觉反馈,例如插入光标。如果没有实现,NSView 假设 dragging 操作从调用 draggingEntered: 起就没有被改变。如果 dragging session 离开了 view 的边框,draggingExited: 方法被调用。如果你需要清理之前的某一个消息,就实现它,比如移除视觉反馈。

当图片被用一个拖操作放下而不是 NSDragOperationNone,目的地被发送 prepareForDragOperation: 紧接着 performDragOperation: 和 concludeDragOperation:。你可以通过在前两个方法中的任一个返回 NO 来取消拖。

在 performDragOperation: 方法里做大量的数据处理;其他两个方法只在必要的时候声明。接下来的代码示例展示了这个方法可能的实现。这个方法再次检查了 pasteboard 的可用数据,并且如果必要的话,它也检查了 dragging 源的操作 mask 里的可用操作。

- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender {
    NSPasteboard *pboard;
    NSDragOperation sourceDragMask;
 
    sourceDragMask = [sender draggingSourceOperationMask];
    pboard = [sender draggingPasteboard];
 
    if ( [[pboard types] containsObject:NSColorPboardType] ) {
        // Only a copy operation allowed so just copy the data
        NSColor *newColor = [NSColor colorFromPasteboard:pboard];
        [self setColor:newColor];
    } else if ( [[pboard types] containsObject:NSFilenamesPboardType] ) {
        NSArray *files = [pboard propertyListForType:NSFilenamesPboardType];
 
        // Depending on the dragging source and modifier keys,
        // the file data may be copied or linked
        if (sourceDragMask & NSDragOperationLink) {
            [self addLinkToFiles:files];
        } else {
            [self addDataFromFiles:files];
        }
    }
    return YES;
}
最后编辑于
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,172评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,346评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,788评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,299评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,409评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,467评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,476评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,262评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,699评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,994评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,167评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,827评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,499评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,149评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,387评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,028评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,055评论 2 352

推荐阅读更多精彩内容