11

NSMenuItem(アプリのメインメニュー)に修飾子なしで同等のキー ""(スペース)を設定したい。

ドキュメントから次のように:

たとえば、メディアを再生するアプリケーションでは、Playコマンドは、コマンドキーなしで「」(スペース)だけにマップされる場合があります。これは、次のコードで実行できます。

[menuItem setKeyEquivalent:@ ""];

[menuItem setKeyEquivalentModifierMask:0];

キーと同等のセットは正常に設定されますが、機能しません。修飾子なしで「スペース」キーを押しても何も起こりませんが、「Fn」修飾子キーで「スペース」を押すと機能します。

修飾子なしで「スペース」を使用する必要があります。助けてください!

4

5 に答える 5

4

私も同じ問題を抱えていました。あまり詳しく調べていませんが、私が知る限り、スペースバーはCocoaへのキーボードショートカットのようには「見えない」ため、にルーティングされ-insertText:ます。私の解決策は、NSWindowをサブクラス化し、レスポンダーチェーンを上るときにそれをキャッチし(おそらく代わりにNSAppをサブクラス化できます)、メニューシステムに明示的に送信することでした。

- (void)insertText:(id)insertString
{
    if ([insertString isEqual:@" "]) {
        NSEvent *fakeEvent = [NSEvent keyEventWithType:NSKeyDown
                                              location:[self mouseLocationOutsideOfEventStream]
                                         modifierFlags:0
                                             timestamp:[[NSProcessInfo processInfo] systemUptime]
                                          windowNumber:self.windowNumber
                                               context:[NSGraphicsContext currentContext]
                                            characters:@" "
                           charactersIgnoringModifiers:@" "
                                             isARepeat:NO
                                               keyCode:49];
        [[NSApp mainMenu] performKeyEquivalent:fakeEvent];
    } else {
        [super insertText:insertString];
    }
}
于 2012-08-03T17:57:58.817 に答える
3

これは難しい質問です。多くの回答が示唆しているように、アプリケーションまたはウィンドウレベルでイベントをインターセプトすることは、メニュー項目を強制的に機能させる確実な方法です。同時に、他のことを壊す可能性があります。たとえば、集中している場合NSTextFieldNSButton、メニュー項目ではなくイベントを消費したい場合などです。これは、ユーザーがシステム環境設定でそのメニュー項目に相当するキーを再定義した場合、つまりに変更Spaceした場合にも失敗する可能性がありますP

メニュー項目と同等のスペースキーを使用しているという事実は、物事をさらに難しくします。スペースは、矢印キーやその他のいくつかのUIイベント文字と同様に、AppKitが異なる方法で処理し、場合によってはメインメニューに伝播する前に消費する特別なUIイベント文字の1つです。

したがって、覚えておくべきことが2つあります。まず、標準のレスポンダーチェーンです。

  1. NSApplication.sendEventキーウィンドウにイベントを送信します。
  2. キーウィンドウはでイベントを受信し、NSWindow.sendEventそれがキーイベントであるかどうかを判断し、自分自身で呼び出しますperformKeyEquivalent
  3. performKeyEquivalent現在のウィンドウのに送信しますfirstResponder
  4. レスポンダーがそれを消費しない場合、イベントは再帰的に上向きにに送信されますnextResponder
  5. performKeyEquivalenttrueレスポンダーの1人がイベントを消費した場合は戻り、falseそれ以外の場合は戻ります。

さて、2番目のトリッキーな部分です。イベントが消費されない場合(つまり、がperformKeyEquivalent返されるときfalse)、ウィンドウはそれを特別なキーボードUIイベントとして処理しようとします。これはCocoaイベント処理ガイドで簡単に説明されています。

Cocoaイベントディスパッチアーキテクチャは、特定のキーイベントをコマンドとして扱い、コントロールフォーカスをウィンドウ内の別のユーザーインターフェイスオブジェクトに移動し、オブジェクト上でのマウスクリックをシミュレートし、モーダルウィンドウを閉じ、選択を許可するオブジェクトを選択します。 。この機能は、キーボードインターフェイスコントロールと呼ばれます。キーボードインターフェイスコントロールに関係するユーザーインターフェイスオブジェクトのほとんどはNSControlオブジェクトですが、コントロールではないオブジェクトも参加できます。

