如何向 NSOutlineView 添加上下文相关菜单(即右键菜单)

发布于 2024-08-02 01:54:15 字数 1178 浏览 8 评论 0原文

如何添加右键单击 NSOutlineView 中的行的功能,以便您可以删除对象或其他一些活动。 (即,就像当您右键单击 Apple Mail 应用程序中的文件夹时)

我想我已经完成了一半,我有一个 NSOutlineView 的子类,它允许我捕获右键单击并显示基于所选行而不是的上下文菜单鼠标单击的行。

@implementation NSContextOutlineView

    - (NSMenu *)defaultMenu {
        if([self selectedRow] < 0) return nil;
        NSMenu *theMenu = [[[NSMenu alloc] initWithTitle:@"Model browser context menu"] autorelease];
        [theMenu insertItemWithTitle:@"Add package" action:@selector(addSite:) keyEquivalent:@"" atIndex:0];
        NSString* deleteItem = [NSString stringWithFormat: @"Remove '%i'", [self selectedRow]];
        [theMenu insertItemWithTitle: deleteItem action:@selector(removeSite:) keyEquivalent:@"" atIndex:1];
        return theMenu;
    }

    - (NSMenu *)menuForEvent:(NSEvent *)theEvent {
        return [self defaultMenu];  
    }
@end

抱歉,如果答案很明显,我只是在网上或文档中找不到任何帮助。

感谢 Void 的回答,它引导我使用这个:

- (NSMenu *)menuForEvent:(NSEvent *)theEvent {
    NSPoint pt = [self convertPoint:[theEvent locationInWindow] fromView:nil];
    id item = [self itemAtRow: [self rowAtPoint:pt]];
    return [self defaultMenuFor: item];
}

How do you add the ability to right click on a row in an NSOutlineView so you can say delete an object or some other activity. (ie Like when you right click on a folder in the Apple Mail app)

I think I am half way there, I have a subclass of NSOutlineView that allows me to catch the right click and display a context menu based on the selected row rather than the row the mouse is clicking on.

@implementation NSContextOutlineView

    - (NSMenu *)defaultMenu {
        if([self selectedRow] < 0) return nil;
        NSMenu *theMenu = [[[NSMenu alloc] initWithTitle:@"Model browser context menu"] autorelease];
        [theMenu insertItemWithTitle:@"Add package" action:@selector(addSite:) keyEquivalent:@"" atIndex:0];
        NSString* deleteItem = [NSString stringWithFormat: @"Remove '%i'", [self selectedRow]];
        [theMenu insertItemWithTitle: deleteItem action:@selector(removeSite:) keyEquivalent:@"" atIndex:1];
        return theMenu;
    }

    - (NSMenu *)menuForEvent:(NSEvent *)theEvent {
        return [self defaultMenu];  
    }
@end

Sorry if the answer is obvious I just cant find any help on this online or in the documentation.

Thanks to Void for the answer, it lead me to using this:

- (NSMenu *)menuForEvent:(NSEvent *)theEvent {
    NSPoint pt = [self convertPoint:[theEvent locationInWindow] fromView:nil];
    id item = [self itemAtRow: [self rowAtPoint:pt]];
    return [self defaultMenuFor: item];
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

被你宠の有点坏 2024-08-09 01:54:15

在 menuForEvent 方法中,您可以找出单击发生在哪一行。 您可以将其作为参数传递给您的 defaultMenu 方法 - 也许将其称为 defaultMenuForRow:

-(NSMenu*)menuForEvent:(NSEvent*)evt 
{
    NSPoint pt = [self convertPoint:[evt locationInWindow] fromView:nil];
    int row=[self rowAtPoint:pt];
    return [self defaultMenuForRow:row];
}

现在您可以为您在事件中找到的行构建菜单...

-(NSMenu*)defaultMenuForRow:(int)row
{
    if (row < 0) return nil;

    NSMenu *theMenu = [[[NSMenu alloc] 
                                initWithTitle:@"Model browser context menu"] 
                                autorelease];
    [theMenu insertItemWithTitle:@"Add package" 
                          action:@selector(addSite:) 
                   keyEquivalent:@"" 
                         atIndex:0];
    [theMenu insertItemWithTitle:[NSString stringWithFormat:@"Remove '%i'", row] 
                          action:@selector(removeSite:) 
                   keyEquivalent:@"" 
                         atIndex:0];
    // you'll need to find a way of getting the information about the 
    // row that is to be removed to the removeSite method
    // assuming that an ivar 'contextRow' is used for this
    contextRow = row;

    return theMenu;        
}

另外,正如评论中已经提到的,你真的不应该在你自己的类上使用 NS 前缀。 将来有可能发生冲突,而且它会让每个正在查看您的代码的人感到困惑 - 包括您自己:)

希望这会有所帮助......

In your menuForEvent method you can find out which row the click occurred on. You can pass that as a parameter to your defaultMenu method -- maybe call it defaultMenuForRow:

