基于NSTableViewCell的上下文菜单

时间:2013-03-03 12:28:47

标签: objective-c cocoa contextmenu nstableview nstableviewcell

我想将一个上下文菜单放到NSTableView上。这部分完成了。我要做的就是根据右键单元格的内容显示不同的菜单条目,不要显示特定列的上下文菜单。

这是:

第0列,1没有上下文菜单

所有其他单元格应该具有如下的上下文菜单:

第一个条目:“删除”samerow.column1.value
第二个条目:“保存”samecolumn.headertext

希望问题很明确。

感谢

- 编辑 -

右边的那个是上下文菜单对于任何给定单元格应该是什么样的。

enter image description here

7 个答案:

答案 0 :(得分:35)

Theres代表那个! - 无需子类

在IB中,如果您将NSTableView拖到窗口/视图上,您​​会注意到该表的menu出口。

因此,实现上下文菜单的一种非常简单的方法是将该出口连接到存根菜单,并将菜单的委托出口连接到实现NSMenuDelegate协议方法- (void)menuNeedsUpdate:(NSMenu *)menu的对象

interface builder screen shot

通常,菜单的委托是向表提供数据源/委托的同一对象,但它也可能是拥有该表的视图控制器。

Have a look at the docs了解有关此内容的更多信息

你可以在协议中使用一堆聪明的东西,但是一个非常简单的实现可能如下所示

#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];
   }        
}

}

然后是构建菜单的方法。

-(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;
}

您选择如何实施titleForRow:titleForColumn:取决于您。

