Как реализовать контекстное меню для NSCollectionView

В моем приложении OSX у меня есть представление коллекции, которое является подклассом NSCollectionView.

Меня все устраивает, за исключением контекстного меню, в котором я пока не могу разобраться.

Итак, я хочу:

  • щелчок правой кнопкой мыши по элементу представления коллекции вызывает контекстное меню
  • параметры, выбранные в меню (удалить, изменить и т. д.), применяются к элементу, по которому был выполнен щелчок.

Я знаю, как это сделать для NSOutlineView или NSTableView, но не для представления коллекции.

Я не могу понять, как получить индекс элемента, по которому щелкнули.

Есть ли у кого-нибудь идеи, как я могу это реализовать?

Любая помощь приветствуется!


person Eugene Gordin    schedule 30.09.2014    source источник


Ответы (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];
}
person Ken Thomases    schedule 01.10.2014
comment
Это слишком сложно. Вы пробовали настроить выход меню в представлении коллекции на меню из файла с пером? Установите объект контроллера в качестве делегата для меню. Каждый раз при вызове меню обновляйте меню в соответствии с выбором в представлении коллекции. - person iljawascoding; 02.02.2015
comment
Мне очень нравится подход подкласса NSCollectionView - дает хорошую согласованность поведения с NSTableView. - person Luke Rogers; 06.07.2015

По сути, все наши решения способны удовлетворить требования, но я хотел бы сделать дополнение к 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?
}

Это то, что я написал как расширение, и надеюсь всем помочь.

person Simon    schedule 12.07.2017
comment
Поскольку NSMouseInRect недоступен в привязках Xamarin, это единственное решение, которое сработало для меня - и оно работает очень хорошо - спасибо! - person tipa; 11.02.2020

Вот идея Кена переопределить menuForEvent: в NSCollectionView подклассе, реализованном в Swift:

// 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)
}
person Luke Rogers    schedule 06.07.2015

В 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)
    }
}
person onmyway133    schedule 23.06.2019

Спасибо за это решение. Я заключил его в подкласс 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

Таким образом, вы можете использовать привязки в Интерфейсном Разработчике, чтобы также выделить выбранные элементы.

#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
person Thomas Abplanalp    schedule 20.04.2017