-(NSMenu*)menuForEvent:(NSEvent*)evt 
{
    NSPoint pt = [self convertPoint:[evt locationInWindow] fromView:nil];
    int row=[self rowAtPoint:pt];
    return [self defaultMenuForRow:row];
}

Now you can build the menu for the row you found in the event...

-(NSMenu*)defaultMenuForRow:(int)row
{
    if (row < 0) return nil;

    NSMenu *theMenu = [[[NSMenu alloc] 
                                initWithTitle:@"Model browser context menu"] 
                                autorelease];
    [theMenu insertItemWithTitle:@"Add package" 
                          action:@selector(addSite:) 
                   keyEquivalent:@"" 
                         atIndex:0];
    [theMenu insertItemWithTitle:[NSString stringWithFormat:@"Remove '%i'", row] 
                          action:@selector(removeSite:) 
                   keyEquivalent:@"" 
                         atIndex:0];
    // you'll need to find a way of getting the information about the 
    // row that is to be removed to the removeSite method
    // assuming that an ivar 'contextRow' is used for this
    contextRow = row;

    return theMenu;        
}

Also, as already mentioned in the comments, you really shouldn't use the NS-prefix on your own classes. There is a potential for a clash in the future plus it will confuse everybody that is looking at your code - including yourself :)

Hope this helps...

也只是曾经 2024-08-09 01:54:15

这是一个 Swift 2.0 示例,它使用子类并扩展默认的 NSOutlineDelegate ,以便您可以在委托中定义菜单。

protocol MenuOutlineViewDelegate : NSOutlineViewDelegate {
    func outlineView(outlineView: NSOutlineView, menuForItem item: AnyObject) -> NSMenu?
}

class MenuOutlineView: NSOutlineView {

    override func menuForEvent(event: NSEvent) -> NSMenu? {
        let point = self.convertPoint(event.locationInWindow, fromView: nil)
        let row = self.rowAtPoint(point)
        let item = self.itemAtRow(row)

        if (item == nil) {
            return nil
        }

        return (self.delegate() as! MenuOutlineViewDelegate).outlineView(self, menuForItem: item!)
    }

}

Here is a Swift 2.0 example which uses a subclass and extends the default NSOutlineDelegate so you can define your menus in the delegate.

protocol MenuOutlineViewDelegate : NSOutlineViewDelegate {
    func outlineView(outlineView: NSOutlineView, menuForItem item: AnyObject) -> NSMenu?
}

class MenuOutlineView: NSOutlineView {

    override func menuForEvent(event: NSEvent) -> NSMenu? {
        let point = self.convertPoint(event.locationInWindow, fromView: nil)
        let row = self.rowAtPoint(point)
        let item = self.itemAtRow(row)

        if (item == nil) {
            return nil
        }

        return (self.delegate() as! MenuOutlineViewDelegate).outlineView(self, menuForItem: item!)
    }

}
随心而道 2024-08-09 01:54:15

无需子类化,它非常简单,您甚至可以动态自定义菜单。

声明一个空菜单,设置其委托并将其设置在大纲视图 .menu 属性上。 作为一个额外的好处,此方法适用于大纲视图和表格视图。

class OutlineViewController: NSViewController {

     private let contextMenu = NSMenu(title: "Context")
     
     override func viewDidLoad() {
        super.viewDidLoad()

        // other init stuff...

        contextMenu.delegate = self
        outlineView.menu = contextMenu
    }
}

extension OutlineViewController: NSMenuDelegate {

    func menuNeedsUpdate(_ menu: NSMenu) {
        // Returns the clicked row indices.
        // If the right click happens inside a selection, it is usually
        // the selected rows, if it appears outside of the selection it
        // is only the right clicked row with a blue border, as defined
        // in the `NSTableView` extension below.
        let indexes = outlineView.contextMenuRowIndexes

        menu.removeAllItems()
        
        // TODO: add/modify item as needed here before it is shown
    }
}

extension NSTableView {

    var contextMenuRowIndexes: IndexSet {
        var indexes = selectedRowIndexes

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

        return indexes
    }
}

No need to subclass, its quite simple, and you can even customize the menu on the fly.

Declare an empty menu, set its delegate and set it on the outline views .menu property. As an added bonus this method works on both, outline and table views the same.

class OutlineViewController: NSViewController {

     private let contextMenu = NSMenu(title: "Context")
     
     override func viewDidLoad() {
        super.viewDidLoad()

        // other init stuff...

        contextMenu.delegate = self
        outlineView.menu = contextMenu
    }
}

extension OutlineViewController: NSMenuDelegate {

    func menuNeedsUpdate(_ menu: NSMenu) {
        // Returns the clicked row indices.
        // If the right click happens inside a selection, it is usually
        // the selected rows, if it appears outside of the selection it
        // is only the right clicked row with a blue border, as defined
        // in the `NSTableView` extension below.
        let indexes = outlineView.contextMenuRowIndexes

        menu.removeAllItems()
        
        // TODO: add/modify item as needed here before it is shown
    }
}

