Cell in Selected NSTableView Not Redrawn

  • In an NSTableView I am providing custom cells via tableView:dataCellForTableColumn:row:, based on values represented in other columns. The cell is usually determined when a value is specified via NSPopupButtonCells in two other columns. When an effecting value is changed, I call reloadDataForRowIndexes:columnIndexes: to refresh the cell(s) in question. The problem is that even thought the delegate method is properly called to return the changed NSCell, the NSTableView will not redraw the cell until the row is deselected or is refreshed by some other means--such as obscuring the tableview with another window or clicking in the cell (but only when it's data cell is editable). I have also tried calling setNeedsDisplayInRect:, but that doesn't work either.

    Oddly, it seems to work fine when called from an action method assigned to the NSMenuItems level of a particular NSPopupButtonCell, but not when called from an action method assigned to the NSPopupButtonCell itself. Although I did try to specify the action for the individual NSMenuItems in the other popup, but that didn't work either. The main difference between the two is that one is specified via Interface Builder and the other is generated in code.

    Code is below. Any help is appreciated,

    Keary Suska
    Esoteritech, Inc.
    "Demystifying technology for your home or business"

    // this method seems to work as expected

    - (IBAction)selectColumn:(id)sender
    {
      // set expression value
      ClauseExpression *expression = [self clauseExpressionForRow:[queryTableView selectedRow]];
      [expression setColumn:[[sender representedObject] description]];

      // set in column cache
      if( [sender representedObject] )
      {
        // different also means clear join operand
        if( [columnCache objectAtIndex:[queryTableView selectedRow]] != [sender representedObject] )
        {
          [columnCache replaceObjectAtIndex:[queryTableView selectedRow] withObject:[sender representedObject]];
          if( [queryTableView selectedRow] > (NSInteger)[[selectQuery whereClause] countOfExpressions] )
          {
            JoinClause *join = [selectQuery objectInJoinClausesAtIndex:([queryTableView selectedRow]-[[selectQuery whereClause] countOfExpressions]-1)];
            [[join joinExpression] setOperand:[ClauseOperand nullOperand]];
            [join setJoinTable:nil];
          }
        }

        // only for WHERE expressions, string columns should default to ILIKE
        if( [queryTableView selectedRow] < (NSInteger)[[selectQuery whereClause] countOfExpressions] && [[sender representedObject] attributeType] == NSStringAttributeType && [[expression comparator] isEqualToString:@"="] )
          [expression setComparator:@"ILIKE"];
      }
      else
      {
        [columnCache replaceObjectAtIndex:[queryTableView selectedRow] withObject:[NSNull null]];
      }

      // column changes may change comparator and operand displays
      [queryTableView reloadDataForRowIndexes:[NSIndexSet indexSetWithIndex:[queryTableView selectedRow]] columnIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(3, 2)]];
    }

    // this one does not

    - (IBAction)selectComparator:(id)sender
    {
      // clear operand
      [[self clauseExpressionForRow:[queryTableView selectedRow]] setOperand:[ClauseOperand nullOperand]];

      // comparator changes may change operand displays
      [queryTableView reloadDataForRowIndexes:[NSIndexSet indexSetWithIndex:[queryTableView selectedRow]] columnIndexes:[NSIndexSet indexSetWithIndex:4]];
    }

    // the most relevant part of this method would be the part immediately under "case 4:"

    - (NSCell *)tableView:(NSTableView *)tableView dataCellForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex
    {
      // most rows are not group rows
      if( tableColumn )
      {
        ClauseExpression *expression = [self clauseExpressionForRow:rowIndex];

        // what we do depends on column
        switch( [tableView columnWithIdentifier:[tableColumn identifier]] )
        {
          case 0:
            // don't return conjunction pop only if we don't have one
            if( ! [expression conjunction] )
            {
              NSTextFieldCell *cell = [[NSTextFieldCell alloc] initTextCell:@""];
              [cell setEditable:NO];
              return [cell autorelease];
            }
            break;
          case 4:
          {
            // show operand only if column selection is non-null and comparator is not a null type
            if( [[expression comparator] rangeOfString:@"NULL"].location != NSNotFound )
            {
              NSTextFieldCell *cell = [[NSTextFieldCell alloc] initTextCell:@""];
              return [cell autorelease];
            }
            else if( [columnCache objectAtIndex:rowIndex] != [NSNull null] )
            {
              // value vs join
              if( rowIndex < (NSInteger)[[selectQuery whereClause] countOfExpressions] )
              {
                // value operand depends on column type
                switch( [[columnCache objectAtIndex:rowIndex] attributeType] )
                {
                  case NSStringAttributeType:
                  case NSInteger16AttributeType:
                  case NSInteger32AttributeType:
                  case NSInteger64AttributeType:
                  case NSDecimalAttributeType:
                  case NSDoubleAttributeType:
                  case NSFloatAttributeType:
                  case NSDateAttributeType:
                  {
                    NSTextFieldCell *cell = [[NSTextFieldCell alloc] initTextCell:@""];
                    [cell setEditable:YES];
                    [cell setPlaceholderString:@"Enter value here"];
                    return [cell autorelease];
                    break;
                  }
                  case NSBooleanAttributeType:
                  {
                    NSButtonCell *cell = [[NSButtonCell alloc] initTextCell:@"Yes (True/On)"];
                    [cell setButtonType:NSSwitchButton];
                    [cell setTarget:self];
                    [cell setAction:@selector(specifyOperand:)];
                    return [cell autorelease];
                    break;
                  }
                }
              }
              else
              {
                NSPopUpButtonCell *cell = [[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO];
                [cell setAutoenablesItems:NO];
                [cell setControlSize:NSSmallControlSize];
                [cell setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSSmallControlSize]]];
                [cell setBordered:NO];
                return [cell autorelease];
              }
            }
            else
            {
              // otherwise blank
              NSTextFieldCell *cell = [[NSTextFieldCell alloc] initTextCell:@""];
              return [cell autorelease];
            }
            break;
          }
        }
      }
      else if( [self tableView:tableView isGroupRow:rowIndex] )
      {
        NSTextFieldCell *cell = [[NSTextFieldCell alloc] initTextCell:@""];
        return [cell autorelease];
      }

      // return default
      return [tableColumn dataCellForRow:rowIndex];
    }
  • On Jul 20, 2012, at 19:03 , Keary Suska wrote:

    > In an NSTableView I am providing custom cells via tableView:dataCellForTableColumn:row:, based on values represented in other columns. The cell is usually determined when a value is specified via NSPopupButtonCells in two other columns. When an effecting value is changed, I call reloadDataForRowIndexes:columnIndexes: to refresh the cell(s) in question. The problem is that even thought the delegate method is properly called to return the changed NSCell, the NSTableView will not redraw the cell until the row is deselected or is refreshed by some other means--such as obscuring the tableview with another window or clicking in the cell (but only when it's data cell is editable). I have also tried calling setNeedsDisplayInRect:, but that doesn't work either.
    >
    > Oddly, it seems to work fine when called from an action method assigned to the NSMenuItems level of a particular NSPopupButtonCell, but not when called from an action method assigned to the NSPopupButtonCell itself. Although I did try to specify the action for the individual NSMenuItems in the other popup, but that didn't work either. The main difference between the two is that one is specified via Interface Builder and the other is generated in code.

    > - (IBAction)selectComparator:(id)sender
    > {
    > // clear operand
    > [[self clauseExpressionForRow:[queryTableView selectedRow]] setOperand:[ClauseOperand nullOperand]];
    >
    > // comparator changes may change operand displays
    > [queryTableView reloadDataForRowIndexes:[NSIndexSet indexSetWithIndex:[queryTableView selectedRow]] columnIndexes:[NSIndexSet indexSetWithIndex:4]];
    > }

    So, to clarify, when it fails, are you saying:

    1. It doesn't get to 'selectComparator:'.

    2. It gets there, but 'dataCellForTableColumn:' doesn't get called for column 4 (at least, not until later, at the point where the row really redraws).

    3. It gets to 'dataCellForTableColumn:' for the right row/column combinations at the expected time, but the display just doesn't redraw.

    ?

    What happens if you simply 'reloadData' instead of trying to be specific, and/or what happens if you simply 'setNeedsDisplay:' instead of trying to be specific?

    Also, I notice that if '[queryTableView selectedRow]' isn't what you are expecting, when 'selectComparator:' is called, you'd get precisely the behavior you describe.
  • On Jul 20, 2012, at 9:36 PM, Quincey Morris wrote:

    > On Jul 20, 2012, at 19:03 , Keary Suska wrote:
    >
    >> In an NSTableView I am providing custom cells via tableView:dataCellForTableColumn:row:, based on values represented in other columns. The cell is usually determined when a value is specified via NSPopupButtonCells in two other columns. When an effecting value is changed, I call reloadDataForRowIndexes:columnIndexes: to refresh the cell(s) in question. The problem is that even thought the delegate method is properly called to return the changed NSCell, the NSTableView will not redraw the cell until the row is deselected or is refreshed by some other means--such as obscuring the tableview with another window or clicking in the cell (but only when it's data cell is editable). I have also tried calling setNeedsDisplayInRect:, but that doesn't work either.
    >>
    >> Oddly, it seems to work fine when called from an action method assigned to the NSMenuItems level of a particular NSPopupButtonCell, but not when called from an action method assigned to the NSPopupButtonCell itself. Although I did try to specify the action for the individual NSMenuItems in the other popup, but that didn't work either. The main difference between the two is that one is specified via Interface Builder and the other is generated in code.
    >
    >> - (IBAction)selectComparator:(id)sender
    >> {
    >> // clear operand
    >> [[self clauseExpressionForRow:[queryTableView selectedRow]] setOperand:[ClauseOperand nullOperand]];
    >>
    >> // comparator changes may change operand displays
    >> [queryTableView reloadDataForRowIndexes:[NSIndexSet indexSetWithIndex:[queryTableView selectedRow]] columnIndexes:[NSIndexSet indexSetWithIndex:4]];
    >> }
    >
    > So, to clarify, when it fails, are you saying:
    >
    > 1. It doesn't get to 'selectComparator:'.

    No, it does.

    > 2. It gets there, but 'dataCellForTableColumn:' doesn't get called for column 4 (at least, not until later, at the point where the row really redraws).

    NSLog shows that the delegate method *is* called, and the expected data cell is being returned for column 4.

    > 3. It gets to 'dataCellForTableColumn:' for the right row/column combinations at the expected time, but the display just doesn't redraw.

    Exactly.

    > What happens if you simply 'reloadData' instead of trying to be specific, and/or what happens if you simply 'setNeedsDisplay:' instead of trying to be specific?

    I tried reloadData, to no effect. I just tried the setNeedsDisplay: and that didn't work either.

    > Also, I notice that if '[queryTableView selectedRow]' isn't what you are expecting, when 'selectComparator:' is called, you'd get precisely the behavior you describe.

    NSLogs show that it is returning expected values. I agree that it "feels" a little fragile but I don't know what other options I have--doing this in the setObjectValue delegate call seems even more fragile.

    Curiously, the following workaround works:

    Change reloadDataForRowIndexes to:

      [self performSelector:@selector(refreshCellAtRect:) withObject:[NSValue valueWithRect:[queryTableView frameOfCellAtColumn:4 row:[queryTableView selectedRow]]] afterDelay:0.0];

    With:

    - (void)refreshCellAtRect:(NSValue *)rectValue;
    {
      [queryTableView setNeedsDisplayInRect:[rectValue rectValue]];
    }

    So there must be something with redraw and the event loop. Anyway, this almost seems like a bug, but I wanted to be sure before I report it.

    Keary Suska
    Esoteritech, Inc.
    "Demystifying technology for your home or business"
  • >> On Jul 20, 2012, at 19:03 , Keary Suska wrote:
    >>
    >>> In an NSTableView I am providing custom cells via tableView:dataCellForTableColumn:row:, based on values represented in other columns. The cell is usually determined when a value is specified via NSPopupButtonCells in two other columns. When an effecting value is changed, I call reloadDataForRowIndexes:columnIndexes: to refresh the cell(s) in question. The problem is that even thought the delegate method is properly called to return the changed NSCell, the NSTableView will not redraw the cell until the row is deselected or is refreshed by some other means--such as obscuring the tableview with another window or clicking in the cell (but only when it's data cell is editable). I have also tried calling setNeedsDisplayInRect:, but that doesn't work either.
    >>>
    >>> Oddly, it seems to work fine when called from an action method assigned to the NSMenuItems level of a particular NSPopupButtonCell, but not when called from an action method assigned to the NSPopupButtonCell itself. Although I did try to specify the action for the individual NSMenuItems in the other popup, but that didn't work either. The main difference between the two is that one is specified via Interface Builder and the other is generated in code.

    For posterity, it is actually not quite the case that one action method works while the other doesn't. It turns out that it works only the first time a change is made. Subsequent changes, using either action method, do not redraw.

    Best,

    Keary Suska
    Esoteritech, Inc.
    "Demystifying technology for your home or business"
previous month july 2012 next month
MTWTFSS
            1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31          
Go to today