Question

I'm searching for an elegant way to detect a right-click/ctrl-click on the header of an NSTableView.

When the right click occurs, I want to display an contextual menu.

- (NSMenu *)menuForEvent:(NSEvent *)

detects only right clicks in the table - not in the header of the table.

thanks for your help.

Was it helpful?

Solution

Get the NSTableHeaderView from the NSTableView and set it's menu.

[[myTableView headerView] setMenu:aMenu];

OTHER TIPS

Sometimes a picture explains a 1000 words.

  1. You do not need to subclass your table view.
  2. On any tableView you can select the TableView and connect the menu outlet to a menu. enter image description here

  3. Now you can wire the selector of the menu (on the right) to your code .

  4. To figure out what row in the table was clicked use

[yourTableView clickedRow]

Done. Like a boss.

You need to subclass NSTableHeaderView. While it is possible to make a menu show up without subclassing, it is not possible to find out which table column was clicked without subclassing (making the context menu useless).

I wrote my own sublcass of the table header view, and added a delegate. In interface builder, find the NSTableHeaderView, assign it your custom subclass, and connect its new delegate outlet. Additionally, create a menu and assign it to the menu outlet.

Then implement the -validateMenu:forTableColumn: method in the delegate. Enable/disable menu items as apropriate (make sure that the menu doesn't autovalidate in IB). Store the clicked column somewhere in an instance variable, so you know which column to act on when the user selects an action.

PGETableViewTableHeaderView.h

#import <Cocoa/Cocoa.h>
@protocol PGETableViewTableHeaderViewDelegate <NSObject>
-(void)validateMenu:(NSMenu*)menu forTableColumn:(NSTableColumn*)tableColumn;
@end
@interface PGETableViewTableHeaderView : NSTableHeaderView
@property(weak) IBOutlet id<PGETableViewTableHeaderViewDelegate> delegate;
@end

PGETableViewTableHeaderView.m

#import "PGETableViewTableHeaderView.h"
@implementation PGETableViewTableHeaderView
-(NSMenu *)menuForEvent:(NSEvent *)event {
    NSInteger columnForMenu = [self columnAtPoint:[self convertPoint:event.locationInWindow fromView:nil]];
    NSTableColumn *tableColumn = nil;
    if (columnForMenu >= 0) tableColumn = self.tableView.tableColumns[columnForMenu];
    NSMenu *menu = self.menu;
    [self.delegate validateMenu:menu forTableColumn:tableColumn];
    return menu;
}
@end

Thanks Jakob Egger for his precise answer. I come up with Swift version of this approach. I changed the delegate method signature a little bit, to give more flexibility in case of more then one TableView in ViewController.

protocol IMenuTableHeaderViewDelegate: class {
    func menuForTableHeader(inTableView tableView: NSTableView, forTableColumn tableColumn: NSTableColumn) -> NSMenu?
}

class MenuTableHeaderView: NSTableHeaderView {
    weak var menuDelegate: IMenuTableHeaderViewDelegate?

    override func menu(for event: NSEvent) -> NSMenu? {
        guard tableView != nil else {
            return nil
        }
        let columnForMenu =  column(at: convert(event.locationInWindow, from: nil))
        if columnForMenu >= 0, tableView!.tableColumns.count > columnForMenu {
            if let tableColumn = tableView?.tableColumns[columnForMenu] {
                return menuDelegate?.menuForTableHeader(inTableView: tableView!, forTableColumn: tableColumn)
            }
        }
        return self.menu;
    }
}

To use this custom class, find NSTableHeaderView in the interface builder and change the class to MenuTableHeaderView

Window where you have to enter custom class name

Example of this approach usage in a ViewController

class ExampleViewController: NSViewController, IMenuTableHeaderViewDelegate {
    @IBOutlet weak var tableView: NSTableView!
    @IBOutlet var tableHeaderMenu: NSMenu!

    var lastColumnForMenu: HeaderColumnForMenu?

    struct HeaderColumnForMenu {
        let tableView: NSTableView
        let tableColumn: NSTableColumn
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        if let tableHeaderWithMenu = tableView.headerView as? MenuTableHeaderView {
            tableHeaderWithMenu.menuDelegate = self
        }
    }

    func menuForTableHeader(inTableView tableView: NSTableView, forTableColumn tableColumn: NSTableColumn) -> NSMenu? {
        //Save column to wich we are going to show menu
        lastColumnForMenu = HeaderColumnForMenu(tableView: tableView, tableColumn: tableColumn)
        if needShowMenu {
            return tableHeaderMenu
        }
        return nil
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top