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:

Animals

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.

Leave a Reply

You must be logged in to post a comment.