keyDown not being called
Asked Answered
F

4

9

I have a custom NSView called SurfaceView. It is the contentView of a NSWindow and it handles basic events like mouse click and drawing. But don't matters what I do, it does not handle the keyDown function. I've already override the acceptsFirstResponder but nothing happens.

If it matters, I run the application using a custom NSEvent loop, shown below:

NSDictionary* info = [[NSBundle mainBundle] infoDictionary];
NSString* mainNibName = [info objectForKey:@"NSMainNibFile"];

NSApplication* app = [NSApplication sharedApplication];
NSNib* mainNib = [[NSNib alloc] initWithNibNamed:mainNibName bundle:[NSBundle mainBundle]];
[mainNib instantiateNibWithOwner:app topLevelObjects:nil];

[app finishLaunching];

while(true)
{   
    NSEvent* event = [app nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate date] inMode:NSDefaultRunLoopMode dequeue:YES];
    [app sendEvent:event];

    // Some code is execute here every frame to do some tasks...

    usleep(5000);
}

Here's the SurfaceView code:

@interface SurfaceView : NSView
{
    Panel* panel;
}

@property (nonatomic) Panel* panel;

- (void)drawRect:(NSRect)dirtyRect;
- (BOOL)isFlipped;
- (void)mouseDown:(NSEvent *)theEvent;
- (void)mouseDragged:(NSEvent *)theEvent;
- (void)mouseUp:(NSEvent *)theEvent;
- (void)keyDown:(NSEvent *)theEvent;
- (BOOL)acceptsFirstResponder;
- (BOOL)becomeFirstResponder;

@end

--

@implementation SurfaceView

@synthesize panel;

- (BOOL)acceptsFirstResponder
{
    return YES;
};

- (void)keyDown:(NSEvent *)theEvent
{
    // this function is never called
};

...

@end

Here's how I create the view:

NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(left, top, wide, tall) styleMask:NSBorderlessWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask backing:NSBackingStoreBuffered defer:NO];

...

[window makeKeyAndOrderFront:nil];

SurfaceView* mainView = [SurfaceView alloc];
[mainView initWithFrame:NSMakeRect(0, 0, wide, tall)];
mainView.panel = panel;
[window setContentView:mainView];
[window setInitialFirstResponder:mainView];
[window setNextResponder:mainView];
[window makeFirstResponder:mainView];
Flexion answered 23/7, 2012 at 23:57 Comment(5)
Have you overridden -becomeFirstResponder too? And after the makeFirstResponder call, can you NSLog(@"%@", window.firstResponder) to double check if the SurfaceView really is the First Responder?Spitfire
Just a quick note, delete all of your method declaration in your interface file, as they're already declared in NSView already. No need to declare them again.Dehisce
And also, what's with the semicolons at the end of each method?Dehisce
@Spitfire Yes. And the window.firstResponder is the SurfaceView.Flexion
@theAmateurProgrammer It's a bad practice I earned from C++.Flexion
F
29

I found out what was preventing the keyDown event from being called. It was the NSBorderlessWindowMask mask, it prevents the window from become the key and main window. So I have created a subclass of NSWindow called BorderlessWindow:

@interface BorderlessWindow : NSWindow
{
}

@end

@implementation BorderlessWindow

- (BOOL)canBecomeKeyWindow
{
    return YES;
}

- (BOOL)canBecomeMainWindow
{
    return YES;
}

@end
Flexion answered 24/7, 2012 at 20:38 Comment(6)
Holly s*! You save my day!Massasauga
Thanks so much for this. You saved me a lot of time. For those using swift with NSViewController you can simply make an extension of NSWindow and return true on these methods.Tribble
@Epic Byte - This is not correct. According to the documentation : "You cannot use extensions to override existing methods or properties on Objective-C types"Cynthea
@TeoSartori That comment was made when overriding was possible with extensions.Tribble
Then it is worth correcting. I wasted some time not getting it working before doubting the comment and checking the docs.Cynthea
After two hours of search, this was the solutionDoublefaced
M
2

In addition to answer: Check in your IB checkbox for NSWindow.

Title Bar should be checked. It the similar to NSBorderlessWindowMask

enter image description here

Massasauga answered 21/10, 2014 at 14:0 Comment(0)
H
0

In my case I had to override the keyDown method on both NSView and NSWindow (created a customization of both). In the NSWindow in the keyDown override I had to call

- (void) keyDown:(NSEvent *) event {

    if (self.firstResponder != self) {
    
        [self.firstResponder keyDown:event];
    }
}

to let the event reach the view. Of course I had also to call makeFirstResponder first on the NSWindow and pass the contentView to it :

[window makeFirstResponder: [window contentView] ];
Horrorstruck answered 3/11, 2022 at 14:27 Comment(0)
D
0

The accepted answer saved me a ton of time

I tested out with Swift 5 + Xcode 15, here's how how you write it in Swift

class BorderlessWindow: NSWindow {
  override var canBecomeKey: Bool {
    return true
  }

  override var canBecomeMain: Bool {
    return true
  }
}

Also, I find that canBecomeMain is not necessary. canBecomeKey alone is enough to solve the problem.

Doggy answered 29/3, 2024 at 5:51 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.