macOS 開(kāi)發(fā) - 討論 drag-drop

文章來(lái)源:蘋(píng)果官方文檔

介紹拖放

Cocoa 讓我們能夠?qū)崿F(xiàn)時(shí)髦高雅的拖放功能,在應(yīng)用內(nèi)部或者在應(yīng)用之間。這個(gè)編程主題講解了如何用少數(shù)幾個(gè)方法來(lái)實(shí)現(xiàn)拖放。

本文結(jié)構(gòu)

在此處和 dragging 協(xié)議講解里的文字里,術(shù)語(yǔ) dragging session 是指整個(gè)過(guò)程,包括一張圖片的選擇、拖、放和目的地的接收或駁回。一個(gè) dragging 操作就是目的地在圖片被釋放的時(shí)候接收了圖片的行為。dragging 源是“擁有”被拖的圖片的對(duì)象;它是進(jìn)行 dragging session 的方法的參數(shù)。
dragging 是一個(gè)可視化的操作。要成為 dragging 操作的源或目的地,這個(gè)對(duì)象必須表示為屏幕的一部分真實(shí)區(qū)域;因此,只有 window 和 view 對(duì)象可以成為拖的源和目的地。(注意源視圖和沒(méi)必要和上面定義的 dragging 源是相同的對(duì)象。)NSWindow 和 NSView 提供方法來(lái)管理拖一個(gè)對(duì)象的用戶界面。只需要實(shí)現(xiàn) NSDraggingSource 或 NSDraggingDestination 協(xié)議中的少數(shù)幾個(gè)方法,具體實(shí)現(xiàn)哪個(gè)要看你的 window 或 view 子類是源還是目的地。

dragging 協(xié)議在這些章節(jié)中講解:

  • Dragging 源
  • Dragging 目的地

如何接收一次 drag 在這些章節(jié)中講解:

  • 接收 drag 操作
  • Dragging 文件

關(guān)于拖放經(jīng)常被問(wèn)到的問(wèn)題在這個(gè)章節(jié)中說(shuō)明:

  • 經(jīng)常被問(wèn)到的問(wèn)題

Dragging 源

一個(gè) dragging session 通過(guò)用戶在 window 或 view 里點(diǎn)擊和移動(dòng)鼠標(biāo)來(lái)初始化。NSView 和 NSWindow 實(shí)現(xiàn)了方法 dragImage:at:offset:event:pasteboard:source:slideBack: 來(lái)管理 dragging session。在 NSView 或 NSWindow 子類里的 mouseDown: 或 mouseDragged: 方法里調(diào)用這個(gè)方法。提供一個(gè)圖片以在拖的過(guò)程中顯示,一個(gè) pasteboard 保管了數(shù)據(jù),還有一個(gè)對(duì)象作為數(shù)據(jù)的“擁有者”或 dragging 源。在 dragging session 中,dragging 源被發(fā)送由 NSDraggingSource 協(xié)議定義的消息來(lái)執(zhí)行所有必要的操作,如下所述。

注意:NSView 也實(shí)現(xiàn)了 convenience 方法 dragFile:fromRect:slideBack:event:,用于被拖的對(duì)象是文件的時(shí)候。NSView 然后自己管理圖片、pasteboard 和 源消息。

拖操作

NSDraggingSource 方法中只有一個(gè)需要被實(shí)現(xiàn):draggingSourceOperationMaskForLocal:。這個(gè)方法聲明了源可以被實(shí)施的操作類型。表格 1 列出了可用的拖操作。(在 Java 中,常量被定義在 NSDraggingInfo 命名空間,以 NS 為前綴。)方法應(yīng)該返回一個(gè)允許的類型按位或組合或 NSDragOperationNone,在沒(méi)有操作被允許的情況下。

表格 1 可用的拖操作

dragging 操作 含義
NSDragOperationCopy 數(shù)據(jù)由可以被拷貝的圖像表示
NSDragOperationLink 數(shù)據(jù)可以被分享
NSDragOperationGeneric 操作可以被目的地定義
NSDragOperationPrivate 操作被私人地在源和目的地之間達(dá)成。
NSDragOperationMove 數(shù)據(jù)可以被移動(dòng)
NSDragOperationDelete 數(shù)據(jù)可以被刪除
NSDragOperationEvery 以上全部
NSDragOperationAll 棄用。用 NSDragOperationEvery 替代。
NSDragOperationNone 一個(gè)拖操作都不給。

如果這個(gè)拖是全部在自己的應(yīng)用中進(jìn)行的或者是在自己的和另一個(gè)應(yīng)用中間進(jìn)行,允許的操作可能會(huì)有不同。傳遞給 draggingSourceOperationMaskForLocal: 的標(biāo)記指明了是鄰近或內(nèi)部的拖。