extension NSTableView {

    var contextMenuRowIndexes: IndexSet {
        var indexes = selectedRowIndexes

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

        return indexes
    }
}
浪漫之都 2024-08-09 01:54:15

比OP问题晚得多,但对于像我这样想知道的人来说,这是我的解决方案。 它还需要子类化 NSOutlineView,无论如何,Apple 文档不鼓励这样做......

而不是覆盖 menuForEvent: 我覆盖 rightMouseDown:

- (void)rightMouseDown:(NSEvent *)event {
    NSPoint pt = [self convertPoint:[event locationInWindow] fromView:nil];
    NSInteger row = [self rowAtPoint:pt];
    id item = [self itemAtRow:row];
    NSMenu *menu;
    //set the menu to one you have defined either in code or IB through outlets
    self.menu = menu;
    [super rightMouseDown:event];
}

这具有保持委托调用来更新的优点此后的菜单,并在右键单击时保持行大纲。

Much later than the OP question, but for others like me wondering, here is my solution. It also needs subclassing NSOutlineView, which is not encouraged by Apple doc, anyway…

Rather than override menuForEvent: I override rightMouseDown:

- (void)rightMouseDown:(NSEvent *)event {
    NSPoint pt = [self convertPoint:[event locationInWindow] fromView:nil];
    NSInteger row = [self rowAtPoint:pt];
    id item = [self itemAtRow:row];
    NSMenu *menu;
    //set the menu to one you have defined either in code or IB through outlets
    self.menu = menu;
    [super rightMouseDown:event];
}

This has the advantage of keeping delegate calls to update the menu thereafter and also keeps row outlining on right click.

多孤肩上扛 2024-08-09 01:54:15

基于 @rdougan 答案的 Swift 5.0 示例:

protocol MenuOutlineViewDelegate : NSOutlineViewDelegate {
    func outlineView(_ outlineView: NSOutlineView, menuForItem item: Any?) -> NSMenu?
}

class MenuOutlineView: NSOutlineView {

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

        return (delegate as! MenuOutlineViewDelegate).outlineView(self, menuForItem: item)
    }

}

显着变化:使用 Any? 而不是 Any(Object) 来允许上下文菜单出现在“根”上。 当您希望整个导航视图(空白区域)保持可右键单击时,这非常有用。

Swift 5.0 example based on @rdougan's answer:

protocol MenuOutlineViewDelegate : NSOutlineViewDelegate {
    func outlineView(_ outlineView: NSOutlineView, menuForItem item: Any?) -> NSMenu?
}

class MenuOutlineView: NSOutlineView {

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

        return (delegate as! MenuOutlineViewDelegate).outlineView(self, menuForItem: item)
    }

}

Notable change: use Any? instead of Any(Object) to allow a context menu to appear on the "root". This is most useful when you want the entire navigation view (the empty areas) to remain right-clickable.

左岸枫 2024-08-09 01:54:15

如果您愿意,可以将菜单附加到单个单元格视图或行视图,并使用界面生成器构建它:

@implementation BSMotleyOutlineView

-(NSMenu *)menuForEvent:(NSEvent *)event
{
    NSPoint pt = [self convertPoint:[event locationInWindow] fromView:nil];
    NSInteger row = [self rowAtPoint:pt];
    if (row >= 0) {
        NSTableRowView* rowView = [self rowViewAtRow:row makeIfNecessary:NO];
        if (rowView) {
            NSInteger col = [self columnAtPoint:pt];
            if (col >= 0) {
                NSTableCellView* cellView = [rowView viewAtColumn:col];
                NSMenu* cellMenu = cellView.menu;
                if(cellMenu) {
                    return cellMenu;
                }
            }
            NSMenu* rowMenu = rowView.menu;
            if (rowMenu) {
                return rowMenu;
            }
        }
    }
    return [super menuForEvent:event];
}
@end

If you prefer, you can attach the menu to the individual cell view or row view and build it with interface builder:

@implementation BSMotleyOutlineView

-(NSMenu *)menuForEvent:(NSEvent *)event
{
    NSPoint pt = [self convertPoint:[event locationInWindow] fromView:nil];
    NSInteger row = [self rowAtPoint:pt];
    if (row >= 0) {
        NSTableRowView* rowView = [self rowViewAtRow:row makeIfNecessary:NO];
        if (rowView) {
            NSInteger col = [self columnAtPoint:pt];
            if (col >= 0) {
                NSTableCellView* cellView = [rowView viewAtColumn:col];
                NSMenu* cellMenu = cellView.menu;
                if(cellMenu) {
                    return cellMenu;
                }
            }
            NSMenu* rowMenu = rowView.menu;
            if (rowMenu) {
                return rowMenu;
            }
        }
    }
    return [super menuForEvent:event];
}
@end
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文