文章來(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;
}