Cocoa:应用内键盘事件处理
2017-06-15 10:25
330 查看
原作:http://www.tuicool.com/articles/quEzuyJ
本文将介绍: Cocoa 中应用内(不包含全局快捷键)键盘事件处理路径,如何在路径的每个阶段重载相应的方法来处理事件,并且给出具体实现方法(基于 Swift 2.2)。
在 Mac 上完全通过键盘操控一个应用无疑是最愉悦的体验,在 CurrencyX 1.2
版本中,我们希望增加一些快捷键来提升用户体验:
在汇率列表中实现:
回车键定位到输入栏;
退格键定位到输入栏并且删除最后一个符号(如果有的话);
数字键直接开始编辑输入栏;
字母键将汇率列表中首字母符合的汇率变为基本汇率(
在搜索汇率中实现:
字母键直接开始编辑搜索栏;
ESC 键返回汇率列表。
我们只需要了解从 按键事件发生 到 某个 Responder 处理事件 过程中发生了什么,然后根据需求在合适的地方实现自定义的方法即可。
本文总结了按键事件处理相关要点,并且给出每一种快捷键我们使用的不同的具体实现方法(基于 Swift 2.2)。
敲下一个键后,
event 的
按照
Key Equivalents
首先,
Hierarchy) 向下传递 (
YES。
如果视图层次中没有对象处理事件,
Menu 的控件,直到某个对象返回 YES。
如果 Menu 中也没有对象处理事件,进行下一步。
Apple 的文档中不建议重载
Keyboard Interface Control
为起点,通过
Key-view loop。
Tab 键切换到下一个焦点等)。
如果按键不属于特定按键,或者特定按键的对应 Command 没有实现,进行下一步。
可以通过
Loop Management 相关方法在代码中控制 Key-view Loop。
Keyboard Actions
Key Window,
Responder Chain 向上传递 (
Responder 响应并处理事件。
如果没有 Responder 响应,进行下一步。
可以通过实现 Command 并在
Input Manager,该方法将根据按键事件是否有绑定的 Command,向调用者发送
Next Responder,当没有 Next Responder 时将 Beep。
了解系统处理按键事情的过程后,在每一步中都有不同的方法可以处理:
等价键判断
设置 View Hierarchy 中某些元素的
设置 Menu 中 某些元素的
详见 Handling
Key Equivalents
键盘界面控制
设置 View Hierarchy 中某些元素的
YES,影响
First Responder 设置 Key-view loop。
详见 Handling
Keyboard Interface Control
键盘动作
重载
在方法中调用
Command 提供自定义实现, 在重载 Command 的时候需要注意,很多方法
在方法中通过事件的
详见 Overriding
the keyDown: Method
通过重载
回车键定位到输入栏;
退格键定位到输入栏并且删除最后一个符号(如果有的话);
数字键直接开始编辑输入栏;
字母键直接开始编辑搜索栏。
通过 Menu Item 的快捷键设置实现:
通过重载 ESC 绑定的 Command 实现
ESC 键返回汇率列表。
1. 重载
String,而
在
实际上在阅读文档之前,一直使用
通过在菜单栏中创建新的 Item 来实现组合快捷键有以下优点:
可以在 Interface Builder 中很方便的定义按键;
对于不熟悉应用的用户来说,菜单栏能够直观的表示快捷键的作用;
没有键盘的用户也可以通过点击菜单栏使用该功能。
实现起来也很简单,在 First Responder 中实现自定义方法,并且通过 MainMenu.xib 将新增的 Menu Item 与 Responder 关联起来即可。 具体步骤如下:
打开 MainMenu.xib;
选择 First Responder;
选择 Utilities 面板中的 Attributes Inspector;
现在 Inspector 中有内容为空的 User Defined 列表,点击下方“+”按钮添加新的 Action,即自定义函数。我们现在希望增加一项名为“搜索”的菜单栏,调用
在 MainMenu.xib 文件中新增菜单栏。可以通过拖拽 Utilities 中的控件或者复制粘贴已有的控件来实现;
将新增项的 Title 设为 “搜索”;
将新增项的 Key Equivalent 设为
选择 Connections Inspector;
如果通过复制粘贴创建控件,需要先删除任何已有的连接;
将 Sent Actions -> action 后的小圆圈连接至 First Responder,出现一个弹窗;
在弹窗中选择相应的方法,可以看到第四步中定义的
这样便实现了通过快捷键调用自定义方法。如果重新打开工程文件后发现第四步中的 User Defined 列表内容为空也不用紧张,之前的功能依然能够正常运行。
ESC 键对应的 Command 是
需要注意的是,在 View Controller 为 First Responder 时按键事件才会正确响应。
在讨论如何处理键盘事件的问题中,一般的回答是在
的 Guide 中却完全没有提到
The property’s value is hardware-independent. The value returned is the same as the value returned in the kEventParamKeyCode when using Carbon Events.
可能在 Cocoa 调用 Carbon 键盘处理相关函数时需要用到
在判断是否按下 'A' 键时,常用的方法也有判断
'A' 来实现。
这样的问题在于,如果切换了输入法之后,同一按键的值是有可能会变的:
实际上由于
在 Menu 中设置的 Key Equivalent 则不受输入法的影响,可以正常工作,猜测这一方法最终实现依赖
另外在常用软件 Sketch 中,切换了奇怪的输入法 Menu 中的快捷键也无法响应,猜测 Sketch 的 Menu 中的 Button 是自定义实现的,并且很有可能是用 characters 来判断按键事件。
终于写完了,Happy Coding :smile:。
Handling
Key Events
NSEvent
Class Reference
NSResponder
Class Reference
NSResponder
Class Reference
objective
c - NSTableView + Delete Key - Stack Overflow
Handling
keyboard events in AppKit with Swift
Notes
from a Swift developer: Adding menu items and their actions
本文将介绍: Cocoa 中应用内(不包含全局快捷键)键盘事件处理路径,如何在路径的每个阶段重载相应的方法来处理事件,并且给出具体实现方法(基于 Swift 2.2)。
前言
在 Mac 上完全通过键盘操控一个应用无疑是最愉悦的体验,在 CurrencyX 1.2版本中,我们希望增加一些快捷键来提升用户体验:
在汇率列表中实现:
回车键定位到输入栏;
退格键定位到输入栏并且删除最后一个符号(如果有的话);
数字键直接开始编辑输入栏;
字母键将汇率列表中首字母符合的汇率变为基本汇率(
NSTableView默认行为)。
⌘ + F跳转至开始搜索界面;
在搜索汇率中实现:
字母键直接开始编辑搜索栏;
ESC 键返回汇率列表。
我们只需要了解从 按键事件发生 到 某个 Responder 处理事件 过程中发生了什么,然后根据需求在合适的地方实现自定义的方法即可。
本文总结了按键事件处理相关要点,并且给出每一种快捷键我们使用的不同的具体实现方法(基于 Swift 2.2)。
一、按键事件的路径
敲下一个键后, NSApp接收到一个按键事件(
NSEvent)。该
event 的
characters即物理按键根据 键值绑定规则 中对应的字符串,可以用作唯一标识。
NSApp需要根据按键事件的类型来做出不同的处理,而判断其类型的过程相对复杂。下图是一个按键事件在应用中真正被处理之前可能经过的路径:
按照
NSApp对按键事件的处理顺序,有三个关键步骤,具体说明如下:
Key Equivalents
首先,
NSApp向 Key Window 发送
performKeyEquivalent:消息,并沿着视图层次(View
Hierarchy) 向下传递 (
NSView中该方法默认实现即向
subViews依次发送该消息),直到某个对象返回
YES。
如果视图层次中没有对象处理事件,
NSApp便将
performKeyEquivalent:消息发送给
Menu 的控件,直到某个对象返回 YES。
如果 Menu 中也没有对象处理事件,进行下一步。
Apple 的文档中不建议重载
performKeyEquivalent:方法,建议利用如
NSButton,
NSMenu,
NSMatrix,
NSSavePanel等默认实现了
performKeyEquivalent:方法的控件,设置其
keyEquivalent属性为对应的键值,控件在键值匹配时会自动触发点击事件。
Keyboard Interface Control
NSWindow默认以 First Responder
为起点,通过
NSView的
nextKeyView和
previousKeyView属性组成首尾相连的
Key-view loop。
NSWindow会将特定按键或按键组合事件与控制焦点视图(Key-view)的 Commands 绑定(如
Tab 键切换到下一个焦点等)。
如果按键不属于特定按键,或者特定按键的对应 Command 没有实现,进行下一步。
可以通过
NSView中 Key-view
Loop Management 相关方法在代码中控制 Key-view Loop。
Keyboard Actions
NSApp通过
sendEvent:将按键事件传给
Key Window,
NSWindow调用 First Responder 的
keyDown:方法(当只有修饰键时,调用
flagsChanged:),并沿着
Responder Chain 向上传递 (
NSResponder中该方法默认实现即将消息传递给 Next Responder),直到某个
Responder 响应并处理事件。
如果没有 Responder 响应,进行下一步。
NSResponder把特定按键事件与相应 Commands 绑定,Responder
可以通过实现 Command 并在
keyDown:中调用
interpretKeyEvents:将事件传递给系统
Input Manager,该方法将根据按键事件是否有绑定的 Command,向调用者发送
doCommandBySelector:或
insertText:消息,这两种方法默认实现都是将消息发送给
Next Responder,当没有 Next Responder 时将 Beep。
二、自定义按键事件处理方法
了解系统处理按键事情的过程后,在每一步中都有不同的方法可以处理:等价键判断
设置 View Hierarchy 中某些元素的
keyEquivalent属性;
设置 Menu 中 某些元素的
keyEquivalent属性。
详见 Handling
Key Equivalents
键盘界面控制
设置 View Hierarchy 中某些元素的
acceptsFirstResponder返回
YES,影响
canBecomeKeyView返回 YES,并通过设置
nextKeyView和
previousKeyView为
First Responder 设置 Key-view loop。
详见 Handling
Keyboard Interface Control
键盘动作
重载
keyDown:方法:
在方法中调用
interpretKeyEvents:并为按键绑定的
Command 提供自定义实现, 在重载 Command 的时候需要注意,很多方法
NSResponder仅声明而没有实现,因此调用
super会抛出异常 ;
在方法中通过事件的
characters与
NSEvent定义的 常量 和
NSText定义的 常量判断特殊按键,并直接调用自定义实现。
详见 Overriding
the keyDown: Method
三、实现过程
我们决定通过三种方法实现期望的功能:
通过重载 keyDown:实现:
回车键定位到输入栏;
退格键定位到输入栏并且删除最后一个符号(如果有的话);
数字键直接开始编辑输入栏;
字母键直接开始编辑搜索栏。
通过 Menu Item 的快捷键设置实现:
⌘ + F跳转至开始搜索界面。
通过重载 ESC 绑定的 Command 实现
ESC 键返回汇率列表。
1. 重载
keyDown:
override func keyDown(theEvent: NSEvent) { // Pseudo-code guard IsValidateKeyDownEvent else { super.keyDown(theEvent) } FocusTextField if (IsDeleteKey) { DeleteLastCharIfExits } else if (IsEnterKey) { DoNothing } else { InsertText } SendControlTextDidChangeNotification }
NSEvent的
characters属性是
String,而
NSEvent定义的 常量 和
NSText定义的 常量 都是
Int,因此可以通过:
extension NSEvent { var keyCharacter: Int? { guard let char = charactersIgnoringModifiers?.utf16.first else { // Handling dead keys return nil } return Int(char) } }
func isDeleteKeyDownEvent(theEvent: NSEvent) -> Bool { if let char = theEvent.keyCharacter where char == NSDeleteCharacter { return true } return false } func isEnterKeyDownEvent(theEvent: NSEvent) -> Bool { if let char = theEvent.keyCharacter where char == NSCarriageReturnCharacter { return true } return false }
在
keyDown:通过调用
isDeleteKeyDownEvent:和
isEnterKeyDownEvent:判断即可。
实际上在阅读文档之前,一直使用
theEvent.keyCode直接与数字进行对比,只是这样代码可读性不高,实际上也是可行的方法。
2. 增加 Menu Item
通过在菜单栏中创建新的 Item 来实现组合快捷键有以下优点:可以在 Interface Builder 中很方便的定义按键;
对于不熟悉应用的用户来说,菜单栏能够直观的表示快捷键的作用;
没有键盘的用户也可以通过点击菜单栏使用该功能。
实现起来也很简单,在 First Responder 中实现自定义方法,并且通过 MainMenu.xib 将新增的 Menu Item 与 Responder 关联起来即可。 具体步骤如下:
打开 MainMenu.xib;
选择 First Responder;
选择 Utilities 面板中的 Attributes Inspector;
现在 Inspector 中有内容为空的 User Defined 列表,点击下方“+”按钮添加新的 Action,即自定义函数。我们现在希望增加一项名为“搜索”的菜单栏,调用
ViewController(任意
canBecomeFirstResponder的类都可以)中实现的
search(sender: AnyObject?),那么只需要设置新增的一栏中 Action 为 search: (注意函数名后的冒号), Type 为默认的 id 即可;
在 MainMenu.xib 文件中新增菜单栏。可以通过拖拽 Utilities 中的控件或者复制粘贴已有的控件来实现;
将新增项的 Title 设为 “搜索”;
将新增项的 Key Equivalent 设为
⌘ + F;
选择 Connections Inspector;
如果通过复制粘贴创建控件,需要先删除任何已有的连接;
将 Sent Actions -> action 后的小圆圈连接至 First Responder,出现一个弹窗;
在弹窗中选择相应的方法,可以看到第四步中定义的
search:。
这样便实现了通过快捷键调用自定义方法。如果重新打开工程文件后发现第四步中的 User Defined 列表内容为空也不用紧张,之前的功能依然能够正常运行。
3. 重载 ESC 键对应的 Command
ESC 键对应的 Command 是 cancelOperation:,因此只需在对应的 ViewController 中添加相应重载代码:
override func cancelOperation(sender: AnyObject?) { // Pseudo-code if DisplayingSearchView { DisplayCurrencyListView } }
需要注意的是,在 View Controller 为 First Responder 时按键事件才会正确响应。
讨论
1. NSEvent 的 keyCode 是什么?
在讨论如何处理键盘事件的问题中,一般的回答是在 keyDown:方法中通过
theEvent.keyCode与固定的某些值进行判断。Apple
的 Guide 中却完全没有提到
keyCode,在文档中对这一属性的说明:
The property’s value is hardware-independent. The value returned is the same as the value returned in the kEventParamKeyCode when using Carbon Events.
可能在 Cocoa 调用 Carbon 键盘处理相关函数时需要用到
keyCode属性进行转换。
2. NSEvent 的 Characters 是什么?
在判断是否按下 'A' 键时,常用的方法也有判断 theEvent.characters或者
theEvent.charactersIgnoringModifiers是不是
'A' 来实现。
这样的问题在于,如果切换了输入法之后,同一按键的值是有可能会变的:
override func keyDown(theEvent: NSEvent) { print(theEvent.keyCode) print(theEvent.characters) } // Output when press 'A': // // U.S. Input Source // 0 // Optional("a") // // Georgian - QWERTY Input Source // 0 // Optional("ა") // // Cangjie Input Source // 0 // Optional("日")
实际上由于
characters发生变化,在代码中似乎并没有除了
keyCode之外合适的途径来判断按下的究竟是哪个键了。
在 Menu 中设置的 Key Equivalent 则不受输入法的影响,可以正常工作,猜测这一方法最终实现依赖
keyCode。
另外在常用软件 Sketch 中,切换了奇怪的输入法 Menu 中的快捷键也无法响应,猜测 Sketch 的 Menu 中的 Button 是自定义实现的,并且很有可能是用 characters 来判断按键事件。
终于写完了,Happy Coding :smile:。
参考
HandlingKey Events
NSEvent
Class Reference
NSResponder
Class Reference
NSResponder
Class Reference
objective
c - NSTableView + Delete Key - Stack Overflow
Handling
keyboard events in AppKit with Swift
Notes
from a Swift developer: Adding menu items and their actions
相关文章推荐
- 键盘控制事件应用教程大全
- Android 应用设置全局Exception处理事件的方法
- (6):Silverlight 2 键盘事件处理
- 【Android应用开发技术:应用组件】Android事件处理机制
- 应用层与驱动层同步事件处理方法
- QT窗口拖拽功能简单应用(处理dragEnterEvent和dropEvent事件,不同的事件有不同的信息,比如mimeData)
- JS处理键盘事件
- 一步一步学Silverlight 2系列(6):键盘事件处理
- 一步一步学Silverlight 2系列(6):键盘事件处理 (转)
- 【Android应用开发技术:应用组件】事件处理机制
- Cordova 混合应用处理输入法相关事件
- 网页编程----js键盘事件小应用
- IQKeyboardManager(自动处理键盘事件)
- Windows编程-处理键盘事件(2)
- LWUIT的List应用系列(二)List事件的处理
- C#处理鼠标和键盘事件<3>
- java键盘事件处理程序
- QT事件处理,鼠标事件,按键键盘事件,定时器,进度条。
- OpenGL键盘事件处理
- 如何处理Form 上的键盘事件,ProcessCmdKey肯定起作用!!