请注意,NSMenuItem提供了属性representedObject,允许您将任意对象绑定到菜单项,从而将信息发送到您的方法中(例如deleteObject:

修改

注意 - 在- (void)menuNeedsUpdate:(NSMenu *)menu子类中实施NSDocument将停止出现在10.8中出现的标题栏中的自动保存/版本菜单。

它仍然适用于10.7所以去图。在任何情况下,菜单委托都必须是您的NSDocument子类以外的其他东西。

答案 1 :(得分:3)

编辑:比以下方法更好的方法是使用委托,如接受的答案所示。

您可以创建UITableView的子类并实现menuForEvent:方法:

-(NSMenu *)menuForEvent:(NSEvent *)event{
    if (event.type==NSRightMouseDown) {
        if (self.selectedColumn == 0 || self.selectedColumn ==1) {
            return nil;
        }else {
            //create NSMenu programmatically or get a IBOutlet from one created in IB
            NSMenu *menu=[[NSMenu alloc] initWithTitle:@"Custom"];

            //code to set the menu items

            //Instead of the following line get the value from your datasource array/dictionary
            //I used this as I don't know how you have implemented your datasource, but this will also work
            NSString *deleteValue = [[self preparedCellAtColumn:1 row:self.selectedRow] title]; 

            NSString *deleteString = [NSString stringWithFormat:@"Delete %@",deleteValue];
            NSMenuItem *deleteItem = [[NSMenuItem alloc] initWithTitle:deleteString action:@selector(deleteAction:) keyEquivalent:@""];
            [menu addItem:deleteItem];

            //save item
            //similarly 
            [menu addItem:saveItem];

            return menu;
        }
    }
    return nil;
}

应该这样做。我没有尝试过代码。但这应该会给你一个想法。

答案 2 :(得分:2)

我也尝试过Warren Burton发布的解决方案,它运行正常。 但在我的情况下,我不得不在菜单项中添加以下内容:

[item1 setTarget:self];
[item2 setTarget:self];

明确设置无目标会导致上下文菜单保持禁用状态。

干杯!

亚历

PS:我会将此作为评论发布,但我没有足够的声誉:

答案 3 :(得分:1)

Warren Burton的回答是正确的。对于那些在Swift中工作的人,下面的示例片段可能会为您节省从Objective C翻译的工作。在我的例子中,我将上下文菜单添加到NSOutlineView中的单元格而不是NSTableView。在此示例中,菜单构造函数查看该项目,并根据项目类型和状态提供不同的选项。委托(在IB中设置)是一个管理NSOutlineView的ViewController。

 func menuNeedsUpdate(menu: NSMenu) {
    // get the row/column from the NSTableView (or a subclasse, as here, an NSOutlineView)
    let row = outlineView.clickedRow
    let col = outlineView.clickedColumn
    if row < 0 || col < 0 {
        return
    }
    let newItems = constructMenuForRow(row, andColumn: col)
    menu.removeAllItems()
    for item in newItems {
        menu.addItem(item)
        // target this object for handling the actions
        item.target = self
    }
}

func constructMenuForRow(row: Int, andColumn column: Int) -> [NSMenuItem]
{
    let menuItemSeparator = NSMenuItem.separatorItem()
    let menuItemRefresh = NSMenuItem(title: "Refresh", action: #selector(refresh), keyEquivalent: "")
    let item = outlineView.itemAtRow(row)
    if let block = item as? Block {
        let menuItem1 = NSMenuItem(title: "Delete \(block.name)", action: #selector(deleteBlock), keyEquivalent: "")
        let menuItem2 = NSMenuItem(title: "New List", action: #selector(addList), keyEquivalent: "")
        return [menuItem1, menuItem2, menuItemSeparator, menuItemRefresh]
    }
    if let field = item as? Field {
        let menuItem1 = NSMenuItem(title: "Delete \(field.name)", action: #selector(deleteField), keyEquivalent: "")
        let menuItem2 = NSMenuItem(title: "New Field", action: #selector(addField), keyEquivalent: "")
        return [menuItem1, menuItem2, menuItemSeparator, menuItemRefresh]
    }
    return [NSMenuItem]()
}

答案 4 :(得分:0)

正如TheGoonie所提到的,我也得到了相同的经验 - 上下文菜单项仍然被禁用。但是,项目被禁用的原因是“自动启用项目”&#39;属性。

制作&#39;自动启用商品&#39;财产关闭。或以编程方式将其设置为NO。

[mTableViewMenu setAutoenablesItems:NO];

答案 5 :(得分:0)

以下是在视图控制器中以编程方式设置NSOutlineView的示例。这是启动和运行上下文菜单所需的所有管道。不需要子类化。

我之前已经将NSOutlineView子类化为覆盖菜单(对于事件:NSEvent),但在上面的Graham's回答hereWarren's回答的帮助下进行了更简单的设置。

class OutlineViewController: NSViewController 
{
    // ...
    var outlineView: NSOutlineView!
    var contextMenu: NSMenu! 

    override func viewDidLoad()
    {
        // ...
        outlineView = NSOutlineView()
        contextMenu = NSMenu()
        contextMenu.delegate = self
        outlineView.menu = contextMenu
    }
}

extension OutlineViewController: NSMenuDelegate
{
    func menuNeedsUpdate(_ menu: NSMenu) {

        // clickedRow catches the right-click here 
        print("menuNeedsUpdate called. Clicked Row: \(outlineView.clickedRow)")

        // ... Flesh out the context menu here
    }
}

答案 6 :(得分:0)

这是我发现的自定义/动态NSMenu最简单的方法,还保留了系统外观(蓝色选择边框)。子类NSTableView并在menu(for:)中设置菜单。

重要的部分是在表视图上设置菜单,但从其super调用中返回菜单

override func menu(for event: NSEvent) -> NSMenu? {
    let point = convert(event.locationInWindow, from: nil)
    let clickedRow = self.row(at: point)
    var menuRows = selectedRowIndexes

    // The blue selection box should always reflect the
    // returned row indexes.
    if menuRows.isEmpty || !menuRows.contains(clickedRow) {
        menuRows = [clickedRow]
    }

    // Build your custom menu based on the menuRows indexes
    self.menu = <#myMenu#>

    return super.menu(for: event)
}