在绑定到NSArrayController的NSTableView中显示行索引

时间:2010-01-02 12:06:39

标签: cocoa nstableview nsarraycontroller

我有一个绑定到NSArrayController的NSTableView。我想有一个表列显示表行的索引。当你自己实现NSTableDataSource时,这很容易做到,但我无法用绑定表视图来解决它。 我想我正在寻找类似于@count键路径的东西,它给出了arrangeObjects的数量(即@index),但这显然是缺失的。

两个澄清:

  1. 每行中显示的索引是该行的索引,与数据在模型或数组控制器中实际排列的方式完全无关。例如,如果整个数据是10000个项目,则索引应该从1到10000,如果用户输入搜索词并且表格仅显示4个项目,那么数字应该从1到4,即使显示的项目实际上来自原始数组。
  2. 我需要这个,因为客户要求我这样做:-)。客户端需要一种能够估计某行之前或之后的行数的方法。例如,您可能想知道添加了多少行。

3 个答案:

答案 0 :(得分:4)

据我了解,您可以选择不绑定该表列,而是使用数据源。我记得NSTableView支持这种“双模式”操作,但找不到任何文档来确认它。

答案 1 :(得分:2)

我最近使用NSRuler子类实现了这一点,该子类在TableView中的每一行旁边绘制行号。我将代码基于类似于here的类似代码。

您可以使用以下方法将其添加到您的桌面视图中:

NSScrollView *scrollView = [tableView enclosingScrollView];
TableLineNumberRulerView *lineNumberView = [[TableLineNumberRulerView alloc] initWithTableView:tableView
                                                                           usingArrayController:arrayController];

[scrollView setVerticalRulerView:lineNumberView];
[scrollView setHasVerticalRuler:YES];
[scrollView setRulersVisible:YES];

这是接口文件:

//
//  TableLineNumberRulerView
//  Line View Test
//
//  Created by Ben Golding, Object Craft Pty Ltd on 7 May 2014.
//  Based on code by Paul Kim on 9/28/08.


#import <Cocoa/Cocoa.h>

@interface TableLineNumberRulerView : NSRulerView<NSCoding>

@property (strong) NSArrayController *arrayController;

@property (strong) NSFont       *font;
@property (strong) NSColor  *textColor;
@property (strong) NSColor  *alternateTextColor;
@property (strong) NSColor  *backgroundColor;
@property (strong) NSDictionary *textAttributes;
@property (assign) NSUInteger   rowCount;

- (id)initWithTableView:(NSTableView *)tableView  usingArrayController:(NSArrayController *)arrayController;

@end

以下是实施:

//
//  TableLineNumberRulerView.m
//  Line View Test
//
//  Created by Ben Golding, Object Craft Pty Ltd on 7 May 2014.
//  Based on code by Paul Kim on 9/28/08.

#import "TableLineNumberRulerView.h"

#define DEFAULT_THICKNESS   22.0
#define RULER_MARGIN        5.0

@implementation TableLineNumberRulerView

@synthesize font;
@synthesize textColor;
@synthesize alternateTextColor;
@synthesize backgroundColor;
@synthesize textAttributes;
@synthesize rowCount;


- (id)initWithTableView:(NSTableView *)tableView usingArrayController:(NSArrayController *)arrayController
{
    NSScrollView *scrollView = [tableView enclosingScrollView];

    if ((self = [super initWithScrollView:scrollView orientation:NSVerticalRuler]) == nil)
        return nil;

    [self setClientView:tableView];

    self.arrayController = arrayController;
    [arrayController addObserver:self forKeyPath:@"arrangedObjects" options:NSKeyValueObservingOptionNew context:nil];

    self.font = [NSFont labelFontOfSize:[NSFont systemFontSizeForControlSize:NSMiniControlSize]];
    self.textColor = [NSColor colorWithCalibratedWhite:0.42 alpha:1.0];
    self.alternateTextColor = [NSColor whiteColor];
    self.textAttributes = @{
        NSFontAttributeName: [self font],
        NSForegroundColorAttributeName: [self textColor]
    };

    self.rowCount = [[arrayController arrangedObjects] count];

    return self;
}

- (void)awakeFromNib
{
    [self setClientView:[[self scrollView] documentView]];      // this will be an NSTableView instance
}

- (void)finalize
{
    [self.arrayController removeObserver:self forKeyPath:@"arrangedObjects"];
}

#pragma mark -
#pragma mark Key-Value observing of changes to array controller

/*
 * This picks up changes to the arrayController's arrangedObjects using KVO.
 * We check the size of the old and new rowCounts and compare them to see if the number
 * digits has changed, and if so, we adjust the ruler width.
 */

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if ([keyPath isEqualToString:@"arrangedObjects"]) {
        NSUInteger newRowCount = [[self.arrayController arrangedObjects] count];

        if ((int)log10(self.rowCount) != (int)log10(newRowCount))
            [self setRuleThickness:[self requiredThickness]];
        self.rowCount = newRowCount;
        // we need to redisplay because line numbers may change or disappear in view
        [self setNeedsDisplay:YES];
    }
}


