これは難しい質問です。多くの回答が示唆しているように、アプリケーションまたはウィンドウレベルでイベントをインターセプトすることは、メニュー項目を強制的に機能させる確実な方法です。同時に、他のことを壊す可能性があります。たとえば、集中している場合NSTextField
やNSButton
、メニュー項目ではなくイベントを消費したい場合などです。これは、ユーザーがシステム環境設定でそのメニュー項目に相当するキーを再定義した場合、つまりに変更Space
した場合にも失敗する可能性がありますP
。
メニュー項目と同等のスペースキーを使用しているという事実は、物事をさらに難しくします。スペースは、矢印キーやその他のいくつかのUIイベント文字と同様に、AppKitが異なる方法で処理し、場合によってはメインメニューに伝播する前に消費する特別なUIイベント文字の1つです。
したがって、覚えておくべきことが2つあります。まず、標準のレスポンダーチェーンです。
NSApplication.sendEvent
キーウィンドウにイベントを送信します。
- キーウィンドウはでイベントを受信し、
NSWindow.sendEvent
それがキーイベントであるかどうかを判断し、自分自身で呼び出しますperformKeyEquivalent
。
performKeyEquivalent
現在のウィンドウのに送信しますfirstResponder
。
- レスポンダーがそれを消費しない場合、イベントは再帰的に上向きにに送信されます
nextResponder
。
performKeyEquivalent
true
レスポンダーの1人がイベントを消費した場合は戻り、false
それ以外の場合は戻ります。
さて、2番目のトリッキーな部分です。イベントが消費されない場合(つまり、がperformKeyEquivalent
返されるときfalse
)、ウィンドウはそれを特別なキーボードUIイベントとして処理しようとします。これはCocoaイベント処理ガイドで簡単に説明されています。
Cocoaイベントディスパッチアーキテクチャは、特定のキーイベントをコマンドとして扱い、コントロールフォーカスをウィンドウ内の別のユーザーインターフェイスオブジェクトに移動し、オブジェクト上でのマウスクリックをシミュレートし、モーダルウィンドウを閉じ、選択を許可するオブジェクトを選択します。 。この機能は、キーボードインターフェイスコントロールと呼ばれます。キーボードインターフェイスコントロールに関係するユーザーインターフェイスオブジェクトのほとんどはNSControlオブジェクトですが、コントロールではないオブジェクトも参加できます。
この部分の動作は非常に簡単です。
- ウィンドウは、対応するアクション(セレクター)でキーイベントを変換します。
- ファーストレスポンダーに確認して
respondsToSelector
呼び出します。
- アクションが呼び出された場合、イベントは消費されたものとして扱われ、イベントの伝播は停止します。
したがって、これらすべてを念頭に置いて、次の2つのことを確認する必要があります。
- レスポンダーチェーンが正しく設定されている。
- レスポンダーは必要なものだけを消費し、それ以外の場合はイベントを伝播します。
最初のポイントはめったに問題を与えません。2つ目は、この例で発生することであり、注意が必要です。AVPlayer
通常、最初のレスポンダーであり、スペースキーイベントやその他のイベントを消費します。これを機能させるには、デフォルトの実装で発生するようにkeyUp
、keyDown
メソッドをオーバーライドして、イベントをレスポンダーチェーンの上位に伝播する必要があります。NSView
// All player keyboard gestures are disabled.
override func keyDown(with event: NSEvent) {
self.nextResponder?.keyDown(with: event)
}
// All player keyboard gestures are disabled.
override func keyUp(with event: NSEvent) {
self.nextResponder?.keyUp(with: event)
}
上記はイベントをレスポンダーチェーンに転送し、最終的にメインメニューで受信されます。1つの落とし穴があります。ファーストレスポンダーがコントロール、NSButton
またはカスタムNSControl
継承オブジェクトである場合、イベントを消費します。通常、これを実現したいのですが、そうでない場合、たとえばカスタムコントロールを実装する場合は、以下をオーバーライドできますrespondsToSelector
。
override func responds(to selector: Selector!) -> Bool {
if selector == #selector(performClick(_:)) { return false }
return super.responds(to: selector)
}
これにより、ウィンドウがキーボードUIイベントを消費するのを防ぎ、メインメニューが代わりにそれを受け取ることができます。ただし、ファーストレスポンダーがそれを消費できる場合を含め、すべてのキーボードUIイベントをインターセプトする場合は、ウィンドウまたはアプリケーションをオーバーライドする必要がありますperformKeyEquivalent
が、他の回答が示唆するように複製することはありません。
override func performKeyEquivalent(with event: NSEvent) -> Bool {
// Attempt to perform the key equivalent on the main menu first.
if NSApplication.shared.mainMenu?.performKeyEquivalent(with: event) == true { return true }
// Continue with the standard implementation if it doesn't succeed.
return super.performKeyEquivalent(with: event)
}
結果を確認せずにメインメニューを呼び出すと、イベントがレスポンダーチェーンによって消費されない場合、performKeyEquivalent
最初に手動で、次に実装から自動的に2回呼び出すことになります。super
これAVPlayer
は、が最初のレスポンダーでありkeyDown
、keyUp
メソッドが上書きされていない場合に当てはまります。
PSスニペットはSwift4ですが、考え方は同じです。✌️
PPS素晴らしいWWDC2010セッション145– Cocoaアプリケーションの主要なイベント処理があり、優れた例でこの主題を詳細にカバーしています。WWDC2010-11はAppleDeveloperPortalにリストされなくなりましたが、完全なセッションリストはここにあります。