1
votes

I'm working with a view-based NSTableView that uses a custom cell view with several controls in it. One of the controls is an image-only NSButton that either shows a checkmark image or no image at all. Additionally, when I mouse over one of these buttons that has no image, I would like a "faded" version of the checkmark image to be displayed while the mouse is inside the button.

I've gotten the code set up using NSTrackingArea to respond to -mouseEntered: and -mouseExited: events, calling -setImage: on the NSButton to show the faded checkmark while the mouse is inside the button, then set the image back to nil when the mouse exits.

As far as I can tell, all that code seems to be working as expected, but while the button will update and show the checkmark image the first time the mouse enters the view, then disappear when it exits, moving the mouse back over the button a second time does not cause the button to redraw for some reason, leaving it always blank after the first redraw no matter how many times the image gets set.

This is a summary of the relevant code:

- (void)refreshActiveButton
{
    self.activeButton.image = self.activeImage;
}

- (void)mouseEntered:(NSEvent *)theEvent
{
    NSLog(@"mouse entered %@", self.listItem.name);
    self.mouseOverActiveButton = YES;
    [self refreshActiveButton];
}

- (void)mouseExited:(NSEvent *)theEvent
{
    NSLog(@"mouse exited %@", self.listItem.name);
    self.mouseOverActiveButton = NO;
    [self refreshActiveButton];
}

- (NSImage*)activeImage
{
    NSLog(@"returning new active image");
    if (self.listItem.isActive)
        return [NSImage imageNamed:@"checkmark16"];
    else if (self.mouseOverActiveButton)
        return fadedCheckmark;
    else
        return nil;
}

The button is set up in a .xib file as a "Momentary Push In" (I've also tried "Momentary Change" - same behavior) with no title displayed and no border.

Running in the debugger, I've been able to confirm that:

  • The -mouseEntered: and -mouseExited: methods do continue to get called when moving the mouse over the button, even when the display doesn't update.
  • The correct image is being assigned to the button, and is returned back from the button instance after being set. (either the fadedCheckmark image when the mouse is inside, or nil when the mouse is outside)

I tried calling -setNeedsDisplay:YES on the button after assigning the image, as well as setting a layerContentRedrawPolicy of NSViewLayerContentsRedrawOnSetNeedsDisplay (the entire table view is layer-backed) with no change in behavior. Edit: also tried disabling layer backing altogether, with no change.

I feel like there must be something obvious staring me in the face that I'm missing, but if anyone can clue me in, I'd appreciate it.

1
Have you tried logging the specific image that is returned from -activeImage? Maybe the image is being set but not to what you expect? In particular, is it possible the self.listItem.isActive state is not being updated as expected?danielpunkass
Sorry - I skimmed and didn't notice you confirmed the correct image is being set.danielpunkass
It does appear to be exactly the same image. In this case, it's either the fadedCheckmark image (when the mouse is inside) or nil (when the mouse is outside). This is all for rows where listItem.isActive is NO.Brian Webster
OK - next brainstorming idea: is it possible this is not redrawing because of some modal runloop thing? This is all in a single drag, right? Maybe you need to run the runloop with some modes that encourage it to redraw? I'm just wildly brainstorming here until somebody knowledgeable comes along ...danielpunkass
Brainstorming is good! But no dragging happening here, just "hovering" the mouse over the button. For giggles I just tried dispatch_async()-ing the setImage: call, but no dice. Maybe I'll reset my PRAM next.Brian Webster

1 Answers

0
votes

The short answer: it's a bug! I've filed it with Apple as radar 20774731, and on Open Radar at http://openradar.appspot.com/radar?id=4957762287042560

The basic sequence is, if you set the button's image to something, then set it back to nil, all subsequent setImage: calls have no effect on the button, forever as far as I can tell. I did forget to mention in the original post that this is all on 10.10.3. I did find some interesting stuff when debugging though.

It appears that once you set an image on the NSButton, after an additional pass of the run loop, it gains an NSImageView as a subview, which is presumably doing the actual drawing of the image. Apple has said they are moving NSCell towards deprecation, so I guess this is the first step, with relieving NSButtonCell of the responsibility of actually drawing the image.

So, the first time you set the image, the image gets set correctly on both the NSButton and the NSImageView. When the image gets set back to nil, they both get their images set back to nil as well. However, when you set the image a second time, while the NSButton's own image property does return back the new image, the underlying NSImageView's image property remains nil. So that might explain why it's not drawing anything.

Ultimately what I ended up doing was using a plain NSImageView with a transparent NSButton right on top of it. I don't get the highlighting you get when you click on a button, but I can live with that.