27

I'm using NSOutlineView for a project, and can't seem to figure out two things:

  • How to remove the disclosure triangle for tree nodes. Apps like iTunes seem to be able to do this:

alt text

Is there some sort of NSOutlineView Delegate method that is used for this? Or does it require a subclass?

  • How to disable indenting for items. I've tried using setIndentationPerLevel: and setting it to 0, as well as changing the column indent to 0 in Interface Builder, but it does not seem to have any effect.
indragie
  • 18,002
  • 16
  • 95
  • 164

8 Answers8

45

You've run into the right person here. I've had to grapple with this just a week ago.

Removing the disclosure triangle: implement the frameOfOutlineCellAtRow: method in your NSOutlineView subclass and return NSZeroRect (only if you want to hide that particular row's triangle, of course.)

- (NSRect)frameOfOutlineCellAtRow:(NSInteger)row {
    return NSZeroRect;
}

Disable indenting: the outline view's standard layout reserves space at the far left to draw the triangles in, in case the item is expandable. But you can override that for individual items by specifying a different drawing frame. You also do that in your subclass, by responding to this message:

- (NSRect)frameOfCellAtColumn:(NSInteger)column row:(NSInteger)row {
    NSRect superFrame = [super frameOfCellAtColumn:column row:row];


    if ((column == 0) /* && isGroupRow */) {
        return NSMakeRect(0, superFrame.origin.y, [self bounds].size.width, superFrame.size.height);
    }
    return superFrame;
}
Marcel Hansemann
  • 1,019
  • 8
  • 11
  • 2
    You can affect the indentation without subclassing the outlineView by setting indentationMarkerFollowsCell to false. – pickwick May 17 '17 at 21:25
38

For future reference, the cleanest and simplest way to hide the disclosure triangle in expandable NSOutlineView items is by implementing the outlineView:shouldShowOutlineCellForItem: method of the NSOutlineViewDelegate protocol in your delegate:

-(BOOL)outlineView:(NSOutlineView *)outlineView shouldShowOutlineCellForItem:(id)item
{
    // replace this with your logic to determine whether the
    // disclosure triangle should be hidden for a particular item
    return [item hidesDisclosureTriangle];
}
Tamas Czinege
  • 118,853
  • 40
  • 150
  • 176
  • 4
    When I try to do this, I am no longer able to (programmatically) collapse these items. Is this a mistake on my side? – Max Seelemann Aug 01 '12 at 11:26
  • 4
    This delegate method doesn't seem to be called for view-based outlineviews. In that case I found that setting indentation to 0 in IB had the desired effect – Ira Cooke Aug 23 '14 at 21:47
10

I had to combine the two approaches above because outlineView:shouldShowOutlineCellForItem: alone does not remove the space reserved for the disclosure triangles (it does remove the triangles themselves).

Delegate:

- (BOOL)outlineView:(NSOutlineView *)outlineView shouldShowOutlineCellForItem:(id)item {
    return NO;
}

Subclass of NSOutlineView:

@implementation ExpandedOutlineView

#define kOutlineCellWidth 11
#define kOutlineMinLeftMargin 6

- (NSRect)frameOfCellAtColumn:(NSInteger)column row:(NSInteger)row {
    NSRect superFrame = [super frameOfCellAtColumn:column row:row];
    if (column == 0) {
        // expand by kOutlineCellWidth to the left to cancel the indent
        CGFloat adjustment = kOutlineCellWidth;

        // ...but be extra defensive because we have no clue what is going on here
        if (superFrame.origin.x - adjustment < kOutlineMinLeftMargin) {
            NSLog(@"%@ adjustment amount is incorrect: adjustment = %f, superFrame = %@, kOutlineMinLeftMargin = %f", NSStringFromClass([self class]), (float)adjustment, NSStringFromRect(superFrame), (float)kOutlineMinLeftMargin);
            adjustment = MAX(0, superFrame.origin.x - kOutlineMinLeftMargin);
        }

        return NSMakeRect(superFrame.origin.x - adjustment, superFrame.origin.y, superFrame.size.width + adjustment, superFrame.size.height);
    }
    return superFrame;
}

@end

Result:

Screenshot of NSOutlineView with no top-level indentation

jvarela
  • 3,744
  • 1
  • 22
  • 43
Andrey Tarantsov
  • 8,965
  • 7
  • 54
  • 58
  • Note that column 0 may not be the outline column. Imagine left-hand checkboxes for inclusion (best place for them so they're all aligned the same). It's better to ask the outline view for its -outlineTableColumn rather than assuming it's column 0. – Joshua Nozzi May 20 '13 at 17:21
  • In that case , use unique identifier for the column. – Neelam Verma Sep 16 '15 at 13:14
  • I'm agree for frameOfCellAtColumn:row: But returning NO to outlineView:shouldShowOutlineCellForItem: will prevent to collapse the item thereafter – Chrstpsln Jul 19 '17 at 12:59
  • 1
    This works nicely as long as the `NSOutlineView` doesn't have `usesAutomaticRowHeights` turned on. – Padraig Aug 23 '19 at 00:19
2

Use PXSourceList. It's the style you're looking for with a very nice api.

Dave DeLong
  • 242,470
  • 58
  • 448
  • 498
  • Thanks Dave, it does indeed have a very nice API. I'm using that in combination with Marcel's tip to remove the indent to achieve what I wanted :-) – indragie Nov 23 '10 at 05:44
  • If you use PXSourceList you can achieve that style by returning `YES` to the `PXSourceListDelegate` message `-sourceList:isGroupAlwaysExpanded:` for the group you want displayed like that. – Alex Rozanski Nov 23 '10 at 07:16
  • Very nice reference. PXSourceList looks like a huge time-saver. Thanks, Dave and especially Alex. :-) – Joshua Nozzi Nov 23 '10 at 14:36
