【问题标题】:Contextual menu on only certain items in a "Source List"仅在“源列表”中的某些项目上的上下文菜单
【发布时间】:2013-01-21 20:59:06
【问题描述】:

我有一个带有源列表 (NSOutlineView) 的窗口。我的源列表只有两个级别。一级是标题,二级是数据。我想在某些数据单元格上有一个上下文菜单。不是全部。

首先,我尝试在代表数据单元格的表格单元格视图上附加一个菜单 -> 没有任何反应。

其次,我在 IB 的大纲视图上附加了一个菜单 -> 上下文菜单在每个单元格(标题和数据)上打开。我搜索停止打开菜单,但我没有找到任何东西。

你有什么想法吗?

谢谢

OS X 10.8.2 Lion、Xcode 4.5.2、SDK 10.8

【问题讨论】:

  • 如果在行视图上设置呢?
  • 如何在行视图上设置?这是 IB 中的层次结构视图 link
  • 我猜大纲视图中没有行视图。我只完成了基于视图的表格视图(具有行视图)和基于单元格的大纲视图,而不是基于视图的大纲视图。对不起。
  • 有人有想法吗?真不知道自己能往哪个方向走……

标签: objective-c cocoa contextmenu nsoutlineview nsmenu


【解决方案1】:

如果您将 NSOutlineView 子类化,您可以覆盖 menuForEvent: 以仅在用户单击正确的行时返回菜单。这是一个例子:

- (NSMenu *)menuForEvent:(NSEvent *)event;
{
    //The event has the mouse location in window space; convert it to our (the outline view's) space so we can find which row the user clicked on.
    NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
    NSInteger row = [self rowAtPoint:point];

    //If the user did not click on a row, or is not exactly one level down from the top level of hierarchy, return nil—that is, no menu.
    if ( row == -1 || [self levelForRow:row] != 1 )
        return nil;

    //Create and populate a menu.
    NSMenu *menu = [[NSMenu alloc] init];
    NSMenuItem *delete = [menu addItemWithTitle:NSLocalizedString( @"Delete", @"" ) action:@selector(delete:) keyEquivalent:@""];

    [self selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];

    //Set the Delete menu item's represented object to the clicked-on item. If the user chooses this item, we'll retrieve its represented object so we know what to delete.
    [delete setRepresentedObject:[self itemAtRow:row]];

    return menu;
}

这假设我们使用 ARC 进行编译,因此您不需要自动释放正在创建的菜单对象。

【讨论】:

  • 另外,如果提问者在同一个笔尖中同时拥有大纲视图和所需菜单,他可以通过通常的插座将其连接起来,然后在此方法中使用[self menu] 来检索它而不是每次创建一个新的。
【解决方案2】:

这个扩展 + 子类(NSOutlineView 和 NSTableView)可以判断菜单是附加到单元格视图还是行视图。只是一个通用的、可重用的子类!

outlineView:viewForTableColumn:item: 中的单元格视图上设置菜单 - menu 是 NSResponder 属性。

(以下是 Swift 语言)

// An extension lets us both subclass NSTableView and NSOutlineView with the same functionality
extension NSTableView {
    // Find a cell view, or a row view, that has a menu. (e.g. NSResponder’s menu: NSMenu?)
    func burnt_menuForEventFromCellOrRowViews(event: NSEvent) -> NSMenu? {
        let point = convertPoint(event.locationInWindow, fromView: nil)
        let row = rowAtPoint(point)
        if row != -1 {
            if let rowView = rowViewAtRow(row, makeIfNecessary: true) as? NSTableRowView {
                let column = columnAtPoint(point)
                if column != -1 {
                    if let cellView = rowView.viewAtColumn(column) as? NSTableCellView {
                        if let cellMenu = cellView.menuForEvent(event) {
                            return cellMenu
                        }
                    }
                }

                if let rowMenu = rowView.menuForEvent(event) {
                    return rowMenu
                }
            }
        }

        return nil
    }
}


