如何在 MAC OS X 应用程序中更改 NSTableView header 背景颜色?
How to change NSTableView header background color in MAC OS X app?
我已经尝试了所有找到的建议解决方案,但最终以最接近的解决方案结束:
目标是自定义颜色:
- 完整 header 背景(例如绿色)
- 文本(例如白色)
- 排序控件颜色(例如白色)
目前我只能正确设置内部背景色和文本颜色,同时将 header 边框和排序控件保留为默认的白色。
我使用自定义的方法NCTableHeaderCell
。
// <C> changing the bgColor doesn't work this way
[self.tv.headerView setWantsLayer:YES];
self.tv.headerView.layer.backgroundColor = NSColor.redColor.CGColor;
for (NSTableColumn *tc in self.tv.tableColumns) {
// <C> this only helps to change the header text color
tc.headerCell = [[NCTableHeaderCell_customizable alloc]initTextCell:@"Hdr"];
// <C> this changes the bgColor of the area of the headerCell label text (the interior) but it leaves border and sort controls in white color;
tc.headerCell.drawsBackground = YES;
tc.headerCell.backgroundColor = NSColor.greenColor;
// <C> changing the textColor doesn't work this way
// <SOLUTION> use NCTableHeaderCell_customizable as done above;
tc.headerCell.textColor = NSColor.redColor;
}
我的习惯 class 看起来像这样:
@implementation NCTableHeaderCell_customizable
// <C> this works as expected
- (NSColor *) textColor
{
return NSColor.whiteColor;
}
// <C> this only sets the interior bgColor leaving the borders in standard color
//
//- (NSColor *) backgroundColor
//{
// return NSColor.redColor;
//}
- (void) drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView;
{
// <C> this only sets the interior bgColor leaving the borders in standard color
//
//self.backgroundColor = NSColor.orangeColor;
[super drawWithFrame:cellFrame inView:controlView];
// <C> this draws the red bg as expected but doesn't show the interior;
//
// [NSColor.redColor set];
// NSRectFillUsingOperation(cellFrame, NSCompositingOperationSourceOver);
// <C> this draws the red bg as expected but
// 1) doesn't layout the interior well (I could fix this);
// 2) doesn't show the sort controls (it's over-drawn with the code bellow);
//
// [NSColor.redColor setFill];
// NSRectFill(cellFrame);
// CGRect titleRect = [self titleRectForBounds:cellFrame];
// [self.attributedStringValue drawInRect:titleRect];
}
- (void) drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView;
{
[super drawInteriorWithFrame:cellFrame inView:controlView];
}
- (void) drawFocusRingMaskWithFrame:(NSRect)cellFrame inView:(NSView *)controlView;
{
[super drawFocusRingMaskWithFrame:cellFrame inView:controlView];
}
- (void) drawSortIndicatorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView ascending:(BOOL)ascending priority:(NSInteger)priority;
{
[super drawSortIndicatorWithFrame:cellFrame inView:controlView ascending:ascending priority:priority];
//NSTableHeaderView *v = (NSTableHeaderView *)controlView;
}
我非常接近解决方案,但我不知道如何正确绘制自定义 header 单元格来存档目标。
我没有找到其他方法,只需要画出一切。希望对你有帮助。
CGRect outCellFrame;
- (void) drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView;
{
// <C> this only sets the interior bgColor leaving the borders in standard color
outCellFrame = cellFrame;
[super drawWithFrame:cellFrame inView:controlView];
// <C> this draws the red bg as expected but doesn't show the interior;
//
// <C> this draws the red bg as expected but
// 1) doesn't layout the interior well (I could fix this);
// 2) doesn't show the sort controls (it's over-drawn with the code bellow);
//
// [NSColor.redColor setFill];
// NSRectFill(cellFrame);
// CGRect titleRect = [self titleRectForBounds:cellFrame];
// [self.attributedStringValue drawInRect:titleRect];
}
-(NSRect *) outer:(NSRect)rect fromInner: (NSRect)innerRect {
NSRect * list = (NSRect *) malloc(sizeof(rect) * 4);
NSRect rem;
NSDivideRect(rect, &list[0], &rem, innerRect.origin.x - rect.origin.x, NSRectEdgeMinX);
NSDivideRect(rect, &list[1], &rem, - innerRect.origin.x - innerRect.size.width + rect.origin.x + rect.size.width , NSRectEdgeMaxX);
NSDivideRect(rect, &list[2], &rem, innerRect.origin.y - rect.origin.y, NSRectEdgeMinY);
NSDivideRect(rect, &list[3], &rem, -innerRect.origin.y - innerRect.size.height + rect.origin.y + rect.size.height , NSRectEdgeMaxY);
return list;
}
-(void) updateBackground:(CGRect) cellFrame and:(CGRect) innerCellFrame{
[self.backgroundColor set];
NSRect * list = [self outer:cellFrame fromInner:innerCellFrame];
NSRectFillListUsingOperation(list, 4, NSCompositingOperationSourceOver);
free(list);}
- (void) drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView;
{
[self updateBackground:outCellFrame and:cellFrame];
[super drawInteriorWithFrame:cellFrame inView:controlView];
}
- (void) drawSortIndicatorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView ascending:(BOOL)ascending priority:(NSInteger)priority;
{
[self.backgroundColor set];
NSImage * image = ascending ? [NSImage imageNamed:@"NSDescendingSortIndicator"]:[NSImage imageNamed:@"NSAscendingSortIndicator"] ;
// use your image here. If you need to change color, try to make a colored templated image here.
CGRect frame = [self sortIndicatorRectForBounds:cellFrame];
[self.backgroundColor set];
CGRect res = NSMakeRect(frame.origin.x, cellFrame.origin.y, cellFrame.size.width - frame.origin.x, cellFrame.size.height);
NSRectFillUsingOperation(res , NSCompositingOperationSourceOver);
[NSColor.blueColor setFill];
[NSColor.blueColor setStroke];
[image drawInRect: [self sortIndicatorRectForBounds:frame]];
}
像下面这样创建 NSTableHeaderCell 并使用 header 单元格的帧大小和来源:
#import <Cocoa/Cocoa.h>
@interface CustomTableHeaderCell : NSTableHeaderCell {
NSMutableDictionary *attrs;
}
#import "CustomTableHeaderCell.h"
@implementation CustomTableHeaderCell
- (id)initTextCell:(NSString *)text
{
if (self = [super initTextCell:text]) {
if (text == nil || [text isEqualToString:@""]) {
[self setTitle:@"One"];
}
/* old_
self.textColor = [NSColor blackColor];
*/
// new_
self.textColor = [NSColor whiteColor];
attrs = [[NSMutableDictionary dictionaryWithDictionary:
[[self attributedStringValue]
attributesAtIndex:0
effectiveRange:NULL]]
mutableCopy];
// self.font = [NSFont fontWithName:appleeH8GhKr0 size:12];
return self;
}
return nil;
}
- (void)drawWithFrame:(NSRect)cellFrame
highlighted:(BOOL)isHighlighted
inView:(NSView *)view
{
CGRect fillRect, borderRect;
CGRectDivide(NSRectToCGRect(cellFrame), &borderRect, &fillRect, 1.0, CGRectMaxYEdge);
// sets the origin and frame for header's title
// fillRect.size.height = 25;
if (fillRect.origin.x == 0)
{
// fillRect.origin.y += 5;
// for adding left margin to first column title try to add spaces in text of header title
// try to play with the numbers of fillRect rect
}
else if (fillRect.origin.x == 239)
{
// fillRect.size.width -= 80;
}
// setting the background color of tableview's header view
NSGradient *gradient = [[NSGradient alloc]
initWithStartingColor:[NSColor greenColor]
endingColor:[NSColor greenColor]];
[gradient drawInRect:NSRectFromCGRect(fillRect) angle:90.0];
[self drawInteriorWithFrame:NSRectFromCGRect(CGRectInset(fillRect, 0.0, 1.0)) inView:view];
}
- (NSRect)adjustedFrameToVerticallyCenterText:(NSRect)frame
{
// super would normally draw text at the top of the cell
NSInteger offsetY = floor((NSHeight(frame) -
([[self font] ascender] - [[self font] descender])) / 2);
return NSInsetRect(frame, 20, offsetY);
}
-(void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView{
[super drawInteriorWithFrame:[self adjustedFrameToVerticallyCenterText:cellFrame]
inView:controlView];
}
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)view
{
[self drawWithFrame:cellFrame highlighted:NO inView:view];
}
- (void)highlight:(BOOL)isHighlighted
withFrame:(NSRect)cellFrame
inView:(NSView *)view
{
[self drawWithFrame:cellFrame highlighted:isHighlighted inView:view];
}
为如下列设置 header 单元格:
NSArray *columns = nil;
if(self.tblVc)
columns = [self.tblVc tableColumns];
NSEnumerator *cols = [columns objectEnumerator];
NSTableColumn *col = nil;
CustomTableHeaderCell *headerCell;
while (col = [cols nextObject]) {
NSString *key = [[col headerCell] stringValue];
headerCell = [[CustomTableHeaderCell alloc]
initTextCell:key];
NSRect rectHeader =self.tblVc.headerView.frame;
rectHeader.size.height = 25;
self.tblVc.headerView.frame=rectHeader;
[col setHeaderCell:headerCell];
}
并在 table 视图的 header 顶部使用披露按钮,如下所示:
它会显示这样的结果:
感谢大家的帮助。我最终得到了这个简单的解决方案,它也解决了排序指标。我放弃了自定义 NSHeaderCell
并在单个自定义 NSTableHeaderRowView
方法中解决了所有问题:
- (void)drawRect:(NSRect)dirtyRect
{
[NSGraphicsContext saveGraphicsState];
// Row: Fill the background
//
[NSColor.whiteColor setFill];
const CGRect headerRect = CGRectMake(0.0, 1.0, self.bounds.size.width, self.bounds.size.height-1.0);
[[NSBezierPath bezierPathWithRect:headerRect] fill];
// Columns
//
for (NSUInteger i = 0; i < self.tableView.numberOfColumns; ++i) {
NSRect rect = [self headerRectOfColumn:i];
// separator on left
//
if (i != 0) {
[[NSColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.35] setFill];
[[NSBezierPath bezierPathWithRect:NSMakeRect(rect.origin.x, rect.origin.y+4.0, 1.0, rect.size.height-8.0)] fill];
}
NSTableColumn *tableColumn = self.tableView.tableColumns[i];
NSSortDescriptor *col_sd = tableColumn.sortDescriptorPrototype;
NSTableHeaderCell *tableHeaderCell = tableColumn.headerCell;
// text
//
NSString *columnText = tableHeaderCell.stringValue;
[columnText drawInRect:NSInsetRect(rect, 5.0, 4.0) withAttributes:nil];
// sort indicator
//
for (NSInteger priority = 0; priority < self.tableView.sortDescriptors.count; ++priority) {
NSSortDescriptor *sortDesciptor = self.tableView.sortDescriptors[priority];
// <C> there is no way to get column from sortDesciptor so I use this trick comparing sortDesciptor with current column.sortDescriptorPrototype;
// <C> not sure why sel_isEqual() dosn't work so I compare its string representation;
if ([NSStringFromSelector(sortDesciptor.selector) isEqualToString:NSStringFromSelector(col_sd.selector)] == YES && [sortDesciptor.key isEqualToString:col_sd.key] == YES) {
SDEBUG(LEVEL_DEBUG, @"sort-hdr", @"MATCH: sel=%@", NSStringFromSelector(sortDesciptor.selector));
// <C> default implementation draws indicator ONLY for priority 0; Otherwise the indicator would have to graphically show the prio;
// <C> it is a support for multi-column sorting;
// <REF> shall you need it:
[tableHeaderCell drawSortIndicatorWithFrame:rect inView:self ascending:sortDesciptor.ascending priority:priority];
}
}
}
[NSGraphicsContext restoreGraphicsState];
}
我已经尝试了所有找到的建议解决方案,但最终以最接近的解决方案结束:
目标是自定义颜色:
- 完整 header 背景(例如绿色)
- 文本(例如白色)
- 排序控件颜色(例如白色)
目前我只能正确设置内部背景色和文本颜色,同时将 header 边框和排序控件保留为默认的白色。
我使用自定义的方法NCTableHeaderCell
。
// <C> changing the bgColor doesn't work this way
[self.tv.headerView setWantsLayer:YES];
self.tv.headerView.layer.backgroundColor = NSColor.redColor.CGColor;
for (NSTableColumn *tc in self.tv.tableColumns) {
// <C> this only helps to change the header text color
tc.headerCell = [[NCTableHeaderCell_customizable alloc]initTextCell:@"Hdr"];
// <C> this changes the bgColor of the area of the headerCell label text (the interior) but it leaves border and sort controls in white color;
tc.headerCell.drawsBackground = YES;
tc.headerCell.backgroundColor = NSColor.greenColor;
// <C> changing the textColor doesn't work this way
// <SOLUTION> use NCTableHeaderCell_customizable as done above;
tc.headerCell.textColor = NSColor.redColor;
}
我的习惯 class 看起来像这样:
@implementation NCTableHeaderCell_customizable
// <C> this works as expected
- (NSColor *) textColor
{
return NSColor.whiteColor;
}
// <C> this only sets the interior bgColor leaving the borders in standard color
//
//- (NSColor *) backgroundColor
//{
// return NSColor.redColor;
//}
- (void) drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView;
{
// <C> this only sets the interior bgColor leaving the borders in standard color
//
//self.backgroundColor = NSColor.orangeColor;
[super drawWithFrame:cellFrame inView:controlView];
// <C> this draws the red bg as expected but doesn't show the interior;
//
// [NSColor.redColor set];
// NSRectFillUsingOperation(cellFrame, NSCompositingOperationSourceOver);
// <C> this draws the red bg as expected but
// 1) doesn't layout the interior well (I could fix this);
// 2) doesn't show the sort controls (it's over-drawn with the code bellow);
//
// [NSColor.redColor setFill];
// NSRectFill(cellFrame);
// CGRect titleRect = [self titleRectForBounds:cellFrame];
// [self.attributedStringValue drawInRect:titleRect];
}
- (void) drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView;
{
[super drawInteriorWithFrame:cellFrame inView:controlView];
}
- (void) drawFocusRingMaskWithFrame:(NSRect)cellFrame inView:(NSView *)controlView;
{
[super drawFocusRingMaskWithFrame:cellFrame inView:controlView];
}
- (void) drawSortIndicatorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView ascending:(BOOL)ascending priority:(NSInteger)priority;
{
[super drawSortIndicatorWithFrame:cellFrame inView:controlView ascending:ascending priority:priority];
//NSTableHeaderView *v = (NSTableHeaderView *)controlView;
}
我非常接近解决方案,但我不知道如何正确绘制自定义 header 单元格来存档目标。
我没有找到其他方法,只需要画出一切。希望对你有帮助。
CGRect outCellFrame;
- (void) drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView;
{
// <C> this only sets the interior bgColor leaving the borders in standard color
outCellFrame = cellFrame;
[super drawWithFrame:cellFrame inView:controlView];
// <C> this draws the red bg as expected but doesn't show the interior;
//
// <C> this draws the red bg as expected but
// 1) doesn't layout the interior well (I could fix this);
// 2) doesn't show the sort controls (it's over-drawn with the code bellow);
//
// [NSColor.redColor setFill];
// NSRectFill(cellFrame);
// CGRect titleRect = [self titleRectForBounds:cellFrame];
// [self.attributedStringValue drawInRect:titleRect];
}
-(NSRect *) outer:(NSRect)rect fromInner: (NSRect)innerRect {
NSRect * list = (NSRect *) malloc(sizeof(rect) * 4);
NSRect rem;
NSDivideRect(rect, &list[0], &rem, innerRect.origin.x - rect.origin.x, NSRectEdgeMinX);
NSDivideRect(rect, &list[1], &rem, - innerRect.origin.x - innerRect.size.width + rect.origin.x + rect.size.width , NSRectEdgeMaxX);
NSDivideRect(rect, &list[2], &rem, innerRect.origin.y - rect.origin.y, NSRectEdgeMinY);
NSDivideRect(rect, &list[3], &rem, -innerRect.origin.y - innerRect.size.height + rect.origin.y + rect.size.height , NSRectEdgeMaxY);
return list;
}
-(void) updateBackground:(CGRect) cellFrame and:(CGRect) innerCellFrame{
[self.backgroundColor set];
NSRect * list = [self outer:cellFrame fromInner:innerCellFrame];
NSRectFillListUsingOperation(list, 4, NSCompositingOperationSourceOver);
free(list);}
- (void) drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView;
{
[self updateBackground:outCellFrame and:cellFrame];
[super drawInteriorWithFrame:cellFrame inView:controlView];
}
- (void) drawSortIndicatorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView ascending:(BOOL)ascending priority:(NSInteger)priority;
{
[self.backgroundColor set];
NSImage * image = ascending ? [NSImage imageNamed:@"NSDescendingSortIndicator"]:[NSImage imageNamed:@"NSAscendingSortIndicator"] ;
// use your image here. If you need to change color, try to make a colored templated image here.
CGRect frame = [self sortIndicatorRectForBounds:cellFrame];
[self.backgroundColor set];
CGRect res = NSMakeRect(frame.origin.x, cellFrame.origin.y, cellFrame.size.width - frame.origin.x, cellFrame.size.height);
NSRectFillUsingOperation(res , NSCompositingOperationSourceOver);
[NSColor.blueColor setFill];
[NSColor.blueColor setStroke];
[image drawInRect: [self sortIndicatorRectForBounds:frame]];
}
像下面这样创建 NSTableHeaderCell 并使用 header 单元格的帧大小和来源:
#import <Cocoa/Cocoa.h>
@interface CustomTableHeaderCell : NSTableHeaderCell {
NSMutableDictionary *attrs;
}
#import "CustomTableHeaderCell.h"
@implementation CustomTableHeaderCell
- (id)initTextCell:(NSString *)text
{
if (self = [super initTextCell:text]) {
if (text == nil || [text isEqualToString:@""]) {
[self setTitle:@"One"];
}
/* old_
self.textColor = [NSColor blackColor];
*/
// new_
self.textColor = [NSColor whiteColor];
attrs = [[NSMutableDictionary dictionaryWithDictionary:
[[self attributedStringValue]
attributesAtIndex:0
effectiveRange:NULL]]
mutableCopy];
// self.font = [NSFont fontWithName:appleeH8GhKr0 size:12];
return self;
}
return nil;
}
- (void)drawWithFrame:(NSRect)cellFrame
highlighted:(BOOL)isHighlighted
inView:(NSView *)view
{
CGRect fillRect, borderRect;
CGRectDivide(NSRectToCGRect(cellFrame), &borderRect, &fillRect, 1.0, CGRectMaxYEdge);
// sets the origin and frame for header's title
// fillRect.size.height = 25;
if (fillRect.origin.x == 0)
{
// fillRect.origin.y += 5;
// for adding left margin to first column title try to add spaces in text of header title
// try to play with the numbers of fillRect rect
}
else if (fillRect.origin.x == 239)
{
// fillRect.size.width -= 80;
}
// setting the background color of tableview's header view
NSGradient *gradient = [[NSGradient alloc]
initWithStartingColor:[NSColor greenColor]
endingColor:[NSColor greenColor]];
[gradient drawInRect:NSRectFromCGRect(fillRect) angle:90.0];
[self drawInteriorWithFrame:NSRectFromCGRect(CGRectInset(fillRect, 0.0, 1.0)) inView:view];
}
- (NSRect)adjustedFrameToVerticallyCenterText:(NSRect)frame
{
// super would normally draw text at the top of the cell
NSInteger offsetY = floor((NSHeight(frame) -
([[self font] ascender] - [[self font] descender])) / 2);
return NSInsetRect(frame, 20, offsetY);
}
-(void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView{
[super drawInteriorWithFrame:[self adjustedFrameToVerticallyCenterText:cellFrame]
inView:controlView];
}
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)view
{
[self drawWithFrame:cellFrame highlighted:NO inView:view];
}
- (void)highlight:(BOOL)isHighlighted
withFrame:(NSRect)cellFrame
inView:(NSView *)view
{
[self drawWithFrame:cellFrame highlighted:isHighlighted inView:view];
}
为如下列设置 header 单元格:
NSArray *columns = nil;
if(self.tblVc)
columns = [self.tblVc tableColumns];
NSEnumerator *cols = [columns objectEnumerator];
NSTableColumn *col = nil;
CustomTableHeaderCell *headerCell;
while (col = [cols nextObject]) {
NSString *key = [[col headerCell] stringValue];
headerCell = [[CustomTableHeaderCell alloc]
initTextCell:key];
NSRect rectHeader =self.tblVc.headerView.frame;
rectHeader.size.height = 25;
self.tblVc.headerView.frame=rectHeader;
[col setHeaderCell:headerCell];
}
并在 table 视图的 header 顶部使用披露按钮,如下所示:
它会显示这样的结果:
感谢大家的帮助。我最终得到了这个简单的解决方案,它也解决了排序指标。我放弃了自定义 NSHeaderCell
并在单个自定义 NSTableHeaderRowView
方法中解决了所有问题:
- (void)drawRect:(NSRect)dirtyRect
{
[NSGraphicsContext saveGraphicsState];
// Row: Fill the background
//
[NSColor.whiteColor setFill];
const CGRect headerRect = CGRectMake(0.0, 1.0, self.bounds.size.width, self.bounds.size.height-1.0);
[[NSBezierPath bezierPathWithRect:headerRect] fill];
// Columns
//
for (NSUInteger i = 0; i < self.tableView.numberOfColumns; ++i) {
NSRect rect = [self headerRectOfColumn:i];
// separator on left
//
if (i != 0) {
[[NSColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.35] setFill];
[[NSBezierPath bezierPathWithRect:NSMakeRect(rect.origin.x, rect.origin.y+4.0, 1.0, rect.size.height-8.0)] fill];
}
NSTableColumn *tableColumn = self.tableView.tableColumns[i];
NSSortDescriptor *col_sd = tableColumn.sortDescriptorPrototype;
NSTableHeaderCell *tableHeaderCell = tableColumn.headerCell;
// text
//
NSString *columnText = tableHeaderCell.stringValue;
[columnText drawInRect:NSInsetRect(rect, 5.0, 4.0) withAttributes:nil];
// sort indicator
//
for (NSInteger priority = 0; priority < self.tableView.sortDescriptors.count; ++priority) {
NSSortDescriptor *sortDesciptor = self.tableView.sortDescriptors[priority];
// <C> there is no way to get column from sortDesciptor so I use this trick comparing sortDesciptor with current column.sortDescriptorPrototype;
// <C> not sure why sel_isEqual() dosn't work so I compare its string representation;
if ([NSStringFromSelector(sortDesciptor.selector) isEqualToString:NSStringFromSelector(col_sd.selector)] == YES && [sortDesciptor.key isEqualToString:col_sd.key] == YES) {
SDEBUG(LEVEL_DEBUG, @"sort-hdr", @"MATCH: sel=%@", NSStringFromSelector(sortDesciptor.selector));
// <C> default implementation draws indicator ONLY for priority 0; Otherwise the indicator would have to graphically show the prio;
// <C> it is a support for multi-column sorting;
// <REF> shall you need it:
[tableHeaderCell drawSortIndicatorWithFrame:rect inView:self ascending:sortDesciptor.ascending priority:priority];
}
}
}
[NSGraphicsContext restoreGraphicsState];
}