2013-04-06

How to capture ESC key in a Cocoa application?

Views: 21739 | Add Comments

You may want to capture(or intercept, detect) the ESC key press in a Cocoa application, capture the ESC key down in Application scope for the whole app, or just in Window scope for some certain windows.

The NSApplicationDelegate is not a subclass of NSResponder, so you can not implement the keyDown: method int app delegate. The NSWindowController is a subclass of NSResponder, you may implement keyDown: method in it, but it will not get invoked when user press the ESC key(with exceptions) and not when user is typing in a text input.

The solution is to use a Event Monitor.

Here’s the sample codes:

 1  @interface MyController : NSWindowController{
 2      id eventMonitor;
 3  }
 4  @end
 5  
 6  @implementation MyController
 7  - (void)windowDidLoad{
 8      NSEvent* (^handler)(NSEvent*) = ^(NSEvent *theEvent) {
 9          NSWindow *targetWindow = theEven.window;
10          if (targetWindow != self.window) {
11              return theEvent;
12          }
13          
14          NSEvent *result = theEvent;
15          NSLog(@"event monitor: %@", theEvent);
16          if (theEven.keyCode == 53) {
17              [self myProcessEscKeyDown];
18              result = nil;
19          }
20  
21          return result;
22      };
23      eventMonitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSKeyDownMask handler:handler];
24  }
25  
26  - (void)windowWillClose:(NSNotification *)notification{
27      [NSEvent removeMonitor:eventMonitor];
28  }
@end

All the stuff is down in a NSWindowController subclass, if you want to implement a key capture in application scope, put these codes in the application delegate, replace windowDidLoad: with applicationDidFinishLaunching:, windowWillClose: with applicationWillTerminate:.

 7  - (void)windowDidLoad{

Register a keyboard event handler just as window is loaded.

10          if (targetWindow != self.window) {
11              return theEvent;
12          }

Let events in other windows pass, we only handle events of the window this controller managed.

16          if (theEven.keyCode == 53) {
17              [self myProcessEscKeyDown: theEvent];
18              result = nil;
19          }

Detect ESC key down event, process it, and drop the event(return nil) so the responder chain will never know.

23      eventMonitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSKeyDownMask handler:handler];

Besides NSKeyDown, there are NSKeyUp, NSLeftMouseUp, NSLeftMouseDown, etc. But we only monitor key down event in this example, so set the mask to NSKeyDownMask.

26  - (void)windowWillClose:(NSNotification *)notification{
27      [NSEvent removeMonitor:eventMonitor];
28  }

Deattach the event monitor when window close.

Discussions on other approaches

You can achieve it by implementing the sendEvent: of a NSWindow subclass, and you need to use this NSWindow subclass to build up UI in Interface Builder, not the default NSWindow class. I don’t recommend you to do in this way, because it will add two extra files in you project.

A NSWindowController’s keyDown: will get a ESC press event if the first responder is a WebView, I don’t know what have the WebView done to hand over the event to the controller even there are still other views in the view hierachy. Please let me know if you know the reason.

[Responder chain]

Responder chain

Cocoa Event Architecture and Responder Chain

Posted by ideawu at 2013-04-06 17:02:56 Tags:

Leave a Comment