NSTableColumn大小适合内容

时间:2011-01-12 21:16:13

标签: objective-c cocoa macos user-interface nstableview

我正在开发和使用Mac OS X 10.6(Snow Leopard)。当我在我的两个NSTableView列标题之间双击时,左侧的列会自动调整大小,就像您期望的那样。

我想在上下文菜单中提供此功能,但似乎没有可公开访问的功能来执行此操作。我用谷歌搜索过,看了NSTableView,NSTableHeaderView和NSTableColumn的文档,但一无所获。我发现很难相信当他们明显编写代码时,他们不会暴露出如此有用的东西。

我看到了-[NSTableColumn sizeToFit]方法,但只考虑了标头的大小。我也愿意将双击事件发送到NSTableHeaderView,但也无法弄清楚如何做到这一点。

更新 - 我意识到提到我有一个NSArrayController(子类)向我的表提供数据很重要,所以我没有NSTableViewDataSource我可以调用{ {1}}。这就是问题的症结所在:每个列都绑定到数组控制器的一个键路径,这就是它获取数据的方式,所以它不能遍历其内容。

7 个答案:

答案 0 :(得分:10)

您可以通过从列中获取单元格来获取每个单元格的长度。

NSCell myCell = [column dataCellForRow:i];

然后获得单元格的宽度。

width = [myCell cellSize].width;

之后,您可以将单元格的宽度设置为列的宽度。

[column setMinWidth:width];
[column setWidth:width];

答案 1 :(得分:6)

NSTableColumn上的以下类别将列重新调整为双击分隔符所产生的相同宽度。

@implementation NSTableColumn (resize)

- (void) resizeToFitContents
{
    NSTableView * tableView = self.tableView;
    NSRect rect = NSMakeRect(0,0, INFINITY, tableView.rowHeight);
    NSInteger columnIndex = [tableView.tableColumns indexOfObject:self];
    CGFloat maxSize = 0;
    for (NSInteger i = 0; i < tableView.numberOfRows; i++) {
        NSCell *cell = [tableView preparedCellAtColumn:columnIndex row:i];
        NSSize size = [cell cellSizeForBounds:rect];
        maxSize = MAX(maxSize, size.width);
    }
    self.width = maxSize;
}

@end

答案 2 :(得分:5)

对于基于视图的表视图(OS X 10.7和更高版本),您必须使用NSTableView方法viewAtColumn:row:makeIfNecessary:,然后获取大小。

如果您的单元格视图是NSTextField,则以下代码有效。对于其他视图,您必须找出获得最小尺寸的方法。

[tableView.tableColumns enumerateObjectsUsingBlock:
    ^(id obj, NSUInteger idx, BOOL *stop) {
        NSTableColumn* column = (NSTableColumn*) obj;
        CGFloat width = 0;
        for (int row = 0; row < tableView.numberOfRows; row++) {
            NSView* view = [tableView viewAtColumn: idx
                row: row
                makeIfNecessary: YES];
            NSSize size = [[view cell] cellSize];
            width = MAX(width, MIN(column.maxWidth, size.width));
        }
        column.width = width;
    }];
}

答案 3 :(得分:5)

我发现以下内容适合我(用Swift编写):

func resizeColumn(columnName: String) {
    var longest:CGFloat = 0 //longest cell width
    let columnNumber = tableView.columnWithIdentifier(columnName)
    let column = tableView.tableColumns[columnNumber] as! NSTableColumn
    for (var row = 0; row < tableView.numberOfRows; row++) {
        var view = tableView.viewAtColumn(columnNumber, row: row, makeIfNecessary: true) as! NSTableCellView
        var width = view.textField!.attributedStringValue.size.width

        if (longest < width) {
            longest = width
        }
    }

    column.width = longest
    viewTable.reloadData()
}

将列的标识符作为参数,它会在该列中找到内容最长的单元格,并调整列的大小以适合该内容。

答案 4 :(得分:1)

我需要做这件事,事实证明(至少对我而言)要更多参与;这不是完美但非常接近;-) 附:编辑处理基于图像的单元格

#define GBLBIN(x)   [[NSUserDefaults standardUserDefaults] boolForKey:(x)]

- (IBAction)autosizeColumns:(id)sender
{
    NSMutableArray * widths = [NSMutableArray array];
    NSIndexSet * rows = [table selectedRowIndexes];
    NSArray * columns = [table tableColumns];
    BOOL deslectAll = NO;
    NSNumber * col = nil;
    int i, j;

    //  If no row's selected, then select all, the default
    if (![rows count])
    {
        [table selectAll:sender];
        rows = [table selectedRowIndexes];
        deslectAll = YES;
    }

    //  Use the selected rows from which we'll use the columns' formatted data widths
    for (i=[rows lastIndex]; i != NSNotFound; i=[rows indexLessThanIndex:i])
    {
        for (j=0; j<[columns count]; j++)
        {
            NSTableColumn * column = [columns objectAtIndex:j];
            id item = [[table dataSource]
                tableView:table objectValueForTableColumn:column row:i];
            NSCell * cell = [column dataCell];
            float width, minWidth=10;

            //  Depending on the dataCell type (image or text) get the 'width' needed
            if ([cell isKindOfClass:[NSTextFieldCell class]])
            {
                NSFont * font = ([cell font]
                    ? [cell font] : [NSFont controlContentFontOfSize:(-1)]);
                NSDictionary * attrs = 
                    [NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName];
                NSFormatter * formatter = [cell formatter];
                NSString * string = item;

                //  We want a string, as IB would have formatted it...
                if (![item isKindOfClass:[NSString class]])
                    if (formatter)
                        string = [formatter stringForObjectValue:item];
                    else
                        string = [NSString stringWithFormat:@"%@", item];

                width = [string sizeWithAttributes:attrs].width + minWidth;
            }
            else
            {
                //  We have NSButtonCell, NSImageCell, etc; get object's width
                width = MAX(minWidth,[[cell image] size].width);
            }

            //  First time seeing this column, go with minimum width
            if (j == [widths count])
            {
                [widths addObject:[NSNumber numberWithFloat:minWidth]];
            }
            col = [widths objectAtIndex:j];
            width = MAX([col floatValue], width);
            [widths replaceObjectAtIndex:j withObject:[NSNumber numberWithInt:width]];
        }
    }

    //  Now go resize each column to its minimum data content width
    for (j=0; j<[widths count]; j++)
    {
        NSTableColumn * column = [columns objectAtIndex:j];
        float width = [[widths objectAtIndex:j] floatValue];

        if (GBLBIN(@"debug"))
        {
            NSLog(@"col:%d '%@' %.f", j, [column identifier], width);
        }
        [column setWidth:width];
    }

    //  Undo select all if we did it
    if (deslectAll)
    {
        [table deselectAll:sender];
    }
}

