14

I'm using NSOutlineView without NSTreeController and have implemented my own datasource. What is the best way to select an item? NSOutlineView support already expandItem: and collapseItem:. And I'm missing a handy method like `selectItem:. How can I do it programatically ?

Thank you.

cocoafan
  • 4,884
  • 4
  • 37
  • 45

6 Answers6

25

Remember to look in superclasses when you can't find something. In this case, one of the methods you need comes from NSTableView, which is NSOutlineView's immediate superclass.

The solution is to get the row index for the item using rowForItem:, and if it isn't -1 (item not visible/not found), create an index set with it with [NSIndexSet indexSetWithIndex:] and pass that index set to the selectRowIndexes:byExtendingSelection: method.

zneak
  • 134,922
  • 42
  • 253
  • 328
Peter Hosey
  • 95,783
  • 15
  • 211
  • 370
  • Hi Peter, thank you for the answer. I already know the method selectRowIndexes:byExtendingSelection:. The problem is that NSOutlineView is working with NSIndexPath and not NSIndexSet. – cocoafan Jul 08 '09 at 13:02
  • I don't see a single instance of NSIndexPath in the NSOutlineView documentation. Perhaps you're thinking of NSTreeController, which you're not using. Moreover, an outline view *is* a table view, which means all the table view functionality should work just fine in the outline view. – Peter Hosey Jul 08 '09 at 13:34
  • Yes I see. To bad that there is no built-in solution which not depends on my datasource. I have to write extra code in my datasource, right? – cocoafan Jul 08 '09 at 19:54
  • No. Those three steps are all you have to do to select a specific item. (Or items, for that matter.) – Peter Hosey Jul 08 '09 at 21:27
  • I think I need at least the code in my datasource to expand a node if rowForItem: fails. – cocoafan Jul 09 '09 at 10:03
  • 1
    cocoafan: You mentioned `expandItem:` in your question. That's a method of the NSOutlineView. I'm not sure whether you can pass it a deeply-nested item, but even if you can't, you can use NSOutlineView's `parentForItem:` to build a stack of the desired item's ancestors, then walk that stack and expand each item in turn. – Peter Hosey Jul 09 '09 at 12:44
22

Here is how I finally ended up. Suggestions and corrections are always welcome.

@implementation NSOutlineView (Additions)

- (void)expandParentsOfItem:(id)item {
    while (item != nil) {
        id parent = [self parentForItem: item];
        if (![self isExpandable: parent])
            break;
        if (![self isItemExpanded: parent])
            [self expandItem: parent];
        item = parent;
    }
}

- (void)selectItem:(id)item {
    NSInteger itemIndex = [self rowForItem:item];
    if (itemIndex < 0) {
        [self expandParentsOfItem: item];
        itemIndex = [self rowForItem:item];
        if (itemIndex < 0)
            return;
    }

    [self selectRowIndexes: [NSIndexSet indexSetWithIndex: itemIndex] byExtendingSelection: NO];
}
@end
cocoafan
  • 4,884
  • 4
  • 37
  • 45
2

No, there isn't a selectItem: method, but there is an rowForItem: method. If you combine that with Peter's advice about using selectRowIndexes:byExtendingSelection: above, you should have all the information you need.

If you really wanted to have a method to select an item, which I would recommend calling setSelectedItem: for consistency's sake, you could write something like this in a category on NSOutlineView

- (void)setSelectedItem:(id)item {
    NSInteger itemIndex = [self rowForItem:item];
    if (itemIndex < 0) {
        // You need to decide what happens if the item doesn't exist
        return;
    }

    [self selectRowIndexes:[NSIndexSet indexSetWithIndex:itemIndex] byExtendingSelection:NO];
}

I have no idea if this code actually works; I just dashed it off to illustrate the concept.

Alex
  • 26,829
  • 3
  • 55
  • 74
1

(This is still coming up as a top result on Google)

Swift 4.2:

You need to make sure the parent is expanded first:

if let parent = outlineView.parent(myItem){
        mapOutlineView.expandItem(parent)
}


let rowIndex = outlineView.row(forItem: myItem)
        guard rowIndex != -1 else {return}
        outlineView.selectRowIndexes(IndexSet(integer: rowIndex), byExtendingSelection: false)
green_knight
  • 1,319
  • 14
  • 26
0

This is an old question, but the situation is still the same. As there was a request for a swift version, here's my take. I didn't find the accepted answer to work for me as I think you should interact directly with your data source class rather than extending the NSOutlineView. Annoyingly, the outline won't find rows unless they are expanded which is why it's simpler to use your dataSource. In my case I found that you have to expand the parent's of your items in reverse order, so that you start at the top level and work your way towards the actual item you're trying to expand. I feel like that should be built in, but unless I missed something - it's not.

In this example FileItem is my data source collection item class. It contains a property "parent" which needs to be valid if it is part of the hierarchy displayed.

func selectItem(_ item: FileItem, byExtendingSelection: Bool = false) {
    guard let outlineView = outlineView else { return }

    var itemIndex: Int = outlineView.row(forItem: item)

    if itemIndex < 0 {
        var parent: FileItem? = item

        var parents = [FileItem?]()
        while parent != nil {
            parents.append(parent)
            parent = parent?.parent
        }

        let reversedTree = parents.compactMap({$0}).reversed()

        for level in reversedTree {
            outlineView.expandItem(level, expandChildren: false)
        }

        itemIndex = outlineView.row(forItem: item)
        if itemIndex < 0 {
            print("Didn't find", item)
            return
        }
    }

    print("Expanding row", itemIndex)

    outlineView.selectRowIndexes(IndexSet(integer: itemIndex), byExtendingSelection: byExtendingSelection)
    outlineView.scrollRowToVisible(itemIndex)
}
Ryan Francesconi
  • 988
  • 7
  • 17
0

Here is a code snippet I used to programmatically select an item in a PXSourceList.

sourceList is a regular PXSouceList object and I wanted to select the second item in the first group of the outline.

    NSInteger itemRow = [sourceList rowForItem:[[(SourceListItem *)[sourceListItems objectAtIndex:0] children] objectAtIndex:1]];
    [sourceList selectRowIndexes:[NSIndexSet indexSetWithIndex:itemRow] byExtendingSelection:YES];

If you don't know yet, PXSourceList is an excellent replacement for an NSOutlineView if you re looking for itunes/mail style outlines. Pick it up here: PxSourceList

isaac
  • 631
  • 1
  • 5
  • 10