1

OK I'm really stumped on this one. I want to make a checkbox with a NSTextFieldCell combined together. It's important that the checkbox goes ON if the mouse hits the box, NOT the text. I've accomplished this, more or less, but the issue is receiving the mouse event because I click one checkbox in a row, but ALL of them turn to NSOnState. I will show what I've done and my various failed attempts in order to get this to work.

So this is how I've done it so far:

header:

@interface MyCheckboxCellToo : NSTextFieldCell {
   NSButtonCell *_checkboxCell;
}

implementation:

- (NSUInteger)hitTestForEvent:(NSEvent *)event inRect:(NSRect)cellFrame ofView:(NSView *)controlView {
    NSPoint point = [controlView convertPoint:[event locationInWindow] fromView:nil];
    NSLog(@"%@", NSStringFromPoint(point));

    NSRect checkFrame;
    NSDivideRect(cellFrame, &checkFrame, &cellFrame, cellFrame.size.height/*3 + [[_checkboxCell image] size].width*/, NSMinXEdge);

    if (NSMouseInRect(point, checkFrame, [controlView isFlipped])) {
        // the checkbox, or the small region around it, was hit. so let's flip the state
        NSCellStateValue checkState = ([_checkboxCell state] == NSOnState) ? NSOffState:NSOnState;
        [self setState:checkState];
        [_checkboxCell setState:checkState];
        [controlView setNeedsDisplay:YES];
        return NSCellHitTrackableArea;
    }        
    return [super hitTestForEvent:event inRect:cellFrame ofView:controlView];    
}

I know I probably shouldn't be doing:

   [self setState:checkState];
   [_checkboxCell setState:checkState];
   [controlView setNeedsDisplay:YES];

in there... because the result is that EVERY checkbox in every goes to NSOnState. Is this because cells are re-used? How come the ImageAndTextCell can have different images in the same tableview? How do I handle the mouse event?

I have tried:

- (BOOL)trackMouse:(NSEvent *)theEvent inRect:(NSRect)cellFrame
ofView:(NSView *)controlView untilMouseUp:(BOOL)untilMouseUp {
   NSLog(@"%s %@", _cmd, theEvent);
   return [_checkboxCell trackMouse:theEvent inRect:cellFrame
ofView:controlView untilMouseUp:untilMouseUp];
//    return YES;
//    return [super trackMouse:theEvent inRect:cellFrame
ofView:controlView untilMouseUp:untilMouseUp];
}

- (BOOL)startTrackingAt:(NSPoint)startPoint inView:(NSView *)controlView {
   NSLog(@"%s %@", _cmd, NSStringFromPoint(startPoint));
   return [super startTrackingAt:startPoint inView:controlView];
}

- (BOOL)continueTracking:(NSPoint)lastPoint at:(NSPoint)currentPoint
inView:(NSView *)controlView {
   NSLog(@"%s %@", _cmd, NSStringFromPoint(currentPoint));
   return [super continueTracking:lastPoint at:currentPoint
inView:controlView];
}

- (void)stopTracking:(NSPoint)lastPoint at:(NSPoint)stopPoint
inView:(NSView *)controlView mouseIsUp:(BOOL)flag {
   NSLog(@"%s %d %@", _cmd, flag, NSStringFromPoint(stopPoint));
}

trackMouse: ... DOES gets called

but

startTrackingAt:..., continueTracking:..., and stopTracking:.... DO NOT get called when I click on the checkbox "hit area"

in trackMouse:... I have tried

return [_checkboxCell trackMouse:theEvent inRect:cellFrame ofView:controlView untilMouseUp:untilMouseUp];

and

return [super trackMouse:theEvent inRect:cellFrame ofView:controlView untilMouseUp:untilMouseUp];

and neither seems to result in the mouse event being handled by the checkbox.

How do I get that single checkbox to go NSOnState? I know I'm pretty close but after a lot of doc reading and google searching I haven't been successful at solving this.

suggestions and comments welcome..


OK here is a bit more to show creation and destruction of the object..

- (id)init {
    if ((self = [super init])) {
        _checkboxCell = [[NSButtonCell alloc] init];
        [_checkboxCell setButtonType:NSSwitchButton];
        [_checkboxCell setTitle:@""];
        [_checkboxCell setTarget:self];
        [_checkboxCell setImagePosition:NSImageLeft];
        [_checkboxCell setControlSize:NSRegularControlSize];

    }
    return self;
}

