objective-ccocoacontextmenunstableview

context menu based on NSTableView cell


I would like to place a context menu onto a NSTableView. this part is done. What I would like to do is to show different menu entries based on the content of the right clicked cell, and NOT show the context menu for specific columns.

this is:

column 0, and 1 no context menu

all other cells should have the context menu like this:

first entry: "delete " samerow.column1.value
second entry: "save " samecolumn.headertext

-EDIT-

the one on the right is how the context menu is supposed to look like for any given cell.

enter image description here


Solution

  • Theres a delegate for that! - No need to subclass

    In IB if you drag an NSTableView onto your window/view you'll notice that theres a menu outlet for the table.

    So a very easy way to implement the contextual menu is to connect that outlet to a stub menu and connect the delegate outlet of the menu to an object which implements the NSMenuDelegate protocol method - (void)menuNeedsUpdate:(NSMenu *)menu

    interface builder screen shot

    Normally the delegate of the menu is the same object which provides the datasource/delegates to the table but it might also be the view controller which owns the table too.

    Have a look at the docs for more info on this

    Theres a bundle of clever stuff you can do in the protocol but a very simple implementation might be like below

    #pragma mark tableview menu delegates
    
    - (void)menuNeedsUpdate:(NSMenu *)menu
    {
    NSInteger clickedrow = [mytable clickedRow];
    NSInteger clickedcol = [mytable clickedColumn];
    
    if (clickedrow > -1 && clickedcol > -1) {
    
    
    
       //construct a menu based on column and row   
       NSMenu *newmenu = [self constructMenuForRow:clickedrow andColumn:clickedcol];
    
       //strip all the existing stuff       
       [menu removeAllItems];
    
       //then repopulate with the menu that you just created        
       NSArray *itemarr = [NSArray arrayWithArray:[newmenu itemArray]];
       for(NSMenuItem *item in itemarr)
       {
          [newmenu removeItem:[item retain]];
          [menu addItem:item];
          [item release];
       }        
    }
    
    }
    

    And then a method to construct the menu.

    -(NSMenu *)constructMenuForRow:(int)row andColumn:(int)col
    {
    
        NSMenu *contextMenu = [[[NSMenu alloc] initWithTitle:@"Context"] autorelease];
    
    NSString *title1 = [NSString stringWithFormat:@"Delete %@",[self titleForRow:row]]; 
    
    NSMenuItem *item1 = [[[NSMenuItem alloc] initWithTitle:title1 action:@selector(deleteObject:) keyEquivalent:@""] autorelease];
        [contextMenu addItem:item1];
        //
    NSString *title2 = [NSString stringWithFormat:@"Save %@",[self titleForColumn:col]];    
    
    NSMenuItem *item2 = [[[NSMenuItem alloc] initWithTitle:title1 action:@selector(saveObject:) keyEquivalent:@""] autorelease];
        [contextMenu addItem:item2];
    
    return contextMenu;
    }
    

    How you choose to implement titleForRow: and titleForColumn: is up to you.

    Note that NSMenuItem provides the property representedObject to allow you to bind an arbitrary object to the menu item and hence send information into your method (e.g deleteObject:)

    EDIT

    Watch out - implementing - (void)menuNeedsUpdate:(NSMenu *)menu in your NSDocument subclass will stop the Autosave/Versions menu that appears in the title bar appearing in 10.8.

    It still works in in 10.7 so go figure. In any case the menu delegate will need to be something other than your NSDocument subclass.