如何实现NSCollectionView的上下文菜单

时间:2014-09-30 22:01:30

标签: macos cocoa contextmenu nscollectionview

在我的 OSX 应用中,我有一个集合视图,它是NSCollectionView的子类。

除了上下文菜单之外,我对这些事情都很满意,我还不知道。

所以我想要的是:

  • 右键单击集合视图项会显示上下文菜单
  • 从菜单中选取的选项(删除,编辑等)将应用于执行点击的项目。

我知道如何为NSOutlineViewNSTableView执行此操作,但不知道如何为集合视图执行此操作。

我无法弄清楚如何获取项目的索引。

有没有人对如何实现这个有任何想法?

非常感谢任何形式的帮助!

5 个答案:

答案 0 :(得分:5)

我使用的一种方法是不尝试将上下文菜单操作应用于单击的一个特定项目,而是应用于所选项目。然后我将点击的项目添加到选择中。

我为集合项视图使用了自定义视图。自定义视图类具有一个插座item到其拥有的集合视图项,我在NIB中连接。它还会覆盖-rightMouseDown:以使项目添加到选择中:

- (void) rightMouseDown:(NSEvent*)event
{
    NSCollectionView* parent = self.item.collectionView;
    NSUInteger index = NSNotFound;
    NSUInteger count = parent.content.count;
    for (NSUInteger i = 0; i < count; i++)
    {
        if ([parent itemAtIndex:i] == self.item)
        {
            index = i;
            break;
        }
    }

    NSMutableIndexSet* selectionIndexes = [[parent.selectionIndexes mutableCopy] autorelease];
    if (index != NSNotFound && ![selectionIndexes containsIndex:index])
    {
        [selectionIndexes addIndex:index];
        parent.selectionIndexes = selectionIndexes;
    }

    return [super rightMouseDown:event];
}

如果您愿意,可以检查它是否已经在选择中,而不是将项目添加到选择中。如果是,请不要修改选择。如果不是,仅用项目替换选项(使其成为唯一选定的项目)。

或者,您可以在项目视图而不是集合视图上设置上下文菜单。然后,菜单项可以指向项目视图或集合视图项目。

最后,您可以继承NSCollectionView并覆盖-menuForEvent:。您仍然可以致电super并返回它返回的菜单,但您可以借此机会在其所在位置记录事件和/或项目。要确定这一点,您可以执行以下操作:

- (NSMenu*) menuForEvent:(NSEvent*)event
{
    _clickedItemIndex = NSNotFound;
    NSPoint point = [self convertPoint:event.locationInWindow fromView:nil];
    NSUInteger count = self.content.count;
    for (NSUInteger i = 0; i < count; i++)
    {
        NSRect itemFrame = [self frameForItemAtIndex:i];
        if (NSMouseInRect(point, itemFrame, self.isFlipped))
        {
            _clickedItemIndex = i;
            break;
        }
    }

    return [super menuForEvent:event];
}

答案 1 :(得分:3)

这里Ken's idea覆盖了在Swift中实现的menuForEvent:子类中的NSCollectionView

// MARK: - Properties

/**
The index of the item the user clicked.
*/
var clickedItemIndex: Int = NSNotFound

// MARK: - Menu override methods

override func menuForEvent(event: NSEvent) -> NSMenu?
{
    self.clickedItemIndex = NSNotFound

    let point = self.convertPoint(event.locationInWindow, fromView:nil)
    let count = self.content.count

    for index in 0 ..< count
    {
        let itemFrame = self.frameForItemAtIndex(index)
        if NSMouseInRect(point, itemFrame, self.flipped)
        {
            self.clickedItemIndex = index
            break
        }
    }

    return super.menuForEvent(event)
}

答案 2 :(得分:2)

基本上,我们所有的解决方案都能满足要求,但我想补充swift3 +,我认为这是一个完整的解决方案。

/// 扩展NSCollectionView功能,增加常用委托
class ANCollectionView: NSCollectionView {
    // 扩展委托方式
    weak open var ANDelegate: ANCollectionViewDelegate?

    override func menu(for event: NSEvent) -> NSMenu? {
        var menu = super.menu(for: event);
        let point = self.convert(event.locationInWindow, from: nil)
        let indexPath = self.indexPathForItem(at: point);
        if ANDelegate != nil{
            menu = ANDelegate?.collectionView(self, menu: menu, at: indexPath);
        }
        return menu;
    }
}

/// 扩展NSCollectionView的委托
protocol ANCollectionViewDelegate : NSObjectProtocol {
    func collectionView(_ collectionView:NSCollectionView, menu:NSMenu?, at indexPath: IndexPath?) -> NSMenu?
}

这是我写的扩展,我希望能帮助每个人。

答案 3 :(得分:0)

感谢您提供此解决方案。我将它包装到NSCollectionView子类中:

#import <Cocoa/Cocoa.h>

@interface TAClickableCollectionViewItem : NSCollectionViewItem
@property (nonatomic, assign) BOOL isClicked;
@end



@interface TAClickableCollectionView : NSCollectionView <NSMenuDelegate>
@property (nonatomic, readonly) id clickedObject;
@property (nonatomic, readonly) TAClickableCollectionViewItem *clickedItem;
@end

因此,您可以在Interface Builder中使用绑定来突出显示单击的项目。

#import "TAClickableCollectionView.h"

@implementation TAClickableCollectionViewItem
@end



@implementation TAClickableCollectionView

- (NSMenu*) menuForEvent:(NSEvent*)event
{
    NSInteger _clickedItemIndex = NSNotFound;
    NSPoint point = [self convertPoint:event.locationInWindow fromView:nil];
    NSUInteger count = self.content.count;
    for (NSUInteger i = 0; i < count; i++)
    {


    NSRect itemFrame = [self frameForItemAtIndex:i];
        if (NSMouseInRect(point, itemFrame, self.isFlipped))
            {
            _clickedItemIndex = i;
            break;
            }
        }

    if(_clickedItemIndex < self.content.count) {
        id obj = [self.content objectAtIndex:_clickedItemIndex];
        TAClickableCollectionViewItem *item = (TAClickableCollectionViewItem *)[self itemAtIndex:_clickedItemIndex];

        if(item != _clickedItem) {
            [self willChangeValueForKey:@"clickedObject"];
            _clickedItem.isClicked = NO;
            _clickedItem = item;
            [self didChangeValueForKey:@"clickedObject"];
        }

        item.isClicked = YES;

        if(obj != _clickedObject) {
            [self willChangeValueForKey:@"clickedObject"];
            _clickedObject = obj;
            [self didChangeValueForKey:@"clickedObject"];
        }
    }

    return [super menuForEvent:event];
}

- (void)menuDidClose:(NSMenu *)menu {
    _clickedItem.isClicked = NO;
}
@end

答案 4 :(得分:0)

在Swift 5中,您可以使用

class ClickedCollectionView: NSCollectionView {
    var clickedIndex: Int?

    override func menu(for event: NSEvent) -> NSMenu? {
        clickedIndex = nil

        let point = convert(event.locationInWindow, from: nil)
        for index in 0..<numberOfItems(inSection: 0) {
            let frame = frameForItem(at: index)
            if NSMouseInRect(point, frame, isFlipped) {
                clickedIndex = index
                break
            }
        }

        return super.menu(for: event)
    }
}