The text color in a table view's individual cell

I want to change the text color of every individual cell of a table view depending of its value,
but I don’t know how to get an access to such a cell (if it’s possible at all).

Here is my unsuccessfully attempt:


set theTableColumns to theTableView's tableColumns() as list
	set theColumn to item 12 of theTableColumns -- column for interest
	
	set theCount to theDataSource's |count|()
	repeat with aRow from 1 to theCount
		set theDataCell to theColumn's dataCellForRow_(aRow) -- ??
		set theItemList to (item aRow of theDataSource)
		if (theItemList's valueForKey_("theValue")) is in {"a", "b", "c", "d"} then
			beep -- it beeps
			theDataCell's setTextColor_(class "NSColor"'s redColor) -- no result
		else
			theDataCell's setTextColor_(class "NSColor"'s greenColor)
		end if
	end repeat


Heiner

Hi,

I don’t know how to implement this in AppleScriptObjC,
in ObjC usually this event handler does it

  • (void)tableView: (NSTableView*) tableView
    willDisplayCell: (id) cell
    forTableColumn: (NSTableColumn*) tableColumn
    row: (NSInteger) row

Hi Stefan,

indeed in NSTableViewDelegate Protocol Reference exist an instance method ‘tableView:willDisplayCell:forTableColumn:row:’ .

And now I have to finde out how to imlement it into the script. If I got it, I’ll come back to the forum.

Thank you for the hint.

Heiner

Back again.

It’s the same problem: the color is changed for the whole column and not for the single cell because all cells have the same id.


set acell to theColumn's dataCellForRow_(aRow)

... send to:

on tableView_willDisplayCell_forTableColumn_row_(aTableView, acell, aColumn, aRow)
		log aTableView -- ok
		log acell -- result:  <NSTextFieldCell: 0x142ab40> -- for all cells
		log aColumn -- ok
		log aRow -- ok
		acell's setTextColor_(current application's class "NSColor"'s greenColor) -- each cell in the column becomes green
	end tableView_willDisplayCell_forTableColumn_row_


???

Heiner

Of course you must filter the cell you want to change
by given row and column information or by the value of the cell

For example to indicate positive and negative values (pseudo code):

if double value of aCell > 0
set color of aCell to greenColor
else
set color of aCell to redColor
end if

So I did it, similarly as in my first post. The following handler shows why it can’t work (in my opinion).


on tableView_willDisplayCell_forTableColumn_row_(aTableView, acell, aColumn, aRow)
		if aTableView is myTableView then 
			set columnIdent to aColumn's identifier
			if columnIdent as string is equal to "myColumnForInterest" then -- restriction 
				set theTableViewRecord to {aRow, acell}
				log theTableViewRecord
			end if
		end if
end tableView_willDisplayCell_forTableColumn_row_

The result is, for every row I got the same cell’s id. That colors the complete column same.

My question: Is my project to carry out?

Heiner

It must work.

Imagine that the handler works like an old TV display.
When the setNeedsDisplay: method of the table view is called (by reloadData or automatically by a NSArrayController)
each cell is drawn one after another. So the willDisplayCell handler will be called as much as how many cells are there.
The handler provides column (aColumn) and row (aRow) information to be able to identify the cell.
This gives you the capability to manipulate every cell before it’s drawn.

This is an ObjC example which I use to display the disk S.M.A.R.T status in a table view column.
It considers green color for OK and read color for failure and also center alignment and inverse color in case of the row is currently selected


- (void)tableView: (NSTableView*) tableView 
  willDisplayCell: (id) cell 
   forTableColumn: (NSTableColumn*) tableColumn 
			  row: (NSInteger) row 
{ 
    
	BOOL selected;
	NSString *smartText;
	NSDictionary *txtDict;
	NSAttributedString *attrStr;
	NSMutableParagraphStyle *style;
	NSColor *color;
	
	if ([[tableColumn identifier] isEqualToString:kSMART]) {
		selected = [[tableView selectedRowIndexes] containsIndex:row];
		smartText = [cell stringValue];
		if (![smartText isEqualToString:@"--"]) {
			style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
			[style setAlignment:NSCenterTextAlignment];
			color = ([smartText isEqualToString:@"OK"]) ? [NSColor greenColor] : [NSColor redColor];
			if (selected)
				txtDict = [NSDictionary dictionaryWithObjectsAndKeys:[NSFont boldSystemFontOfSize:11], NSFontAttributeName, 
						   [NSColor whiteColor],NSForegroundColorAttributeName, style, NSParagraphStyleAttributeName, nil];
			else
				txtDict = [NSDictionary dictionaryWithObjectsAndKeys:[NSFont systemFontOfSize:11], NSFontAttributeName, 
						   [color blendedColorWithFraction:0.5 ofColor:[NSColor blackColor]], NSForegroundColorAttributeName, 
						   style, NSParagraphStyleAttributeName, nil];
			attrStr = [[NSAttributedString alloc] initWithString:smartText attributes:txtDict];
			[cell setAttributedStringValue:attrStr];
			[attrStr release];
			[style release];
		}
	}
} 