0

The correct way to specify the intention per level is by overwriting indentationPerLevel of NSOutlineView.

class NoIndentOutlineView: NSOutlineView {
    override var indentationPerLevel: CGFloat {
        get {
            return 0;
        }
        set {
            // Do nothing
        }
    }
}

This will make sure children have the same indention as the parents.

paxos
  • 877
  • 5
  • 11
0

This is in regards to hiding the disclosure button. If you're looking to globally hide it on expandable items, one can do this:

extension NSOutlineView {
    override open func makeView(withIdentifier identifier: NSUserInterfaceItemIdentifier, owner: Any?) -> NSView? {
        if identifier == NSOutlineView.disclosureButtonIdentifier {
            return nil
        } else {
            return super.makeView(withIdentifier: identifier, owner: owner)
        }
    }
}

Else you can subclass NSOutlineView and override the makeView function.

I know the q is for obj-c. I just had to work through this and my project is swift.

Mobile Ben
  • 7,121
  • 1
  • 27
  • 43
0

Above are great answers for what the OP asked about - removing the disclosure triangle and the space it occupies. To achieve 'list without expansion and all items on the same level', which is the effect they're after, all you need to do is put a number of top level items into the array used as data source and implement

func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
    return false
}
green_knight
  • 1,319
  • 14
  • 26
-3

I tried this in Swift. It just works. I don't know whether it is a proper way. There should be equivalent in Obj-C:

extension NSTableRowView {
    override open func layout() {
        super.layout()
        if let cell = subviews.first as? NSTableCellView, cell.objectValue is <YourHeaderCellClass> {
            subviews.first?.setFrameOrigin(NSZeroPoint)
        }
    }
}

Be sure you return YourHeaderCellClass item in datasource method.

func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any
snakehnb
  • 63
  • 7
  • 1
    Sorry to downvote your answer, but this is a very dangerous way of doing it. You are making assumptions about the index of subviews in their arrays, which is an implementation detail beyond your control. As in all things Cocoa, it is best to not fight the framework but first search for a delegate method you can override (see other answers above). – Martin Winter Mar 09 '18 at 15:37