Show contextual menu on ctrl-click/right-click on header of NSTableView
Asked Answered
S

4

16

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.

Stempson answered 3/10, 2010 at 12:34 Comment(0)
D
19

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

[[myTableView headerView] setMenu:aMenu];
Dependent answered 3/10, 2010 at 14:51 Comment(2)
If you select the header in Interface Builder, you can also drag a connection to a menu in your NIB/XIB.Pongee
how would you find out which column has been right clicked?Flack
G
40

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.

Galliwasp answered 19/4, 2011 at 22:36 Comment(5)
only 18 'likes' in 2 years.Foumart
yeah not that many. But I did get a badge for this one I think. Getting more votes than the accepted answer.Galliwasp
I don't understand why this got so many upvotes. The question clearly asks about a context menu for the table HEADER. This just shows how to get a context menu on the rows.Dravidian
@JakobEgger: This also works for the Table Header View in the IB. I think you'd then need [yourTableView clickedColumn]Prevot
@Julian clickedColumn is -1 when you click on the header. You need to subclass NSTableHeaderView. This answer is wrong, and your comment is wrong.Dravidian
D
19

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

[[myTableView headerView] setMenu:aMenu];
Dependent answered 3/10, 2010 at 14:51 Comment(2)
If you select the header in Interface Builder, you can also drag a connection to a menu in your NIB/XIB.Pongee
how would you find out which column has been right clicked?Flack
D
11

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
Dravidian answered 27/6, 2014 at 7:57 Comment(2)
This is the best answer to the question - as it a) answers the question and b) deals with the problem if which column was clicked.Oshaughnessy
This is the complete answer +1Dispensation
M
1

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
    }
}
Mf answered 4/11, 2017 at 6:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.