鼠标事件后,自定义Carbon键事件处理程序失败

时间:2018-11-13 03:06:12

标签: swift cocoa appkit macos-carbon

我正在尝试编写一个自定义NSMenu,它将能够列出按键输入并拦截必要的事件。这是为我打开的source clipboard manager提供一个简单的按需搜索功能。

似乎唯一的方法就是安装一个自定义的Carbon事件处理程序,该事件处理程序将侦听关键事件并相应地对其进行处理,但是这样的自定义处理程序似乎存在问题。

通常,我可以将事件向下传播到其他处理程序(例如系统处理程序),并且应该对其进行适当处理。这可以通过一个简单的回调来完成:

let eventHandlerCallback: EventHandlerUPP = { eventHandlerCallRef, eventRef, userData in
  let response = CallNextEventHandler(eventHandlerCallRef, eventRef!)
  print("Response \(response)")
  return response
}

此回调可正常运行,并始终打印Response 0。此响应表示该事件已正确处理。

但是,一旦我们在键盘事件之前发送了鼠标事件,事情就会变得很奇怪。在这种情况下,回调将失败并显示Response -9874。此响应表示该事件未正确处理。

似乎该事件未能在自定义视图下的某个地方处理,而且我不知道确切的位置或如何解决此问题。

为了进行复制,我将uploaded the code to Gist添加到XCode游乐场并运行。看到菜单弹出窗口后,按一些键(最好是箭头键,因为它们不会关闭菜单),并在控制台中观察Response 0。之后,将光标移到菜单内,然后按更多箭头键。您现在应该在控制台中看到Response -9874

2 个答案:

答案 0 :(得分:0)

不清楚您是否有NSTextField作为菜单视图,但是如果使用菜单视图,则很容易为该文本字段设置一个委托,该委托可以在用户键入时获取该字段的当前内容(这需要使用箭头键向后移动,然后使用Delete键等删除字符)。您的委托实现适当的委托方法,并在每次文本更改时被调用:

extension CustomMenuItemViewController: NSTextFieldDelegate {
    func controlTextDidChange( _ obj: Notification) {
        if let postingObject = obj.object as? NSTextField {
            let text = postingObject.stringValue
            print("the text is now: \(text)")
        }
    }
}

只是为了确认它能按预期工作,所以我在xib文件中为自定义菜单项视图(标签+编辑字段)创建了ViewController类,然后使用具有自定义视图的单个菜单项动态构建了一个简单的测试菜单控制器的视图,并将其添加到我的应用程序委托内的菜单栏中:

func installCustomMenuItem() {
    let menuBarItem = NSMenuItem(title: "Test", action: nil, keyEquivalent: "")
    let menu = NSMenu(title: "TestMenu" )
    let subMenuBarItem = NSMenuItem(title: "Custom View", action: nil, keyEquivalent: "")

    subMenuBarItem.view = menuItemVC.view
    menu.addItem(subMenuBarItem)
    menuBarItem.submenu = menu
    NSApp.mainMenu?.addItem(menuBarItem)
}

输入“ hello”后看起来像这样:

enter image description here

您可以从控制台为每个键入的字符调用我的处理程序:

the text is now: H 
the text is now: He 
the text is now: Hel 
the text is now: Hell
the text is now: Hello

您的情况可能有所不同,但是这种方法似乎很干净,可能对您有用。如果由于某种原因而无法执行此操作,请添加一个澄清的注释,我们将查看是否无法为您使用。


添加:

在我看来,您可能不希望使用NSTextField,因此我很好奇是否可以通过自定义视图轻松实现此操作并且相对容易。

制作NSView的子类:

class CustomMenuView: NSView {

    override var acceptsFirstResponder: Bool {
        return true
    }

    override func draw(_ dirtyRect: NSRect) {
        super.draw(dirtyRect)

        // Drawing code here.
    }

    override func keyDown(with event: NSEvent) {
        print("key down with character: \(String(describing: event.characters)) " )
    }
}

