16

I'm looking for a way to get right-clicked row index from NSTableView but I can't find any delegate methods or class attributes for it. Any suggestion is appreciated.

Abcd Efg
  • 2,058
  • 22
  • 40

5 Answers5

23

Use the NSTableView method - (NSInteger)clickedRow to get the index of the last clicked row. The returned NSInteger will be the index of the right clicked row.

You do not need to subclass NSTableView for this solution. clickedRow is also available on NSOutlineView.

Community
  • 1
  • 1
Graham Miln
  • 2,614
  • 3
  • 31
  • 31
  • 2
    I don't think this should work when right clicking a row because the return value of clickedRow is only meaningful in the target's implementation of the action or double-action method. – Ira Cooke Dec 05 '13 at 19:45
  • 1
    This works, so long as it is called from within the NSTableView's target action or doubleAction method. Do you have another situation where you want the clicked row? – Graham Miln Dec 06 '13 at 07:43
  • 4
    I think the original poster asked for a solution when right clicking a row. From what I could tell it's not possible to trigger either of those methods with a right-click. – Ira Cooke Dec 06 '13 at 09:10
  • 4
    According to the 10.5 release notes (https://developer.apple.com/library/content/releasenotes/AppKit/RN-AppKitOlderNotes/index.html#X10_5Notes): “The key thing to note is that clickedRow and clickedColumn will now both be valid when a contextual menu is popped up. … The clickedRow and clickedColumn properties will still be valid when the action is sent from the NSMenuItem.”. I found that this does work *if* the table’s `menu` outlet is set and if you override `-menuForEvent:` you have to call `super`. – Michael Tsai Jul 04 '17 at 15:20
  • Thanks, this is the easiest answer to implement. – Borzh Oct 19 '17 at 18:56
  • 2
    This answer works correctly. Set the NSTableView's menu property to an NSMenu item you own. Set that NSMenu's delegate. In menuNeedsUpdate(_: NSMenu) you can call clickedRow() on the NSTableView. This DOES work for right clicks at the point this menu delegate is called. – Giles Feb 12 '18 at 15:03
  • @MichaelTsai that comment is the key for me. I had to use the `menu` outlet. Just creating and returning a `NSMenu()` in code was not updating my `clickedRow` value – Kirkova May 21 '19 at 21:31
11

While I haven't done this, I am pretty sure you can by overriding NSView's - (NSMenu*)menuForEvent:(NSEvent*)theEvent. The example in this link does a point conversion to determine the index.

-(NSMenu*)menuForEvent:(NSEvent*)theEvent
{
    NSPoint mousePoint = [self convertPoint:[theEvent locationInWindow] fromView:nil];
   int row = [self rowAtPoint:mousePoint];
   // Produce the menu here or perform an action like selection of the row.
}
Peter DeWeese
  • 17,664
  • 8
  • 75
  • 98
  • 3
    I was hoping to find a way without subclassing NSTableView. – Abcd Efg Sep 19 '12 at 12:44
  • 1
    Graham's answer below works without subclassing. Set the NSTableView's menu property to an NSMenu item you own. Set that NSMenu's delegate. In menuNeedsUpdate(_: NSMenu) you can call clickedRow() on the NSTableView. This DOES work for right clicks at the point this menu delegate is called. – Giles Feb 12 '18 at 15:02
6

Updated Answer

If you want to get clicked row index on menu opening, the answer is NSTableView.clickedRow. Anyway this property is available only in specific moments, and usually just -1.

When is this index to be available? That's in NSMenuDelegate.menuWillOpen method. So you conform the delegate and implement the method on your class, and access the clickedRow property. It's done.

final class FileNavigatorViewController: NSViewController, NSMenuDelegate {
    let ov = NSOutlineView() // Assumed you setup this properly.
    let ctxm = NSMenu()
    override func viewDidLoad() {
        super.viewDidLoad()
        ov.menu = ctxm
        ctxm.delegate = self
    }
    func menuWillOpen(_ menu: NSMenu) {
        print(outlineView.clickedRow)
    }
}

Clicked row index is available until you click an item in the menu. So this also works.

final class FileNavigatorViewController: NSViewController {
    let ov = NSOutlineView() // Assumed you setup this properly.
    let ctxm = NSMenu()
    let item1 = NSMenuItem()
    override func viewDidLoad() {
        super.viewDidLoad()
        ov.menu = ctxm
        ov.addItem(item1)
        ov.target = self
        ov.action = #selector(onClickItem1(_:))
    }
    @objc
    func onClickItem1(_: NSObject?) {
        print(outlineView.clickedRow)
    }
}

I tested this on macOS Sierra (10.12.5).


Old Answer

Starting from OS X 10.11, Apple finally added a method to access clickedRow easily. Just subclass NSTableView and override this method and you'll get the clickedRow as far as I experienced.

func willOpenMenu(menu: NSMenu, withEvent event: NSEvent)

This needs subclassing, but anyway, the cleanest and simplest way to access clickedRow.

Also, there's a pairing method.

func didCloseMenu(menu: NSMenu, withEvent event: NSEvent?)
eonil
  • 75,400
  • 74
  • 294
  • 482
  • Would be awesome if you could provide a working example. I spent the last two days getting a context menu in a NSTableCellView work but failed – ixany Jan 30 '17 at 10:36
3

Just select row on right-click by implementing menuForEvent: in NSTableView subclass:

@implementation MyTableView

- (NSMenu *)menuForEvent:(NSEvent *)theEvent
{
    int row = [self rowAtPoint:[self convertPoint:theEvent.locationInWindow fromView:nil]];
    if (row == -1) 
        return nil;
    if (row != self.selectedRow)
        [self selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
    return self.menu;
}

@end
k06a
  • 14,368
  • 10
  • 57
  • 97
  • 2
    This does not match the behavior in Finder and Mail, which let you use the contextual menu to act on a row without affecting the selection. – Michael Tsai Jul 03 '17 at 23:22
0

if you dont need to open NSMenu but need to know "right click action with row number", i think most simple way is below. (Swift4 Code) Don't need any other connected Outer NSMenu class.

class SomeViewController: NSViewController, NSTableViewDataSource, NSTableViewDelegate {
  @IBOutlet weak var tableView: NSTableView!
  ...

  override func viewDidLoad() {
    ...
    tableView.action = #selector(some method()) // left single click action
    tableView.doubleAction = #selector(someMethod()) // left double click action
  }

  // right click action
  override func rightMouseDown(with theEvent: NSEvent) {
    let point = tableView.convert(theEvent.locationInWindow, from: nil)
    let row = tableView.row(at: point)
    print("right click")
    print(row)
  }
kj13
  • 1,485
  • 1
  • 10
  • 12