用戶可以按下輔助按鍵來(lái)進(jìn)一步選擇要執(zhí)行哪個(gè)操作。如果 control、option 或 command 鍵被按下,源的操作 mask 被過(guò)濾到只包含表格 2 中給定的操作。要阻止輔助鍵修改 mask,你的 dragging 源應(yīng)該實(shí)現(xiàn) ignoreModifierKeysWhileDragging 然后返回 YES。

表格 2 modifier 鍵選擇的拖操作

輔助按鍵 dragging 操作
Control NSDragOperationLink
Option NSDragOperationCopy
Command NSDragOperationGeneric

拖消息

在拖的過(guò)程中,源對(duì)象被發(fā)送一系列消息來(lái)通知它拖操作的狀態(tài)。在拖最開(kāi)始的時(shí)候,源被發(fā)送 draggedImage:beganAt: 消息。每次被拖的對(duì)象移動(dòng)了,源就被發(fā)送一個(gè) draggedImage:movedTo: 消息。最終,當(dāng)用戶已經(jīng)釋放了鼠標(biāo)按鈕并且目的地執(zhí)行了放操作或拒收了,源會(huì)被發(fā)送 draggedImage:endedAt:operation: 消息。operation 參數(shù)是目的地執(zhí)行的拖操作,或是 NSDragOperationNone 如果拖失敗了的話。(在 Java 里,這些方法的名字為 startedDraggingImage、movedDraggingImage 和 finishedDraggingImage。)

dragging 源通常不需要實(shí)現(xiàn)這些方法里的每一個(gè)。但如果你要支持 NSDragOperationMove 或 NSDragOperationDelete 操作,需要實(shí)現(xiàn) draggedImage:endedAt:operation: 來(lái)從源中移除被拖的數(shù)據(jù)。(注意一個(gè) NSDragOperationDelete 操作在拖動(dòng)任意對(duì)象到dock中的垃圾桶圖標(biāo)的時(shí)候被觸發(fā)。)

被拖的圖片

一個(gè) dragging session 中被拖的圖片只是簡(jiǎn)單地表示 pasteboard 上的數(shù)據(jù)。盡管 dragging 目的地可以訪問(wèn)圖片,它主要還是關(guān)心圖片表示的 pasteboard 中的數(shù)據(jù) —— 目的地最終執(zhí)行的 dragging 操作還是基于 pasteboard 數(shù)據(jù),而不是圖片本身。

當(dāng) dragging session 由 NSView 方法 dragFile:fromRect:slideBack:event: 啟動(dòng)的時(shí)候,NSView 使用該文件的 Finder 圖標(biāo)作為圖片。對(duì)于你自定義的拖,需要配一個(gè)合適的圖片??赡馨@示的數(shù)據(jù)半透明的截圖,比如被選中的文字部分,或是數(shù)據(jù)的符號(hào)化表示,比如拖電子表格數(shù)據(jù)的時(shí)候用一個(gè)表格圖標(biāo)。

Dragging 目的地

要接收拖操作,必須注冊(cè)你的 window 或 視圖會(huì)接收的 pasteboard 類型,通過(guò)發(fā)送 registerForDraggedTypes: 消息,在 NSWindow 和 NSView 中都定義了,然后實(shí)現(xiàn)幾個(gè) NSDraggingDestination 協(xié)議中的方法。dragging session 中,候選目的地只有在注冊(cè)了可以匹配被拖的 pasteboard 數(shù)據(jù)類型的時(shí)候才會(huì)接收 NSDraggingDestination。目的地在圖片進(jìn)入、內(nèi)部移動(dòng)以及退出或釋放內(nèi)部邊界的時(shí)候接收這些消息。
盡管 NSDraggingDestination 被聲明為一個(gè) informal 協(xié)議,你創(chuàng)建用來(lái)實(shí)現(xiàn)協(xié)議的 NSWindow 和 NSView 子類只需要實(shí)現(xiàn)相關(guān)的方法即可。(NSWindow 和 NSView 類為全部這些方法都提供了私有實(shí)現(xiàn)。)一個(gè) window 對(duì)象或它的 delegate 可以實(shí)現(xiàn)這些方法;但是,delegate 的實(shí)現(xiàn)級(jí)別更高,如果兩個(gè)地方都有實(shí)現(xiàn)的話。

目的地消息的sender

NSDraggingDestination 方法的每一個(gè)都只有一個(gè)參數(shù):sender,調(diào)用方法的對(duì)象。在 NSDraggingDestination 方法的實(shí)現(xiàn)中,目的地可以發(fā)送 NSDraggingInfo 協(xié)議消息給 sender 來(lái)獲取當(dāng)前 dragging session 上的更多信息,比如查詢 dragging pasteboard 或者源的操作 mask。在 Java 里,sender 是一個(gè) NSDragDestination 對(duì)象,實(shí)現(xiàn)了 NSDraggingInfo 接口。

Dragging Pasteboard

