Putting radio buttons in Cappuccino CPTableViews
Cappuccino’s table views aren’t fully finished, so—in version 0.7.1—you can’t just add radio buttons to a table column and have them work. Nevertheless, I wanted something like this:
Since I spent some time figuring out a workaround, I thought I’d save someone some time and write it up.
The workaround used subclasses of CPCheckbox and CPTableColumn. Those were used in the normal way when laying out the window:
var checkColumn = [[CheckboxTableColumn alloc] initWithIdentifier:@“checks“]; // … var checkButton = [[CritterCheckBox alloc] init]; [checkButton setTarget: animalController]; [checkButton setAction: @selector(toggleAnimal:)]; [checkColumn setDataCell: checkButton]
(nib2cib doesn’t work yet for my application.)
The controller needs to know which checkbox got clicked. I decided to put an index in each checkbox and implement clickedRow on it. (That allowed the action method to ignore whether it was invoked by the checkbox or by selecting a table row, which turns out to be convenient for this application.) The table column’s dataViewForRow: method returns a numbered checkbox. So far, that looks like this:
@implementation CritterCheckBox : CPCheckBox { (CPInteger) index; } - (CPInteger) clickedRow { return index; } // … @end @implementation CheckboxTableColumn : CPTableColumn { } - (id) dataViewForRow: (CPInteger) row { var retval = [[CritterCheckBox alloc] init]; var target = [[self dataCell] target]; [retval setTarget: target]; [retval setAction: [[self dataCell] action]]; retval.index = row; // .. return retval; } @end
It turns out that CPTableView makes copies of the cells it gets from the CPTableColumn. It uses CPKeyedArchiver/CPKeyedUnarchiver to do that, so those have to be implemented:
@implementation CritterCheckBox : CPCheckBox //… - (void) encodeWithCoder: (CPCoder)aCoder { [super encodeWithCoder:aCoder]; [aCoder encodeObject: index forKey: @“critter row index“]; } - (void) initWithCoder: (CPCoder)aCoder { self = [super initWithCoder:aCoder]; index = [aCoder decodeObjectForKey: @“critter row index“]; [self setTarget: GlobalCheckboxTarget]; // < <<<<<<<<<<<<<<<< return self; } @end
The line marked with arrows is a gross hack. CPControls will archive and unarchive actions, but not targets. I decided to fake that by stuffing the target in a global variable and reassigning it on every unarchiving.
When the column is displayed, some of the checkboxes should start off checked. So when a checkbox is created, it asks its target for the appropriate value in the normal tableView:objectValueForTableColumn:row: way:
- (id) dataViewForRow: (CPInteger) row { // … checked = [target tableView: nil objectValueForTableColumn: target.checkColumn row: row]; if (checked) [retval setState: CPOnState]; return retval; }
I know a lot of this is stylistically crummy Objective-J. Part of the reason is that I haven’t figured out an Objective-J style yet.
Suggestions about better approaches welcome.
The CheckboxHacks.j source is available on github. So is the whole tree.