47

我已经阅读了 Apple 的粘贴板编程指南,但它没有回答我的特定问题。

我正在尝试编写一个 Cocoa 应用程序(用于 OS X,而不是 iOS),它将跟踪写入通用粘贴板的所有内容(因此,每当任何应用程序复制和粘贴时,而不是拖放,它也使用了 NSPasteboard)。我可以(几乎)通过不断地在后台线程上轮询通用粘贴板并检查changeCount来实现这一点。当然,这样做会让我觉得内心很肮脏。

我的问题是,有没有办法让粘贴板服务器在任何时候对通用粘贴板进行更改时通过某种回调通知我?我在 NSPasteboard 类参考中找不到任何东西,但我希望它潜伏在其他地方。

我可以想象的另一种方法是,如果有一种方法可以将通用粘贴板实现换成 NSPasteboard 的子类,我可以定义自己来发出回调。也许这样的事情是可能的?

如果公共的、App Store 合法的 API 可以做到这一点,我会非常喜欢,但如果需要使用私有 API,我也会接受。

谢谢!

4

6 回答 6

48

不幸的是,唯一可用的方法是轮询(嘘!)。没有通知,也没有什么可观察到更改的粘贴板内容。查看 Apple 的ClipboardViewer 示例代码,了解他们如何处理检查剪贴板。添加一个(希望不是过分热心的)计时器来继续检查差异,你就有了一个应该是 App-Store-Friendly 的基本(如果笨重)解决方案。

在bugreporter.apple.com提交增强请求以请求通知或其他一些回调。不幸的是,它最早要等到下一个主要操作系统版本才会对您有所帮助,但现在它正在轮询,直到我们都要求他们给我们更好的东西。

于 2011-02-17T19:16:13.607 回答
13

曾经有一个邮件列表上的帖子描述了针对通知 API 的决定。虽然我现在找不到。最重要的是,可能有太多的应用程序会注册该 api,即使他们真的不需要。如果你然后复制一些东西,整个系统会疯狂地浏览新的剪贴板内容,为计算机创造大量的工作。所以我认为他们不会很快改变这种行为。整个 NSPasteboard API 也是围绕使用 changeCount 在内部构建的。因此,即使您自定义的 NSPasteboard 子类仍然需要继续轮询。

如果你真的想检查粘贴板是否改变,只需保持观察 changeCount 半秒。比较整数真的很快,所以这里真的没有性能问题。

于 2012-04-07T11:03:12.297 回答
11

根据 Joshua 提供的答案,我想出了类似的实现,但很快,这里是其要点的链接:PasteboardWatcher.swift

来自相同的代码片段:

class PasteboardWatcher : NSObject {

    // assigning a pasteboard object
    private let pasteboard = NSPasteboard.generalPasteboard()

    // to keep track of count of objects currently copied
    // also helps in determining if a new object is copied
    private var changeCount : Int

    // used to perform polling to identify if url with desired kind is copied
    private var timer: NSTimer?

    // the delegate which will be notified when desired link is copied
    weak var delegate: PasteboardWatcherDelegate?

    // the kinds of files for which if url is copied the delegate is notified
    private let fileKinds : [String]

    /// initializer which should be used to initialize object of this class
    /// - Parameter fileKinds: an array containing the desired file kinds
    init(fileKinds: [String]) {
        // assigning current pasteboard changeCount so that it can be compared later to identify changes
        changeCount = pasteboard.changeCount

        // assigning passed desired file kinds to respective instance variable
        self.fileKinds = fileKinds

        super.init()
    }
    /// starts polling to identify if url with desired kind is copied
    /// - Note: uses an NSTimer for polling
    func startPolling () {
        // setup and start of timer
        timer = NSTimer.scheduledTimerWithTimeInterval(2, target: self, selector: Selector("checkForChangesInPasteboard"), userInfo: nil, repeats: true)
    }

    /// method invoked continuously by timer
    /// - Note: To keep this method as private I referred this answer at stackoverflow - [Swift - NSTimer does not invoke a private func as selector](http://stackoverflow.com/a/30947182/217586)
    @objc private func checkForChangesInPasteboard() {
        // check if there is any new item copied
        // also check if kind of copied item is string
        if let copiedString = pasteboard.stringForType(NSPasteboardTypeString) where pasteboard.changeCount != changeCount {

            // obtain url from copied link if its path extension is one of the desired extensions
            if let fileUrl = NSURL(string: copiedString) where self.fileKinds.contains(fileUrl.pathExtension!){

                // invoke appropriate method on delegate
                self.delegate?.newlyCopiedUrlObtained(copiedUrl: fileUrl)
            }

            // assign new change count to instance variable for later comparison
            changeCount = pasteboard.changeCount
        }
    }
}

注意:在我试图确定用户是否复制了文件 url 的共享代码中,提供的代码可以很容易地修改用于其他一般目的。

于 2015-06-20T07:44:45.553 回答
1

没有必要轮询。粘贴板通常只会在当前视图处于非活动状态或没有焦点时更改。粘贴板有一个计数器,当内容更改时会递增。当窗口重新获得焦点(windowDidBecomeKey)时,检查 changeCount 是否已更改,然后进行相应处理。

这不会捕获所有更改,但如果粘贴板在变为活动状态时有所不同,则让您的应用程序做出响应。

在斯威夫特...

var pasteboardChangeCount = NSPasteboard.general().changeCount
func windowDidBecomeKey(_ notification: Notification)
{   Swift.print("windowDidBecomeKey")
    if  pasteboardChangeCount != NSPasteboard.general().changeCount
    {   viewController.checkPasteboard()
        pasteboardChangeCount  = NSPasteboard.general().changeCount
    }
}
于 2017-07-31T02:00:06.540 回答
0

对于那些需要 Swift 5 中一些非常简化的版本的人来说,它可以工作(基于@Devarshi 代码):

    func WatchPasteboard(copied: @escaping (_ copiedString:String) -> Void) {
        let pasteboard = NSPasteboard.general
        var changeCount = NSPasteboard.general.changeCount
        Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
            if let copiedString = pasteboard.string(forType: .string) {
                if pasteboard.changeCount != changeCount {        
                    copied(copiedString)                
                    changeCount = pasteboard.changeCount
                }
            }
        }
    }

使用方法如下:

WatchPasteboard {
    print("copy detected : \($0)")
}

它会像下面这样打印出来..

watched : pasteboard1
watched : pasteboard2
于 2020-02-16T14:17:28.373 回答
-2

对于更严格的情况,我有一个解决方案:检测您的内容何时NSPasteboard被其他内容替换。

如果您创建一个符合实际内容的类NSPasteboardWriting并将其-writeObjects:与实际内容一起传递,NSPasteboard则将保留此对象,直到其内容被替换。如果没有对该对象的其他强引用,则将其释放。

NSPasteboard这个对象的释放是新获得新内容的时刻。

于 2017-08-03T12:07:49.863 回答