この部分の動作は非常に簡単です。

  1. ウィンドウは、対応するアクション(セレクター)でキーイベントを変換します。
  2. ファーストレスポンダーに確認してrespondsToSelector呼び出します。
  3. アクションが呼び出された場合、イベントは消費されたものとして扱われ、イベントの伝播は停止します。

したがって、これらすべてを念頭に置いて、次の2つのことを確認する必要があります。

  1. レスポンダーチェーンが正しく設定されている。
  2. レスポンダーは必要なものだけを消費し、それ以外の場合はイベントを伝播します。

最初のポイントはめったに問題を与えません。2つ目は、この例で発生することであり、注意が必要です。AVPlayer通常、最初のレスポンダーであり、スペースキーイベントやその他のイベントを消費します。これを機能させるには、デフォルトの実装で発生するようにkeyUpkeyDownメソッドをオーバーライドして、イベントをレスポンダーチェーンの上位に伝播する必要があります。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は、が最初のレスポンダーでありkeyDownkeyUpメソッドが上書きされていない場合に当てはまります。

PSスニペットはSwift4ですが、考え方は同じです。✌️

PPS素晴らしいWWDC2010セッション145– Cocoaアプリケーションの主要なイベント処理があり、優れた例でこの主題を詳細にカバーしています。WWDC2010-11はAppleDeveloperPortalにリストされなくなりましたが、完全なセッションリストはここにあります。

于 2019-01-02T12:21:09.960 に答える
1

私はひねりを加えて同じ問題を経験しています...

NSMenuItemのリンクされたIBActionがAppDelegateにある間、同等のスペースバーキーは私のアプリで正常に機能します。

IBActionを専用コントローラーに移動すると失敗します。他のすべてのメニュー項目キーと同等のものは引き続き機能しますが、スペースバーは応答しません(修飾キーでは問題ありませんが、修飾されていない@ ""は機能しません)。

コントローラに直接リンクするか、レスポンダーチェーンを介してリンクするかなど、さまざまな回避策を試しましたが、役に立ちませんでした。私はコードの方法を試しました:

[menuItem setKeyEquivalent:@" "];  
[menuItem setKeyEquivalentModifierMask:0];  

とInterfaceBuilderの方法では、動作は同じです

Justinの回答に従って、NSWindowのサブクラス化を試みましたが、これまでのところ、それを機能させることができませんでした。

そのため、今のところ、この1つのIBActionを降伏して、動作するAppDelegateに再配置しました。私はこれを解決策とは見なさず、ただ実行するだけです...おそらくそれはバグであるか、または(より可能性が高いですが)イベントメッセージングとレスポンダーチェーンを十分に理解していません。

于 2012-11-07T22:03:08.923 に答える
1

私もスペースを使用する必要があるので、この投稿をアップしますが、それらのソリューションはどれも私にはうまくいきません。

したがって、NSApplicationをサブクラス化し、justinkソリューションsendEvent:でセレクターを使用します。

- (void)sendEvent:(NSEvent *)anEvent
{
    [super sendEvent:anEvent];
    switch ([anEvent type]) {
    case NSKeyDown:
        if (([anEvent keyCode] == 49) && (![anEvent isARepeat])) {

            NSPoint pt; pt.x = pt.y = 0;
            NSEvent *fakeEvent = [NSEvent keyEventWithType:NSKeyDown
                                                  location:pt
                                             modifierFlags:0
                                                 timestamp:[[NSProcessInfo processInfo] systemUptime]
                                              windowNumber: 0 // self.windowNumber
                                                   context:[NSGraphicsContext currentContext]
                                                characters:@" "
                               charactersIgnoringModifiers:@" "
                                                 isARepeat:NO
                                                   keyCode:49];
            [[NSApp mainMenu] performKeyEquivalent:fakeEvent];
        }
        break;

    default:
        break;
    }
}

それが役立つことを願っています

于 2015-08-27T10:13:01.447 に答える
0

クイックスウィフト4-5メソッド:

ビューコントローラの場合:

// Capture space and call main menu
override func keyDown(with event: NSEvent) {
    if event.keyCode == 49 && !event.isARepeat{
        NSApp.mainMenu?.performKeyEquivalent(with: event)
    }
    super.keyDown(with: event)
}
于 2019-10-31T13:22:32.110 に答える