Hi Stefan,
thanks for your example.
As I’m not familiar with Obj-C I need some time to ‘translate’ it into ASOC. I’ll come back later again.

Danke

Heiner

Here is now the result. Because I don’t need to set style and text, the handler is reduced.


on tableView_willDisplayCell_forTableColumn_row_(aTableView, acell, aColumn, aRow)
		if aTableView is myTableView then
			if (aColumn's identifier)'s isEqualToString_("theIdentifier") then
				set isSelected to (myTableView's selectedRowIndexes)'s containsIndex_(aRow)
				set cellContent to acell's stringValue()
				if cellContent is in {"a", "b", "c"} then
					set aColor to current application's class "NSColor"'s greenColor
				else if cellContent is in {"d"} then
					set aColor to current application's class "NSColor"'s orangeColor
				else
					set aColor to current application's class "NSColor"'s redColor
				end if
				if isSelected then
					set txtColor to current application's class "NSColor"'s whiteColor
				else
					set txtColor to aColor
				end if
				acell's setTextColor_(txtColor)
			end if
		end if
	end tableView_willDisplayCell_forTableColumn_row_


Thanks to Stefan again!

Heiner

Hi,

I was wondering of all of this could be accomplished using bindings only?

Thanks to this post http://macscripter.net/viewtopic.php?id=34282 I have the most part of it working without AppleScript code, except for the conditional part.
Is there a way to make formatting of color conditional via bindings?

thanks,
Cliff

Yes, if the data source is managed by an NSArrayController

Hi Stefan,

Could you point me in a direction where to look.

I’m using an ArrayControler, all works fine except for the conditional stuff, for which I hav no clue where to look.
So far I thought I tried all the obvious, to me that is…

Let’s say I try to set the text color of the selected row.

  • I have a row that holds the NSColor
  • in IB I made a binding in the Table Column Bindings’ Text Color part to it’s own Array Controller
    • Controller Key: selectedObjects → I figured this would return true if the row is selected
    • Model Key Path: the key of the column holding the NSColor

If I try to run this setup, I get the following error:

2010-11-17 10:02:18.157 Mail Handtekening[19881:a0f] Cannot create NSColor from object (
“NSCalibratedWhiteColorSpace 1 1”
) of class NSCFArray
2010-11-17 10:02:18.160 Mail Handtekening[19881:a0f] *** Terminating app due to uncaught exception ‘NSInternalInconsistencyException’, reason: ‘Cannot create NSColor from object (
“NSCalibratedWhiteColorSpace 1 1”
) of class NSCFArray’
*** Call stack at first throw:
(
0 CoreFoundation 0x00007fff85d3a7b4 __exceptionPreprocess + 180
1 libobjc.A.dylib 0x00007fff8770d0f3 objc_exception_throw + 45
2 CoreFoundation 0x00007fff85d3a5d7 +[NSException raise:format:arguments:] + 103
3 AppKit 0x00007fff824c1108 _NSColorFromValue + 201
4 AppKit 0x00007fff823ed123 -[NSTextColorBinder _setTextColorInObject:mode:compareDirectly:toTextColor:] + 155
5 AppKit 0x00007fff823ecb53 -[NSTextColorBinder updateTableColumnDataCell:forDisplayAtIndex:] + 117
6 AppKit 0x00007fff82099c48 -[_NSBindingAdaptor tableColumn:willDisplayCell:row:] + 106
7 AppKit 0x00007fff81f83a84 -[NSTableView preparedCellAtColumn:row:] + 455
8 AppKit 0x00007fff81f9c8e7 -[NSTableView _drawContentsAtRow:column:withCellFrame:] + 47
9 AppKit 0x00007fff81f9b980 -[NSTableView drawRow:clipRect:] + 1242
10 AppKit 0x00007fff81f9b273 -[NSTableView drawRowIndexes:clipRect:] + 369
11 AppKit 0x00007fff81f99c20 -[NSTableView drawRect:] + 1302
12 AppKit 0x00007fff81f8fc49 -[NSView _drawRect:clip:] + 3390
13 AppKit 0x00007fff81f8e8bc -[NSView _recursiveDisplayAllDirtyWithLockFocus:visRect:] + 1325
14 AppKit 0x00007fff81f8ec26 -[NSView _recursiveDisplayAllDirtyWithLockFocus:visRect:] + 2199
15 AppKit 0x00007fff81f8ec26 -[NSView _recursiveDisplayAllDirtyWithLockFocus:visRect:] + 2199
16 AppKit 0x00007fff81f8ec26 -[NSView _recursiveDisplayAllDirtyWithLockFocus:visRect:] + 2199
17 AppKit 0x00007fff81f8ec26 -[NSView _recursiveDisplayAllDirtyWithLockFocus:visRect:] + 2199
18 AppKit 0x00007fff81f8ec26 -[NSView _recursiveDisplayAllDirtyWithLockFocus:visRect:] + 2199
19 AppKit 0x00007fff81f8ec26 -[NSView _recursiveDisplayAllDirtyWithLockFocus:visRect:] + 2199
20 AppKit 0x00007fff81f8ec26 -[NSView _recursiveDisplayAllDirtyWithLockFocus:visRect:] + 2199
21 AppKit 0x00007fff81f8cf8e -[NSView _recursiveDisplayRectIfNeededIgnoringOpacity:isVisibleRect:rectIsVisibleRectForView:topView:] + 767
22 AppKit 0x00007fff81f8cab0 -[NSThemeFrame _recursiveDisplayRectIfNeededIgnoringOpacity:isVisibleRect:rectIsVisibleRectForView:topView:] + 254
23 AppKit 0x00007fff81f89362 -[NSView _displayRectIgnoringOpacity:isVisibleRect:rectIsVisibleRectForView:] + 2683
24 AppKit 0x00007fff81f02b9a -[NSView displayIfNeeded] + 969
25 AppKit 0x00007fff81ecabe3 -[NSWindow _reallyDoOrderWindow:relativeTo:findKey:forCounter:force:isModal:] + 1050
26 AppKit 0x00007fff81eca77a -[NSWindow orderWindow:relativeTo:] + 94
27 AppKit 0x00007fff81e96770 -[NSIBObjectData nibInstantiateWithOwner:topLevelObjects:] + 1726
28 AppKit 0x00007fff81e9488d loadNib + 226
29 AppKit 0x00007fff81e93d9a +[NSBundle(NSNibLoading) _loadNibFile:nameTable:withZone:ownerBundle:] + 248
30 AppKit 0x00007fff81e93bd2 +[NSBundle(NSNibLoading) loadNibNamed:owner:] + 326
31 AppKit Program received signal: “SIGABRT”.
0x00007fff81e91153 NSApplicationMain + 279
32 Mail Handtekening 0x0000000100000ef2 main + 70
33 Mail Handtekening 0x0000000100000ea4 start + 52
34 ??? 0x0000000000000001 0x0 + 1
)

If I use ‘selection’ as the Controller Key for the binding, it is actually using my NSColor, except for the conditional part I hoped to accomplish. If I click a row, every row get’s the color of the clicked row’s NSColor. This might make sense since I used the binding of the Table Column in stead of the Text Field Cell.
If remove the binding from the Table Column and apply it on the Text Field Cell’s Text Color, it kind of works, the wrong way though…
Let’s say I click the second row, which holds colorRed in it’s NSColor, nothing happens, if I then click row three, which holds colorGreen, it turns red. It turns out this way the invoked row’s color has only influence on the next clicked row.

Anyway, I guess I overlooked the fact that I can’t restore the original text color when a row get’s ‘unclicked’, via bindings.

thanks,
Cliff

as far as I know only arrangedObjects array can be bound to control the text color of single cells.

To change the text color of currently selected cells is very easy using the table view delegate method


- (void)tableView: (NSTableView*) tableView 
  willDisplayCell: (id) cell 
   forTableColumn: (NSTableColumn*) tableColumn 
			  row: (int) row 
{ 
	if ([cell isHighlighted]) {
		[cell setTextColor:[NSColor redColor]];
	}
} 

Yes I’m using the ASOC counterpart of this at the moment, but like to move to bindings as far as possible.

Call it lazy, but I like the less code idea…

thanks,
Cliff

Right, but sometimes the delegate way is the only one.
For example you can’t bind the title of a button cell in table view,
actually you can, but the result is weird. The only way to accomplish this is the same delegate method

I would like to make some remarks to my post #9.

The posted handler works without any bindings to an additional array controller. It works only with the delegate method! That means:

  1. Set the table view as a property (here: property MyTableView: missing value) ; Save!

  2. Open the HUD-Window of the table view.
    a) Bind it’s name in the Referencing Outlets to the AppDelegate’s cube (or File’s Owner cube in a document based app)
    b) Bind the ‘delegate’ in the Outlets to the AppDelegate’s cube (or File’s Owner cube in a document based app.

That’s all of bindings.

Put the on tableView_willDisplayCell_forTableColumn_row_(aTableView, acell, aColumn, aRow) - handler somewhere in your script. The ‘system’ will call repetitive the information of the table view.

Try something like this:


on tableView_willDisplayCell_forTableColumn_row_(aTableView, acell, aColumn, aRow) 

log aTableView
log acell
log aColumn
log aRow

end tableView_willDisplayCell_forTableColumn_row_

Hope it helps

Heiner

The next problem with text color…

Setting the text color on selection, as mentioned by Heiner and Stefan, works great.

Now for the looks I’d like to use “Source List” Highlighting for my table view in IB.
Guess what, text coloring stops working if you don’t use black as the standard text color.

Any ideas?

thanks,
Cliff

You can use a dark blue color too.

But as the documentation says:

And that is a light gray color by default. I think that makes a ‘color-mixture’ which is not defined.

And there is another problem: you can’t make an empty selection.

Heiner

FYI, passing a NSMutableAttributedString to the table’s arrayController in stead of just a AS string seems to solve the aforementioned color displaying problem while using “Source List” Highlighting for my table view.

regards,
Cliff