盡管一個(gè)標(biāo)準(zhǔn)的 dragging pasteboard(用 [NSPasteboard pasteboardWithName:NSDragPboard] 獲得)被提供為獲得 dragging 數(shù)據(jù)的一種便捷方式,但無(wú)法保證這會(huì)是一次跨進(jìn)程拖使用的 pasteboard。因此,要確保獲得正確的 pasteboard,你的代碼應(yīng)該使用 [sender draggingPasteboard]。

目的地消息的順序

六個(gè) NSDraggingDestination 方法被調(diào)用在一個(gè)確定的順序中:

  • 當(dāng)圖片被拖進(jìn)目的地的邊界時(shí),目的地被發(fā)送一個(gè) draggingEntered: 方法。這個(gè)方法會(huì)返回一個(gè)值,指明目的地將要執(zhí)行的 dragging 操作。
  • 圖片留在目的地的時(shí)候,一系列 draggingUpdated: 消息被發(fā)送。方法會(huì)返回一個(gè)值,指明目的地將要執(zhí)行的 dragging 操作。
  • 如果圖片被拖出目的地,draggingExited: 被發(fā)送,并且 NSDraggingDestination 消息隊(duì)列會(huì)停止。如果它再次進(jìn)入,隊(duì)列會(huì)重新開(kāi)始(用一個(gè)新的 draggingEntered: 消息)。
  • 如果圖片被釋放了,它會(huì)滑回源(然后打斷隊(duì)列)或一個(gè) prepareForDragOperation: 消息被發(fā)送到目的地,根據(jù)最近最近一次調(diào)用 draggingEntered: 或 draggingUpdated: 的返回值。
  • 如果 prepareForDragOperation: 消息返回了 YES,一個(gè) performDragOperation: 消息被發(fā)送。
  • 最終,如果 performDragOperation: 返回了 YES,concludeDragOperation: 被發(fā)送。

接收拖操作

這篇文檔展示了一段示例代碼,可以讓一個(gè) view(或 window)接收幾種數(shù)據(jù)類型的 dragging session,基于被拖的類型執(zhí)行不同的拖操作。樣例實(shí)現(xiàn)可以接收一種顏色或是一個(gè)文件。被拖的顏色會(huì)被拷貝,文件會(huì)被鏈接或拷貝。
在一個(gè) view 可以接收拖操作之前,需要注冊(cè)可以接收的數(shù)據(jù)類型,通過(guò)調(diào)用它的 registerForDraggedTypes:,像這樣:

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

現(xiàn)在,不管什么時(shí)間只要一個(gè)由這些數(shù)據(jù)類型之一的數(shù)據(jù)類型 dragging session 進(jìn)入了 view 的邊界,view 就會(huì)被發(fā)送一系列 NSDraggingDestination 消息。下面的代碼是一個(gè)得到 draggingEntered: 發(fā)送的初始化簡(jiǎn)單例子。方法從 sender 獲得了 dragging pasteboard 和可用的拖操作。如果 pasteboard 包含顏色數(shù)據(jù)并且源對(duì)象許可了拖動(dòng),方法會(huì)返回 NSDragOperationGeneric,指示目的地對(duì)象許可 pasteboard 上的顏色數(shù)據(jù)的拖動(dòng)。如果 pasteboard 包含了一個(gè)文件名并且源許可了鏈接,方法會(huì)返回 NSDragOperationLink,指示目的地許可鏈接。如果源不允許鏈接,目的地也會(huì)檢查是不是有拷貝操作可以作替代,如果是就返回 NSDragOperationCopy。如果這些測(cè)試都失敗了,方法返回 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 的繼續(xù),目的地被發(fā)送 draggingUpdated: 消息。你只需要在目的地需要了解被拖拽圖片當(dāng)前位置的時(shí)候?qū)崿F(xiàn)它,可能是改變 dragging 操作或更新你正在提供的某個(gè)視覺(jué)反饋,例如插入光標(biāo)。如果沒(méi)有實(shí)現(xiàn),NSView 假設(shè) dragging 操作從調(diào)用 draggingEntered: 起就沒(méi)有被改變。如果 dragging session 離開(kāi)了 view 的邊框,draggingExited: 方法被調(diào)用。如果你需要清理之前的某一個(gè)消息,就實(shí)現(xiàn)它,比如移除視覺(jué)反饋。

當(dāng)圖片被用一個(gè)拖操作放下而不是 NSDragOperationNone,目的地被發(fā)送 prepareForDragOperation: 緊接著 performDragOperation: 和 concludeDragOperation:。你可以通過(guò)在前兩個(gè)方法中的任一個(gè)返回 NO 來(lái)取消拖。

在 performDragOperation: 方法里做大量的數(shù)據(jù)處理;其他兩個(gè)方法只在必要的時(shí)候聲明。接下來(lái)的代碼示例展示了這個(gè)方法可能的實(shí)現(xiàn)。這個(gè)方法再次檢查了 pasteboard 的可用數(shù)據(jù),并且如果必要的話,它也檢查了 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;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容