答案 5 :(得分:1)

以下是一些扩展方法,用于根据Monomac开发人员在C#中的内容调整列的大小。

注意:此列仅按其内容调整列,其他注意事项未考虑(例如,空列的宽度为10px)。

感谢obsh-c片段的slashlos。

public static void SizeColumnsByContent(this NSTableView tableView, int minWidth = 10) {
        Contract.Requires(tableView != null);
        Contract.Requires(tableView.DataSource != null);
        var columns = tableView.TableColumns();
        var widths = new List<int>();
        for (int row=0; row< tableView.RowCount; row++) {
            for (int col=0; col < tableView.ColumnCount; col++) {
                // Determine what the fit width is for this cell
                var column = columns[col];
                var objectValue = tableView.DataSource.GetObjectValue(tableView, column, row);
                var width = column.DataCell.DetermineFitWidth(objectValue, minWidth);

                // Record the max width encountered for current coolumn
                if (row == 0) {
                    widths.Add(width);
                } else {
                    widths[col] = Math.Max(widths[col], width);
                }
            }

            //  Now go resize each column to its minimum data content width
            for (int col=0; col < tableView.ColumnCount; col++) {
                columns[col].Width = widths[col];
            }
        }
    }

    public static int DetermineFitWidth(this NSCell cell, NSObject objectValueAtCell, int minWidth = 10) {
        int width;
        if (cell is NSTextFieldCell) {
            var font = cell.Font ?? NSFont.ControlContentFontOfSize(-1);
            var attrs = NSDictionary.FromObjectAndKey(font, NSAttributedString.FontAttributeName);

            // Determine the text on the cell
            NSString cellText;
            if (objectValueAtCell is NSString) {
                cellText = (NSString)objectValueAtCell;
            } else if (cell.Formatter != null) {
                cellText = cell.Formatter.StringFor(objectValueAtCell).ToNSString();
            } else {
                cellText = objectValueAtCell.Description.ToNSString();
            }

            width = (int)cellText.StringSize(attrs).Width + minWidth;

        } else if (cell.Image != null) {
            // if cell has an image, use that images width
            width = (int)Math.Max(minWidth, (int)cell.Image.Size.Width);
        }  else {
            // cell is something else, just use its width
            width = (int)Math.Max(minWidth, (int)cell.CellSize.Width);
        }

        return width;

    }

答案 6 :(得分:1)

我在实现其他一些解决方案时遇到了麻烦...但是当我使用单元格的字符串值创建单元格时,我设法让列自动调整宽度。到目前为止似乎工作得很好!

- (id)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
    NSString *identifier = [tableColumn identifier];
    NSUInteger col = [keys indexOfObject:identifier];

    // Get an existing cell with the MyView identifier if it exists
    NSTextField *cell = [tableView makeViewWithIdentifier:identifier owner:self];

    // Get cell data
    NSDictionary *dict = [self.tableContents objectAtIndex:row];
    NSString *stringValue = [dict objectForKey:[keys objectAtIndex:col]]; 

    // There is no existing cell to reuse so create a new one
    if (cell == nil) {

        // Create the new NSTextField with a frame of the {0,0} with the width of the table.
        // Note that the height of the frame is not really relevant, because the row height will modify the height.
        NSRect rect = NSRectFromCGRect(CGRectMake(0, 0, 64, 24));
        cell = [[NSTextField alloc] initWithFrame:rect];
        [cell setBordered:NO];
        [cell setBackgroundColor:[NSColor clearColor]];
        [cell setAutoresizesSubviews:YES];

        // autosize to fit width - update column min width
        NSRect rectAutoWidth = [stringValue boundingRectWithSize:(CGSize){CGFLOAT_MAX, 24} options:NSStringDrawingUsesFontLeading|NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:cell.font}]; 
        if ( tableColumn.minWidth < rectAutoWidth.size.width )
        {
            [tableColumn setMinWidth:rectAutoWidth.size.width+20]; // add padding
        }

        // The identifier of the NSTextField instance is set to MyView. This allows the cell to be reused.
        cell.identifier = identifier;
    }

    // result is now guaranteed to be valid, either as a reused cell or as a new cell, so set the stringValue of the cell to the nameArray value at row

    // set value
    cell.stringValue = stringValue;

    // Return the result
    return cell;
}