- copyWithZone:(NSZone *)zone {
    MyCheckboxCellToo *cell = (MyCheckboxCellToo *)[super copyWithZone:zone];
    cell->_checkboxCell = [_checkboxCell copyWithZone:zone];
    return cell;
}

- (void)dealloc {
    [_checkboxCell release];
    [super dealloc];
}
maz
  • 8,056
  • 4
  • 26
  • 25
  • You might find it easier to simply make these separate columns, since a checkbox and text field refer to different properties of the model object. (Do you have them referring to the same property somehow?) – Peter Hosey Jun 11 '10 at 05:39
  • I'm splitting the functionality so that clicking the text will allow the user to 'browse' the item without having to select it(which the checkbox will do). – maz Jun 15 '10 at 19:07

3 Answers3

0

I can't directly answer the handling-events-in-cells part of the question, but I can answer these parts:

I know I probably shouldn't be doing:

[self setState:checkState];
[_checkboxCell setState:checkState];
[controlView setNeedsDisplay:YES];

in there... because the result is that EVERY checkbox in every goes to NSOnState. Is this because cells are re-used?

They're reused in a different sense from how UITableView reuses UITableViewCells.

(I noticed that you tagged the question “NSTableViewCell”. No such class exists in AppKit.)

How come the ImageAndTextCell can have different images in the same tableview?

The answer to both questions is the same.

While UITableView uses one cell for each row, and keeps on hand as many cells as there are rows on the screen, NSTableView uses a single cell for every column.

Each column exists as an NSTableColumn object, and each NSTableColumn has exactly one cell*. The table view sets the cell's object value to whatever value you provided for that column-row intersection, and then tells the cell to draw at the appropriate place within its bounds. It'll do the same thing, with that same cell, for that column for every row.

(There are exceptions here, since Apple added the ability to use a different cell for specific rows, and since they added group rows, which have no columns and use a different cell. The fundamental mechanism is the same, though.)

I'm sorry that I can't help you all the way to a solution (aside from my just-make-them-separate-columns suggestion), but I hope this at least helps you part of the way.

*It has a header cell, too, but that doesn't count here. The “exactly one cell” I'm talking about is its data cell.

Peter Hosey
  • 95,783
  • 15
  • 211
  • 370
  • HI Peter, if care to take a look on question I'll be more than grateful if you're able to help me. Thanks. Here is the link: http://stackoverflow.com/questions/31599502/nsbuttoncell-nsswitchbutton-doesnt-display-checked-box – neowinston Jul 24 '15 at 14:07
0

Ok this is what appeared to fix my issue, more or less.

in hitForTestEvent: ...

instead of

    if (NSMouseInRect(point, checkFrame, [controlView isFlipped])) {
    // the checkbox, or the small region around it, was hit. so let's flip the state
    NSCellStateValue checkState = ([_checkboxCell state] == NSOnState) ? NSOffState:NSOnState;
    [self setState:checkState];
    [_checkboxCell setState:checkState];
    [controlView setNeedsDisplay:YES];
    return NSCellHitTrackableArea;
}        

it should be:

    if (NSMouseInRect(point, checkFrame, [controlView isFlipped])) {
    [_checkboxCell setState:![_checkboxCell state]];
    return NSCellHitTrackableArea;
}        

I still have some issues where if I click the checkbox hit zone in a certain spot it doesn't register but more or less this works.

maz
  • 8,056
  • 4
  • 26
  • 25
0

I created a custom cell class that's a subclass of NSButtonCell instead of NSTextFieldCell. It returns 0 in hitTestForEvent:inRect:ofView: if the click isn't on the checkbox.

When the user clicks on the checkbox, in the NSBrowser delegate's browser:setObjectValue:forItem: I get an NSBoolean and set the "checked" ivar of my corresponding "node" object equal to that NSBoolean.

In the NSBrowser delegate's browser:willDisplayCell:atRow:column: method I call [browser itemAtIndex:] to get my "node", and call the cell's setState: method with the node's "checked" ivar.

Check out the "ComplexBrowser" Xcode sample project for sample code on the browser delegate stuff.

Whatever you do, I don't recommend subclassing NSBrowserCell. That was a big crashy dead-end for me.

UPDATE Aug 30, 2011: This doesn't work in 10.5. Ugh. I'm back to square one.

Stefan
  • 1,248
  • 11
  • 14