- (CGFloat)requiredThickness
{
    NSUInteger      lineCount = [[self.arrayController arrangedObjects] count],
                    digits = (unsigned)log10((lineCount < 1) ? 1: lineCount) + 1;
    NSMutableString *sampleString = [NSMutableString string];
    NSSize          stringSize;

    for (NSUInteger i = 0; i < digits; i++) {
        // Use "8" since it is one of the fatter numbers. Anything but "1"
        // will probably be ok here. I could be pedantic and actually find the fattest
        // number for the current font but nah.
        [sampleString appendString:@"8"];
    }

    stringSize = [sampleString sizeWithAttributes:[self textAttributes]];

    // Round up the value. There is a bug on 10.4 where the display gets all wonky when scrolling if you don't
    // return an integral value here.
    return ceil(MAX(DEFAULT_THICKNESS, stringSize.width + RULER_MARGIN * 2));
}

- (void)drawHashMarksAndLabelsInRect:(NSRect)aRect
{
    NSTableView *tableView = (NSTableView *)[self clientView];
    NSRect bounds = [self bounds];
    NSRect visibleRect = [[tableView enclosingScrollView] documentVisibleRect];
    NSRange visibleRowRange = [tableView rowsInRect:visibleRect];
    CGFloat yinset = NSHeight([[tableView headerView] bounds]);

    if (backgroundColor != nil) {
        [backgroundColor set];
        NSRectFill(bounds);

        [[NSColor colorWithCalibratedWhite:0.58 alpha:1.0] set];
        [NSBezierPath strokeLineFromPoint:NSMakePoint(NSMaxX(bounds) - 0/5, NSMinY(bounds))
                                  toPoint:NSMakePoint(NSMaxX(bounds) - 0.5, NSMaxY(bounds))];
    }

//    NSLog(@"drawHashMarksAndLabelsInRect: bounds %@, ruleThickness %lf", NSStringFromRect(bounds), [self ruleThickness]);

    for (NSUInteger row = visibleRowRange.location; NSLocationInRange(row, visibleRowRange); row++) {
        // Line numbers are internally stored starting at 0
        NSString *labelText = [NSString stringWithFormat:@"%lu", row + 1];
        NSSize stringSize = [labelText sizeWithAttributes:self.textAttributes];
        NSRect rowRect = [tableView rectOfRow:row];
        CGFloat ypos = yinset + NSMinY(rowRect) - NSMinY(visibleRect);

        [labelText drawInRect:NSMakeRect(NSWidth(bounds) - stringSize.width - RULER_MARGIN,
                                         ypos + (NSHeight(rowRect) - stringSize.height) / 2.0,
                                         NSWidth(bounds) - RULER_MARGIN * 2.0, NSHeight(rowRect))
               withAttributes:self.textAttributes];
    }
}

#pragma mark -
#pragma mark NSCoding methods

#define FONT_CODING_KEY         @"font"
#define TEXT_COLOR_CODING_KEY       @"textColor"
#define ALT_TEXT_COLOR_CODING_KEY   @"alternateTextColor"
#define BACKGROUND_COLOR_CODING_KEY @"backgroundColor"

- (id)initWithCoder:(NSCoder *)decoder
{
    if ((self = [super initWithCoder:decoder]) != nil) {
        if ([decoder allowsKeyedCoding]) {
            font = [decoder decodeObjectForKey:FONT_CODING_KEY];
            textColor = [decoder decodeObjectForKey:TEXT_COLOR_CODING_KEY];
            alternateTextColor = [decoder decodeObjectForKey:ALT_TEXT_COLOR_CODING_KEY];
            backgroundColor = [decoder decodeObjectForKey:BACKGROUND_COLOR_CODING_KEY];
        } else {
            font = [decoder decodeObject];
            textColor = [decoder decodeObject];
            alternateTextColor = [decoder decodeObject];
            backgroundColor = [decoder decodeObject];
        }
    }
    return self;
}

- (void)encodeWithCoder:(NSCoder *)encoder
{
    [super encodeWithCoder:encoder];

    if ([encoder allowsKeyedCoding]) {
        [encoder encodeObject:font forKey:FONT_CODING_KEY];
        [encoder encodeObject:textColor forKey:TEXT_COLOR_CODING_KEY];
        [encoder encodeObject:alternateTextColor forKey:ALT_TEXT_COLOR_CODING_KEY];
        [encoder encodeObject:backgroundColor forKey:BACKGROUND_COLOR_CODING_KEY];
    } else {
        [encoder encodeObject:font];
        [encoder encodeObject:textColor];
        [encoder encodeObject:alternateTextColor];
        [encoder encodeObject:backgroundColor];
    }
}

@end

答案 2 :(得分:1)

假设您的目标是复制iTunes的行为,您的列只需要显示1到(可见行数)。它根本不需要与您的模型相关。

所以,给你的控制器一个动态生成的数组属性,并在实际模型对象的数组上使它成为depend

对于此属性,实现array accessor methods,索引的getter只需计算idx + 1box,然后返回该对象。您可能还需要实现整个数组getter以满足KVC。

确保将列设置为不可编辑,并将绑定设置为无条件设置enabled。否则,当用户尝试为行输入新索引时,您将获得异常(因为没有此属性的setter)。

需要注意的一点是:这可能会导致问题,因为NSTableView不希望其列绑定到离散数组;它希望它的所有列都绑定到单个数组中的模型对象的不同属性。除了绑定列的内容绑定外,您可能还需要将表视图本身的content绑定到模型对象数组,并I've had trouble with that before