Determining width of a cell in an NSOutlineView

  • I only have a single column in my NSOutlineView.

    The outline view looks like:

    > Collapsible Row 1
        Item 1

    > Collapsible Row 2
        Item 2

    I need the height of a row an of an item displayed when the disclosure
    triangle is clicked to be based on the width of the cell.

    I can get the indentation level by doing:

    NSInteger level    = [outlineView levelForItem:item];

    and the amount that is indented per level by doing:

    CGFloat indentation = [outlineView indentationPerLevel];

    Asking for the width of the column does not return the same width of
    the bounds parameter when drawInteriorWithFrame is called for my cell.

    What looks like could work is to get the frame of the NSOutlineView
    and then subtract the width of the frame by:

        indentation * (level + 1)

    but this doesn't seem like it should be the right answer.

    There doesn't seem to be a method I can call to obtain the bounds that
    will be passed into drawInteriorWithFrame for my cell. Have I missed
    something?

    Any other ideas?

    thank you.
  • On Dec 19, 2008, at 1:35 PM, Eric Gorr wrote:

    > I only have a single column in my NSOutlineView.
    >
    > The outline view looks like:
    >
    >> Collapsible Row 1
    > Item 1
    >
    >> Collapsible Row 2
    > Item 2
    >
    > I need the height of a row an of an item displayed when the
    > disclosure triangle is clicked to be based on the width of the cell.
    >
    > I can get the indentation level by doing:
    >
    > NSInteger level    = [outlineView levelForItem:item];
    >
    > and the amount that is indented per level by doing:
    >
    > CGFloat indentation = [outlineView indentationPerLevel];
    >
    >
    > Asking for the width of the column does not return the same width of
    > the bounds parameter when drawInteriorWithFrame is called for my cell.
    >
    >
    > What looks like could work is to get the frame of the NSOutlineView
    > and then subtract the width of the frame by:
    >
    > indentation * (level + 1)
    >
    > but this doesn't seem like it should be the right answer.
    >
    >
    > There doesn't seem to be a method I can call to obtain the bounds
    > that will be passed into drawInteriorWithFrame for my cell. Have I
    > missed something?
    >

    -frameOfCellAtColumn:row: is exactly what will be passed to your cell.

    corbin
  • On Dec 19, 2008, at 5:20 PM, Corbin Dunn wrote:

    >
    > On Dec 19, 2008, at 1:35 PM, Eric Gorr wrote:
    >
    >> I only have a single column in my NSOutlineView.
    >>
    >> The outline view looks like:
    >>
    >>> Collapsible Row 1
    >> Item 1
    >>
    >>> Collapsible Row 2
    >> Item 2
    >>
    >> I need the height of a row an of an item displayed when the
    >> disclosure triangle is clicked to be based on the width of the cell.
    >>
    >> I can get the indentation level by doing:
    >>
    >> NSInteger level    = [outlineView levelForItem:item];
    >>
    >> and the amount that is indented per level by doing:
    >>
    >> CGFloat indentation = [outlineView indentationPerLevel];
    >>
    >>
    >> Asking for the width of the column does not return the same width
    >> of the bounds parameter when drawInteriorWithFrame is called for my
    >> cell.
    >>
    >>
    >> What looks like could work is to get the frame of the NSOutlineView
    >> and then subtract the width of the frame by:
    >>
    >> indentation * (level + 1)
    >>
    >> but this doesn't seem like it should be the right answer.
    >>
    >>
    >> There doesn't seem to be a method I can call to obtain the bounds
    >> that will be passed into drawInteriorWithFrame for my cell. Have I
    >> missed something?
    >>
    >
    > -frameOfCellAtColumn:row: is exactly what will be passed to your cell.

    Unfortunately, this does not work as calling this function causes
    heightOfRowByItem to be called. So, an endless loop is entered.

    I just need to know the width so I base the height on the width.
  • On Dec 19, 2008, at 2:30 PM, Eric Gorr wrote:

    >
    > On Dec 19, 2008, at 5:20 PM, Corbin Dunn wrote:
    >
    >>
    >> On Dec 19, 2008, at 1:35 PM, Eric Gorr wrote:
    >>
    >>> I only have a single column in my NSOutlineView.
    >>>
    >>> The outline view looks like:
    >>>
    >>>> Collapsible Row 1
    >>> Item 1
    >>>
    >>>> Collapsible Row 2
    >>> Item 2
    >>>
    >>> I need the height of a row an of an item displayed when the
    >>> disclosure triangle is clicked to be based on the width of the cell.
    >>>
    >>> I can get the indentation level by doing:
    >>>
    >>> NSInteger level    = [outlineView levelForItem:item];
    >>>
    >>> and the amount that is indented per level by doing:
    >>>
    >>> CGFloat indentation = [outlineView indentationPerLevel];
    >>>
    >>>
    >>> Asking for the width of the column does not return the same width
    >>> of the bounds parameter when drawInteriorWithFrame is called for
    >>> my cell.
    >>>
    >>>
    >>> What looks like could work is to get the frame of the
    >>> NSOutlineView and then subtract the width of the frame by:
    >>>
    >>> indentation * (level + 1)
    >>>
    >>> but this doesn't seem like it should be the right answer.
    >>>
    >>>
    >>> There doesn't seem to be a method I can call to obtain the bounds
    >>> that will be passed into drawInteriorWithFrame for my cell. Have I
    >>> missed something?
    >>>
    >>
    >> -frameOfCellAtColumn:row: is exactly what will be passed to your
    >> cell.
    >
    > Unfortunately, this does not work as calling this function causes
    > heightOfRowByItem to be called. So, an endless loop is entered.

    Yes, of course...since it needs to know the height, but it does answer
    your question of how to obtain the bounds that are passed to
    drawInteriorWithFrame:.

    >
    > I just need to know the width so I base the height on the width.
    >

    For that, you want to use -cellSizeForBounds: -- pass in a large
    height, but a constrained width. Use a width that is equal to the
    [tableColumn width] minus indentation * (level + 1) ---- although, the
    actual value that outlineview uses for indentation is dependent on
    some internal logic (ie: if you are using "group rows", or the "source
    list highlighting style"). But, this should give you a value that is
    fairly close to what you want.

    corbin
  • On Dec 19, 2008, at 6:03 PM, Corbin Dunn wrote:

    >
    > For that, you want to use -cellSizeForBounds: -- pass in a large
    > height, but a constrained width. Use a width that is equal to the
    > [tableColumn width] minus indentation * (level + 1) ---- although,
    > the actual value that outlineview uses for indentation is dependent
    > on some internal logic (ie: if you are using "group rows", or the
    > "source list highlighting style"). But, this should give you a value
    > that is fairly close to what you want.

    Vacation got in the way of replying sooner...

    Thanks for the information.

    While I believe this will work in my specific situation, I have filed
    a bug (rdar://6472757) requesting that a method be added which can
    simply return the width, taking into account things like "group rows"
    and "source list highlighting style".
  • On Dec 19, 2008, at 6:03 PM, Corbin Dunn wrote:

    >
    > On Dec 19, 2008, at 2:30 PM, Eric Gorr wrote:
    >
    >>
    >>
    >> I just need to know the width so I base the height on the width.
    >>
    >
    > For that, you want to use -cellSizeForBounds: -- pass in a large
    > height, but a constrained width. Use a width that is equal to the
    > [tableColumn width] minus indentation * (level + 1) ---- although,
    > the actual value that outlineview uses for indentation is dependent
    > on some internal logic (ie: if you are using "group rows", or the
    > "source list highlighting style"). But, this should give you a value
    > that is fairly close to what you want.

    I guess I spoke to soon. This doesn't actually seem to work.

    I have the following code in my -outlineView:heightOfRowByItem: method:

    NSTableColumn  *column        = [outlineView outlineTableColumn];
    NSUInteger      nItems          = [item count];
    CGFloat        indentation    = [outlineView indentationPerLevel];
    NSInteger      level          = [outlineView levelForItem:item];
    NSRect          bounds          = NSMakeRect(0, 0, [column width],
    10000);
    NSCell          *tableCell      = [column dataCell];
    CGFloat        totalOffset    = ( indentation * ( level + 1 ) );
    NSSize          cellSize        = [tableCell cellSizeForBounds:bounds];
    NSUInteger      nItemsPerRow    = ( cellSize.width - totalOffset ) / 80;

    The width of the column is 711. However, cellSize.width is just 42.xx
    when it should be around 700  as well.

    Any thoughts?
  • On Jan 5, 2009, at 8:56 AM, Eric Gorr wrote:

    >
    > On Dec 19, 2008, at 6:03 PM, Corbin Dunn wrote:
    >
    >>
    >> On Dec 19, 2008, at 2:30 PM, Eric Gorr wrote:
    >>
    >>>
    >>>
    >>> I just need to know the width so I base the height on the width.
    >>>
    >>
    >> For that, you want to use -cellSizeForBounds: -- pass in a large
    >> height, but a constrained width. Use a width that is equal to the
    >> [tableColumn width] minus indentation * (level + 1) ---- although,
    >> the actual value that outlineview uses for indentation is dependent
    >> on some internal logic (ie: if you are using "group rows", or the
    >> "source list highlighting style"). But, this should give you a
    >> value that is fairly close to what you want.
    >
    > I guess I spoke to soon. This doesn't actually seem to work.
    >
    > I have the following code in my -outlineView:heightOfRowByItem:
    > method:
    >
    > NSTableColumn  *column        = [outlineView outlineTableColumn];
    > NSUInteger      nItems          = [item count];
    > CGFloat        indentation    = [outlineView indentationPerLevel];
    > NSInteger      level          = [outlineView levelForItem:item];
    > NSRect          bounds          = NSMakeRect(0, 0, [column width],
    > 10000);
    > NSCell          *tableCell      = [column dataCell];
    > CGFloat        totalOffset    = ( indentation * ( level + 1 ) );
    > NSSize          cellSize        = [tableCell
    > cellSizeForBounds:bounds];
    > NSUInteger      nItemsPerRow    = ( cellSize.width - totalOffset ) /
    > 80;
    >

    Thanks for logging the bug (re: other response).

    Your code has some problems; it is asking for the cellSize before
    accounting for the indentation. You probably want:
    ...
    CGFloat        totalOffset    = ( indentation * ( level + 1 ) );
    bounds.size.width -= totalOffset; // corbin
    NSSize          cellSize        = [tableCell cellSizeForBounds:bounds];

    At this point, cellSize.height should give you a good height to return
    from the -heightForRow: method.

    Now here:

    > NSUInteger      nItemsPerRow    = ( cellSize.width - totalOffset ) /
    > 80;

    I'm confused what this is trying to do; maybe your ultimate goal is
    different than what I think it is. I figure you are trying to find the
    appropriate row height for a given cell so it won't get clipped
    (something like what Console on Leopard does).

    corbin

    > The width of the column is 711. However, cellSize.width is just
    > 42.xx when it should be around 700  as well.
    >
    > Any thoughts?
  • On Jan 5, 2009, at 12:37 PM, Corbin Dunn wrote:

    >
    > On Jan 5, 2009, at 8:56 AM, Eric Gorr wrote:
    >
    >>
    >> On Dec 19, 2008, at 6:03 PM, Corbin Dunn wrote:
    >>
    >>>
    >>> On Dec 19, 2008, at 2:30 PM, Eric Gorr wrote:
    >>>
    >>>>
    >>>>
    >>>> I just need to know the width so I base the height on the width.
    >>>>
    >>>
    >>> For that, you want to use -cellSizeForBounds: -- pass in a large
    >>> height, but a constrained width. Use a width that is equal to the
    >>> [tableColumn width] minus indentation * (level + 1) ---- although,
    >>> the actual value that outlineview uses for indentation is
    >>> dependent on some internal logic (ie: if you are using "group
    >>> rows", or the "source list highlighting style"). But, this should
    >>> give you a value that is fairly close to what you want.
    >>
    >> I guess I spoke to soon. This doesn't actually seem to work.
    >>
    >> I have the following code in my -outlineView:heightOfRowByItem:
    >> method:
    >>
    >> NSTableColumn  *column        = [outlineView outlineTableColumn];
    >> NSUInteger      nItems          = [item count];
    >> CGFloat        indentation    = [outlineView indentationPerLevel];
    >> NSInteger      level          = [outlineView levelForItem:item];
    >> NSRect          bounds          = NSMakeRect(0, 0, [column width],
    >> 10000);
    >> NSCell          *tableCell      = [column dataCell];
    >> CGFloat        totalOffset    = ( indentation * ( level + 1 ) );
    >> NSSize          cellSize        = [tableCell
    >> cellSizeForBounds:bounds];
    >> NSUInteger      nItemsPerRow    = ( cellSize.width -
    >> totalOffset ) / 80;
    >>
    >
    > Thanks for logging the bug (re: other response).
    >
    > Your code has some problems; it is asking for the cellSize before
    > accounting for the indentation. You probably want:
    > ...
    > CGFloat        totalOffset    = ( indentation * ( level + 1 ) );
    > bounds.size.width -= totalOffset; // corbin
    > NSSize          cellSize        = [tableCell
    > cellSizeForBounds:bounds];
    >
    > At this point, cellSize.height should give you a good height to
    > return from the -heightForRow: method.
    >
    > Now here:
    >
    >> NSUInteger      nItemsPerRow    = ( cellSize.width -
    >> totalOffset ) / 80;
    >
    >
    > I'm confused what this is trying to do; maybe your ultimate goal is
    > different than what I think it is. I figure you are trying to find
    > the appropriate row height for a given cell so it won't get clipped
    > (something like what Console on Leopard does).

    Ah, I think there has been a miscommunication.

    I ultimately need to be able to compute the height of the cell based
    on the width. Based on your reply, it looks like I can determine the
    width of the cell merely by doing:

    NSTableColumn  *column        = [outlineView outlineTableColumn];
    CGFloat        totalCellWidth = [column width] - ( indentation *
    ( level + 1 ) );

    The height of the cell should be based on that value so nItems with
    nItemsPerRow can fit entirely inside of the cell. Each item I am
    drawing will be 80x80 pixels.

    It would seem that what I need to do does not require the use of -
    cellSizeForBounds:.
  • On Jan 5, 2009, at 12:59 PM, Eric Gorr wrote:

    >
    > On Jan 5, 2009, at 12:37 PM, Corbin Dunn wrote:
    >
    >>
    >> On Jan 5, 2009, at 8:56 AM, Eric Gorr wrote:
    >>
    >>>
    >>> On Dec 19, 2008, at 6:03 PM, Corbin Dunn wrote:
    >>>
    >>>>
    >>>> On Dec 19, 2008, at 2:30 PM, Eric Gorr wrote:
    >>>>
    >>>>>
    >>>>>
    >>>>> I just need to know the width so I base the height on the width.
    >>>>>
    >>>>
    >>>> For that, you want to use -cellSizeForBounds: -- pass in a large
    >>>> height, but a constrained width. Use a width that is equal to the
    >>>> [tableColumn width] minus indentation * (level + 1) ----
    >>>> although, the actual value that outlineview uses for indentation
    >>>> is dependent on some internal logic (ie: if you are using "group
    >>>> rows", or the "source list highlighting style"). But, this should
    >>>> give you a value that is fairly close to what you want.
    >>>
    >>> I guess I spoke to soon. This doesn't actually seem to work.
    >>>
    >>> I have the following code in my -outlineView:heightOfRowByItem:
    >>> method:
    >>>
    >>> NSTableColumn  *column        = [outlineView outlineTableColumn];
    >>> NSUInteger      nItems          = [item count];
    >>> CGFloat        indentation    = [outlineView indentationPerLevel];
    >>> NSInteger      level          = [outlineView levelForItem:item];
    >>> NSRect          bounds          = NSMakeRect(0, 0, [column width],
    >>> 10000);
    >>> NSCell          *tableCell      = [column dataCell];
    >>> CGFloat        totalOffset    = ( indentation * ( level + 1 ) );
    >>> NSSize          cellSize        = [tableCell
    >>> cellSizeForBounds:bounds];
    >>> NSUInteger      nItemsPerRow    = ( cellSize.width -
    >>> totalOffset ) / 80;
    >>>
    >>
    >> Thanks for logging the bug (re: other response).
    >>
    >> Your code has some problems; it is asking for the cellSize before
    >> accounting for the indentation. You probably want:
    >> ...
    >> CGFloat        totalOffset    = ( indentation * ( level + 1 ) );
    >> bounds.size.width -= totalOffset; // corbin
    >> NSSize          cellSize        = [tableCell
    >> cellSizeForBounds:bounds];
    >>
    >> At this point, cellSize.height should give you a good height to
    >> return from the -heightForRow: method.
    >>
    >> Now here:
    >>
    >>> NSUInteger      nItemsPerRow    = ( cellSize.width -
    >>> totalOffset ) / 80;
    >>
    >>
    >> I'm confused what this is trying to do; maybe your ultimate goal is
    >> different than what I think it is. I figure you are trying to find
    >> the appropriate row height for a given cell so it won't get clipped
    >> (something like what Console on Leopard does).
    >
    > Ah, I think there has been a miscommunication.
    >
    > I ultimately need to be able to compute the height of the cell based
    > on the width. Based on your reply, it looks like I can determine the
    > width of the cell merely by doing:
    >
    > NSTableColumn  *column        = [outlineView outlineTableColumn];
    > CGFloat        totalCellWidth = [column width] - ( indentation *
    > ( level + 1 ) );
    >
    > The height of the cell should be based on that value so nItems with
    > nItemsPerRow can fit entirely inside of the cell. Each item I am
    > drawing will be 80x80 pixels.
    >
    > It would seem that what I need to do does not require the use of -
    > cellSizeForBounds:.

    The latest problem I am having with this is that an NSOutlineView will
    not fully draw itself properly if the height of a row changes at the
    window it is on is being resized.

    I've got a couple of pictures of this which can be viewed at:

    http://ericgorr.net/cocoadev/8h6pcc/largetosmall.tiff
    http://ericgorr.net/cocoadev/8h6pcc/smalltolarge.tiff

    The only redraw problem that makes a little sense to me is when making
    the window bigger, the only part that is drawn problem is the part
    that was added at the control got bigger.

    Of course, once I let go of the mouse button and stop resizing the
    window, it draws itself correctly.

    What I have tried doing is responding to -windowDidResize: and running
    the code:

    [resourcesThumbnails reloadData];
    [resourcesThumbnails setNeedsDisplay:YES];
    [resourcesThumbnails display];
    [[notification object] flushWindow];

    resourcesThumbnails is the NSOutlineView, but this will not cause the
    entire control to be drawn properly.

    Is there anything else I can try?
  • >
    > The latest problem I am having with this is that an NSOutlineView
    > will not fully draw itself properly if the height of a row changes
    > at the window it is on is being resized.

    Override -drawRect: in a subclass of NSOutlineView and just call
    [super drawRect:] -- live resize caching is conflicting with what you
    want. Ideally, overriding -preservesContentDuringLiveResize should
    work, but unfortunately it does not work in Tiger/Leopard.

    corbin