简体   繁体   English

NSTableColumn大小适合内容

[英]NSTableColumn size to fit contents

I am developing in and against Mac OS X 10.6 (Snow Leopard). 我正在开发和反对Mac OS X 10.6(Snow Leopard)。 When I double-click between two of my NSTableView's column headers, the column on the left autosizes, like you would expect. 当我在我的两个NSTableView列标题之间双击时,左侧的列会自动调整大小,就像您期望的那样。

I want to provide this in a context menu as well, but it seems there is no publicly accessible function to do this. 我想在上下文菜单中提供这个,但似乎没有可公开访问的功能来执行此操作。 I've Googled, and looked at the documentation for NSTableView, NSTableHeaderView, and NSTableColumn, but found nothing. 我用谷歌搜索过,看了NSTableView,NSTableHeaderView和NSTableColumn的文档,但一无所获。 I find it hard to believe they wouldn't expose something so useful when they obviously have the code written to do it. 我发现很难相信当他们明显编写代码时,他们不会暴露出如此有用的东西。

I saw the -[NSTableColumn sizeToFit] method, but that only takes the header's size into account. 我看到了-[NSTableColumn sizeToFit]方法,但只考虑了标题的大小。 I would also settle for sending the double-click event to the NSTableHeaderView, but couldn't figure out how to do that, either. 我也愿意将双击事件发送到NSTableHeaderView,但也无法弄清楚如何做到这一点。

Update - I realized it's important to mention I have an NSArrayController (subclass) providing the data to my table, so I don't have an NSTableViewDataSource on which I could call -[tableView: objectValueForTableColumn: row:] . 更新 - 我意识到重要的是要提到我有一个NSArrayController(子类)向我的表提供数据,所以我没有可以调用的NSTableViewDataSource -[tableView: objectValueForTableColumn: row:] That is the crux of the problem: each column is bound to a keypath of the array controller and that's how it gets its data, so it can't loop through its contents. 这就是问题的症结所在:每个列都绑定到数组控制器的一个键路径,这就是它获取数据的方式,因此无法遍历其内容。

You can get the length of each cell by getting the cells from the column. 您可以通过从列中获取单元格来获取每个单元格的长度。

NSCell myCell = [column dataCellForRow:i];

Then get the width of the cell. 然后获得单元格的宽度。

width = [myCell cellSize].width;

After that you can set the width of the cell as the width of the column. 之后,您可以将单元格的宽度设置为列的宽度。

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

For view-based table views (OS X 10.7 and later), you have to use the NSTableView method viewAtColumn:row:makeIfNecessary: , and then get the size. 对于基于视图的表视图(OS X 10.7和更高版本),您必须使用NSTableView方法viewAtColumn:row:makeIfNecessary:然后获取大小。

The following code works if your cell view is an NSTextField . 如果您的单元格视图是NSTextField则以下代码有效。 For other views you have to figure out the way to get its minimum size. 对于其他视图,您必须找出获得最小尺寸的方法。

[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;
    }];
}

The following category on NSTableColumn resizes columns to the same width that double-clicking on the separator would produce. 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

I found the following to work for me (written in Swift): 我发现以下内容适合我(用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()
}

Given a column's identifier as an argument, it finds the cell in that column with the longest content, and resizes the column to fit that content. 给定列的标识符作为参数,它会在该列中找到内容最长的单元格,并调整列的大小以适合该内容。

Here are some extension methods to size columns by their content in C# for Monomac developers. 下面是一些扩展方法,用于根据Monomac开发人员在C#中的内容调整列的大小。

NOTE: this sizes columns by their content only, other considerations no considered (empty cols are 10px wide for eg). 注意:此列仅按其内容对列进行大小调整,其他注意事项均未考虑(例如,空列为10px宽)。

Thanks to slashlos for the obj-c snippet. 感谢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;

    }

I had trouble implementing some of the other solutions... But I managed to get columns to automatically resize width when cells are created using the cell's string value. 我在实现其他一些解决方案时遇到了麻烦......但是我设法让列在使用单元格的字符串值创建单元格时自动调整宽度。 Seems to work pretty well so far! 到目前为止似乎工作得很好!

- (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;
}

I needed to do this very thing and it turned out (for me at least) to be a bit more involved; 我需要做到这一点,事实证明(至少对我来说)更多的参与; this isn't perfect but pretty close ;-) ps Edited to handle image based cells 这不是完美但非常接近;-) ps编辑处理基于图像的细胞

#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];
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM