11
votes

In hopes of creating a rollover effect on a button, I created a subclass of NSButton called Button.

Button.h:

#import <AppKit/AppKit.h>

@interface Button : NSButton {
}

- (void)mouseEntered:(NSEvent *)theEvent;
- (void)mouseExited:(NSEvent *)theEvent;
- (void)mouseDown:(NSEvent *)ev;
- (void)mouseUp:(NSEvent *)theEvent;

@end

Button.m: #import "Button.h"

@implementation Button

- (id)initWithFrame:(NSRect)frameRect  {
    self = [super initWithFrame:frameRect];
    if(self != nil) {
    NSLog(@"btn init");
}
    return self;
}


- (void)mouseEntered:(NSEvent *)theEvent{
    NSLog(@"mouseEntered");
    [self setImage:[NSImage imageNamed:@"lockIcon_2.png"]];
    [self setNeedsDisplay];
}
- (void)mouseExited:(NSEvent *)theEvent{
    [self setImage:[NSImage imageNamed:@"lockIcon_1.png"]];
    NSLog(@"mouseExited");  
    [self setNeedsDisplay];
}

- (void)mouseDown:(NSEvent *)ev {
    NSLog(@"mouseDown!");
}

- (void)mouseUp:(NSEvent *)ev {
    NSLog(@"mouseUp!");
}

@end

With the code above, every time I click on a button I see "mouseDown" in the logs, but I don't see "mouseEntered" nor "mouseExited" (and of course don't see the image change)?? Sadly, I know I'm missing something obvious, but I'm just not seeing it... ???

2

2 Answers

24
votes

The problem is that NSButton can handle some mouse events only if you add your custom NSTrackingArea to button.

Try to add this code in your button class. It helped me. Also you can play with options if they not satisfied you.

- (void)createTrackingArea
{
    NSTrackingAreaOptions focusTrackingAreaOptions = NSTrackingActiveInActiveApp;
    focusTrackingAreaOptions |= NSTrackingMouseEnteredAndExited;
    focusTrackingAreaOptions |= NSTrackingAssumeInside;
    focusTrackingAreaOptions |= NSTrackingInVisibleRect;

    NSTrackingArea *focusTrackingArea = [[NSTrackingArea alloc] initWithRect:NSZeroRect
            options:focusTrackingAreaOptions owner:self userInfo:nil];
    [self addTrackingArea:focusTrackingArea];
}


- (void)awakeFromNib
{
    [self createTrackingArea];
}

Hope it helps.

1
votes

While setting up a tracking area is definitely a way to do this, NSButton/NSButtonCell already have this functionality built in.

Check out NSButton's showsBorderOnlyWhileMouseInside, and the corresponding NSButtonCell's mouseEntered and mouseExited. It is documented here:

https://developer.apple.com/documentation/appkit/nsbuttoncell/1527903-showsborderonlywhilemouseinside

Depending on your use-case, this may or may not work. For me, I was subclassing NSButtonCell anyways, so it was perfect. One thing to be aware of is it also affects calls to drawBezel.