class OutlineView: NSOutlineView {
    override func menuForEvent(event: NSEvent) -> NSMenu? {
        // Because of weird NSTableView/NSOutlineView behaviour, must set receiver’s menu otherwise the target cannot be found
        self.menu = burnt_menuForEventFromCellOrRowViews(event)

        return super.menuForEvent(event)
    }
}

class TableView: NSTableView {
    override func menuForEvent(event: NSEvent) -> NSMenu? {
        // Because of weird NSTableView/NSOutlineView behaviour, must set receiver’s menu otherwise the target cannot be found
        self.menu = burnt_menuForEventFromCellOrRowViews(event)

        return super.menuForEvent(event)
    }
}

【讨论】:

    【解决方案3】:

    从您的问题中不清楚您的大纲是基于视图还是基于单元格。这很重要。

    如果你是基于视图的,那么你的视图实例可以实现

    - (NSMenu *)menuForEvent:(NSEvent *)theEvent
    

    并返回适合该项目的菜单 - 如果您根本不想要菜单,则返回 nil。

    如果您是基于单元的,或者出于某种原因不想在视图类中处理此问题,则需要将 NSOutlineView 子类化并在那里实现- (NSMenu *)menuForEvent:(NSEvent *)theEvent。同样,您将确定哪个单元格被点击或激活,并从中决定您想要什么菜单。

    【讨论】:

    • 谢谢马克。是的,我的大纲视图是“基于视图的”。我有一个类是 NSTableCellView 的子类。我在这个类中添加了一个“menuForEvent”的实现,我在里面放了一个断点,但它从来没有到达过……有什么特别的事情要做吗?
    • 是时候系统化了。首先检查您的视图在单击它时是否接收到 mouseDown 事件。可能某些子视图正在拦截事件。一旦你确定你得到了这个事件,你应该确保你没有因为忘记在某处调用超类方法而排除了内置方法。
    【解决方案4】:
    - (void)rightMouseDown:(NSEvent *)event
    

    一个 NSView 不会把它传递给下一个视图,这个方法会查看当前类有一个 menuForEvent:,如果有,那么它被调用。如果没有,那么它就完成了,不会发生其他任何事情。这就是为什么你不会看到 NSTableCellView 响应 menuForEvent: 因为表格视图吞下了 rightMouseDown:。

    您可以继承 tableview 并处理 rightMouseDown: 事件并调用 NSTableCellView 的 rightMouseDown: 并处理显示您在情节提要中构建并连接到 NSTableViewCell 的菜单。

    这是我在子类 NSTableView 中的解决方案:

    - (void)rightMouseDown:(NSEvent *)event
    {
        for (NSTableRowView *rowView in self.subviews) {
    
            for (NSView *tableCellView in [rowView subviews]) {
    
                if (tableCellView) {
                    NSPoint eventPoint = [event locationInWindow];
    //            NSLog(@"Window Point:     %@", NSStringFromPoint(eventPoint));
    
                    eventPoint = [self convertPoint:eventPoint toView:nil];
                    eventPoint = [self convertPoint:eventPoint toView:self];
    //            NSLog(@"Table View Point: %@", NSStringFromPoint(eventPoint));
    
                    NSRect newRect = [tableCellView convertRect:[tableCellView bounds] toView:self];
    //            NSLog(@"Rect: %@", NSStringFromRect(newRect));
    
                    BOOL rightMouseDownInTableCellView = [tableCellView mouse:eventPoint inRect:newRect];
    //            NSLog(@"Mouse in view: %hhd", mouseInView);
    
                    if (rightMouseDownInTableCellView) {
                        if (tableCellView) {
                            // Lets be safe and make sure that the object is going to respond.
                            if ([tableCellView respondsToSelector:@selector(rightMouseDown:)]) {
                                [tableCellView rightMouseDown:event];
                            }
                        }
                    }
                }
            }
        }
    }    
    

    这将找到鼠标右键事件发生的位置,检查我们是否有正确的视图并将 rightMouseDown: 传递给该视图。

    如果此解决方案适合您,请告诉我。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-01-31
      • 1970-01-01
      • 1970-01-01
      • 2017-12-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多