Domanda

I have a window with a Source List (NSOutlineView). My source list has just two levels. Level one is header and level two is data. I want to have a contextual menu on some of the data cells. Not all.

First, I try to attach a menu on the table cell view who represents the data cell -> nothing happens.

Second, I attach a menu on the Outline View in IB -> the contextual menu opens on each cells (header and data). I search for stopping the opening of the menu, but I don't find anything.

Do you have some ideas ?

Thank you

OS X 10.8.2 Lion, Xcode 4.5.2, SDK 10.8

È stato utile?

Soluzione

If you subclass NSOutlineView, you can override menuForEvent: to return a menu only if the user clicked on the correct row. Here's an example:

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

This assumes we're compiling with ARC, so you don't need to autorelease the menu object being created.

Altri suggerimenti

This extension + subclass (both NSOutlineView and NSTableView) does the sensible thing of seeing whether a menu is attached to a cell view or row view. Just a general, reusable subclass!

Set the menu on the cell view in outlineView:viewForTableColumn:item:menu is a NSResponder property.

(Below is in 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)
    }
}

It's not clear from your question whether your outline is view based or cell based. That's important.

If you're view based, then your view instances can implement

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

and return the menu appropriate to that item -- or nil f you don't want a menu at all.

If you're cell based, or if you don't want to handle this in the view class for some reason, you'll need to subclass NSOutlineView and implement - (NSMenu *)menuForEvent:(NSEvent *)theEvent there. Again, you'll figure out which cell is hit or active, and decide from that what menu you want.

- (void)rightMouseDown:(NSEvent *)event

An NSView will not pass this to the next view, This method looks to see that the current class has a menuForEvent:, if it does then it is called. If it does not then it is finished and nothing else will happen. This is why you will not see an NSTableCellView respond to a menuForEvent: because the table view swallows the rightMouseDown:.

You may subclass the tableview and handle the rightMouseDown: event and call the NSTableCellView's rightMouseDown: and handle displaying your menu that you have constructed in your storyboard and hooked up to your NSTableViewCell.

Here is my solution in a subclassed 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];
                        }
                    }
                }
            }
        }
    }
}    

This will find where the right mouse event occurred, check to see if we have the correct view and pass the rightMouseDown: to that view.

Please let me know if this solution works for you.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top