如何在 MAC OS X 应用程序中更改 NSTableView header 背景颜色?

How to change NSTableView header background color in MAC OS X app?

我已经尝试了所有找到的建议解决方案,但最终以最接近的解决方案结束:

目标是自定义颜色:

  1. 完整 header 背景(例如绿色)
  2. 文本(例如白色)
  3. 排序控件颜色(例如白色)

目前我只能正确设置内部背景色和文本颜色,同时将 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];
}