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.