在自定义视图控制器中将您的根视图的类设置为此类,然后像以前一样进行所有操作-在applicationDidFinishLaunching中加载视图控制器并构建菜单,并查看视图控制器的视图(现在是CustomMenuView)设置为menuBarItem.view。

就是这样。现在,当下拉菜单时,您需要为每次按下的键调用keyDown方法。

key down with character: Optional("H") 
key down with character: Optional("e") 
key down with character: Optional("l") 
key down with character: Optional("l") 
key down with character: Optional("o") 
key down with character: Optional(" ")
key down with character: Optional("T") 
key down with character: Optional("h") 
key down with character: Optional("i") 
key down with character: Optional("s") 
key down with character: Optional(" ") 
key down with character: Optional("i") 
key down with character: Optional("s") 
key down with character: Optional(" ") 
key down with character: Optional("c") 
key down with character: Optional("o") 
key down with character: Optional("o") 
key down with character: Optional("l") 

:)

现在,您的自定义视图(和子视图,如果愿意的话)可以绘制自己的图,依此类推。


添加了带有ViewController的请求样本:

// Simple swift playground test
// The pop-up menu will show up onscreen in the playground at a fixed location.   
// Click in the popup and then all key commands will be logged.
// The ViewController in my example above may be taking care of putting the custom view in the responder chain, or the fact that it's in a menubar and being invoked via a MenuItem might be.
// I'd suggest trying it in the actual environment rather than in a playground. In my test app you click the menu name in the menubar to drop down the menu and it is added to the responder chain and works as expected without having to click in the menu first to get the events flowing.
// There is no reason you need to be hooking events either with carbon events or the newer format.  If you're in the responder chain of and implement the necessary, method then you'll get the key events you're looking for.

import AppKit

class CustomMenuView: NSView {

    override var acceptsFirstResponder: Bool {
        return true
    }

    override func draw(_ dirtyRect: NSRect) {
        super.draw(dirtyRect)

        // Drawing code here.
    }

    override func keyDown(with event: NSEvent) {
        print("key down with character: \(String(describing: event.characters)) " )
    }
}


func installCustomMenuItem() -> NSMenu {
//  let menuBarItem = NSMenuItem(title: "Test", action: nil, keyEquivalent: "")
    let resultMenu = NSMenu(title: "TestMenu" )
    let subMenuBarItem = NSMenuItem(title: "Custom View", action: nil, keyEquivalent: "")

    subMenuBarItem.view = CustomMenuView(frame: NSRect(x: 0, y: 0, width: 40, height: 44))
    resultMenu.addItem(subMenuBarItem)
//  menuBarItem.submenu = menu

    return resultMenu
}

var menu = installCustomMenuItem()

menu.popUp(positioning: nil, at: NSPoint(x:600,y:400), in: nil)

答案 1 :(得分:0)

我无法弄清楚为什么会发生此问题或如何解决此问题,但我知道可以通过拦截所有键并手动模拟其行为来解决此问题。

例如,这就是我现在处理向下箭头键的方式,应该选择菜单列表中的下一项:

class Menu: NSMenu {
  func selectNext() {
    var indexToHighlight = 1
    if let item = highlightedItem {
      indexToHighlight = index(of: item) + 1
    }

    if let itemToHighlight = self.item(at: indexToHighlight) {
      let highlightItemSelector = NSSelectorFromString("highlightItem:")
      perform(highlightItemSelector, with: itemToHighlight)

      if itemToHighlight.isSeparatorItem || !itemToHighlight.isEnabled || itemToHighlight.isHidden {
        selectNext()
      }
    }
  }
}

这样,当我收到带有向下箭头键的向下键事件时-我可以调用函数和return true来防止事件到达默认的NSMenu处理程序。同样,可以使用向上箭头键。

如果使用返回键,我将得到以下代码:

class Menu: NSMenu {
  func select() {
    if let item = highlightedItem {
      performActionForItem(at: index(of: item))
      cancelTracking()
    }
  }
}

实现此操作的完整提交是https://github.com/p0deje/Maccy/commit/158610d1d