Предотвратить закрытие NSPanel клавишей Escape с закрытым окном

Кто-нибудь знает, как лучше всего предотвратить закрытие NSPanel клавишей escape, когда это ключевое окно? Моя панель является дочерним окном, и я хочу, чтобы она вела себя как полупостоянная часть окна, больше как ящик, а для текстовых элементов управления в ней я хочу, чтобы клавиша Escape отменяла редактирование.

Недавно я нашел больше информации об окнах и клавише Escape в документации Cocoa. В ссылке на класс NSResponder под cancelOperation:, где говорится, что «окно отправляет сообщение о действии по умолчанию cancelOperation: первому ответчику, а оттуда сообщение перемещается вверх по цепочке ответчиков». Похоже, что для NSPanel все по-другому, и окно закрывается без первого ответчика, получающего вызов cancelOperation:, или делегатов NSTextView, получающих свой вызов doCommandBySelector:.

Мои знания о входах и выходах цепочки респондентов позорны, учитывая, что я работаю с OS X так долго, как я. Я думал, что мне нужно заставить keyDown: в моем подклассе NSPanel вести себя как обычное окно. Я попытался переопределить NSPanel и смог перехватить keyDown:, перенаправив вызов на keyDown: NSWindow вместо super, но изменений не было, Escape по-прежнему закрывал окно без сообщений первому ответившему. Было ли вообще разумно пытаться?

Затем я попытался полностью переопределить подкласс панели keyDown:, заставив его делать следующее:

[self.firstResponder cancelOperation:self]

Я бы подумал, что это позволит моему текстовому полю обрабатывать побег, как оно обычно ожидает, и, возможно, если бы текстовое поле не было первым ответчиком, вызов зашел бы в тупик. Однако я попробовал, и панель просто закрывается, как и раньше. Очевидно, я не перехватываю вещи на правильном уровне.

Кто-нибудь знает, какова последовательность методов, которые запускаются между событиями нажатия клавиши низкого уровня и закрытием панели, или что мне нужно переопределить, чтобы перехватить его и убедиться, что cancelOperation: переходит к моему первому ответчику?


person Pierre Houston    schedule 19.10.2012    source источник


Ответы (2)


Быстрый порт ответа Кейт-Кнаубера:

class ValueEditor : NSObject, NSControlTextEditingDelegate {
enum CommandType {
    case none
    case accept
    case next
    case prev
    case cancel
}

class func commandTypeType(for command: Selector) -> CommandType {
    let commandType: CommandType

    switch command {
    case #selector(NSStandardKeyBindingResponding.insertLineBreak(_:)) :
        fallthrough
    case #selector(NSStandardKeyBindingResponding.insertNewline(_:)) :
        fallthrough
    case #selector(NSStandardKeyBindingResponding.insertNewlineIgnoringFieldEditor(_:)) :
        fallthrough
    case #selector(NSStandardKeyBindingResponding.insertParagraphSeparator(_:)) :
        commandType = .accept

    case #selector(NSStandardKeyBindingResponding.insertTab(_:)) :
        fallthrough
    case #selector(NSWindow.selectNextKeyView(_:)) :
        fallthrough
    case #selector(NSStandardKeyBindingResponding.insertTabIgnoringFieldEditor(_:)) :
        commandType = .next

    case #selector(NSStandardKeyBindingResponding.insertBacktab(_:)) :
        fallthrough
    case #selector(NSWindow.selectPreviousKeyView(_:)) :
        commandType = .prev

    case #selector(NSStandardKeyBindingResponding.cancelOperation(_:)) :
        commandType = .cancel

    default:
        commandType = .none
    }

    return commandType
}


// MARK: - NSControl delegate

func control(_ control: NSControl,
             textView: NSTextView,
             doCommandBy commandSelector: Selector) -> Bool {
    let commandType: CommandType = ValueEditor.commandTypeType(for: commandSelector)

    switch commandType {
    case .cancel:
        control.abortEditing()

        // When the user hits 'ESC' key with a field editor active, cancel the field editor,
        // but return `true` here so that the NSPanel doesn’t close.
        // Hitting 'ESC' a second time will close the NSPanel.
        return true

    default:
        return false
    }

}

}

Не забудьте установить экземпляр ValueEditor в качестве делегата ваших объектов NSTextView!

person JanX2    schedule 01.05.2020

где-то в вашем пера или в коде установите делегата NSTableView на свой контроллер.

Обратите внимание, что setDelegate: — это не то же самое, что setDatasource:!

В моем случае: @interface ValueEditor: NSObject

  + (ValueEditorCmdType)cmdTypeForSelector:(SEL)command
  {
     ValueEditorCmdType cmdType = kCmdTypeNone;
     if ( command == @selector(insertLineBreak:) || command == @selector(insertNewline:) || command == @selector(insertNewlineIgnoringFieldEditor:) || command == @selector(insertParagraphSeparator:))
        cmdType = kCmdTypeAccept;
     else if (  command == @selector(insertTab:) || command == @selector(selectNextKeyView:)  || command == @selector(insertTabIgnoringFieldEditor:))
        cmdType = kCmdTypeNext;
     else if ( command == @selector(insertBacktab:) || command == @selector(selectPreviousKeyView:))
        cmdType = kCmdTypePrev;
     else if ( command == @selector(cancelOperation:) )   
        cmdType = kCmdTypeCancel;
     return cmdType;
  }

  #pragma mark - NSControl delegate
  - (BOOL)control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)command
  {
     ValueEditorCmdType cmdType = [ValueEditor cmdTypeForSelector:command];
     if ( cmdType == kCmdTypeCancel )
     {
        [control abortEditing];  

        // when user hits 'ESC' key with a field editor active, cancel the field editor,
        // but return YES here so that NSPanel doesn't close.  
        // Hitting 'ESC' a 2nd time will close the NSPanel.
        return YES;
     }
     return NO;
  }
person Keith Knauber    schedule 26.03.2013