Привязка содержимого NSPopupButton к NSAttributedString

У меня есть NSArrayController динамически заполняет таблицу множеством столбцов, один из которых имеет всплывающую кнопку. Содержимое ячейки всплывающей кнопки необходимо использовать NSAttributedString так как мне нужно отобразить научную переменную с индексом (например, X1 с пониженным 1).

Привязка всплывающей ячейки content values к массиву NSAttributedString приводит к бреду в интерфейсе, так как понимает только NSString объекты.

Меню всплывающей кнопки не является привязываемым (то есть невозможно назначать динамически через привязки).

Содержимое всплывающего меню кнопок также не может быть связано динамически.

Кто-нибудь может предложить способ (придерживаясь привязок по крайней мере для остальной части содержимого таблицы) для динамического заполнения NSPopUpButtonCell меню с NSAttributedString объекты?

1 ответ

Решение

Для отображения приписанных строк в меню, когда оно всплывающее, я предлагаю установить столбец таблицы, содержащий всплывающую ячейку, чтобы он Content обязательное указание на NSArrayController что само по себе связано с NSArray из NSAttributedStrings содержащий все параметры, а затем поставить делегата на NSMenu содержится во всплывающей ячейке, а затем делает что-то вроде этого в делегате:

- (void)menuNeedsUpdate:(NSMenu*)menu
{
    for (NSMenuItem* item in menu.itemArray)
        if ([item.representedObject isKindOfClass: [NSAttributedString class]])
        {
            item.attributedTitle = item.representedObject;
        }
}

Привязка поставит не домогаться NSAttributedString в representedObject собственность NSMenuItem, Вы можете найти его там и положить в attributedTitle свойство, которое заставит его показывать приписанную строку в меню. В итоге, пункт меню, нарисованный в меню, с его attributedTitle свойство, установленное соответствующим образом, нарисует стилизованный текст.

Что немного сложнее, так это рисование атрибутивной строки, как и предполагалось во всплывающей ячейке, когда меню не отображается. NSPopUpButtonCell кажется, чтобы сделать, имея NSMenuItem это привлекает к этому. К сожалению, создание этого конкретного NSMenuItem Похоже, не включает в себя толкание нематериального значения в нем. Вместо этого заголовок представляется в виде простой строки без атрибутов. Я не смог придумать элегантного решения для этого, но я нашел неуместный обходной путь:

Сначала добавьте NSTextField колонка к вашему NSTableView который рисует текущую выбранную приписанную строку правильно (то есть с атрибутами). Сделайте этот столбец скрытым. Подкласс NSPopUpButtonCell или используйте категорию и соответствующее хранилище, чтобы добавить новое частное свойство в NSPopUpButtonCell, Это свойство будет содержать блок, который вы можете использовать во время отрисовки для извлечения соответствующей ячейки из скрытого столбца. Добавить NSTableViewDelegateи реализовать -tableView:dataCellForTableColumn:row:, Когда это вызывается для всплывающего столбца, создайте блок для извлечения ячейки из скрытого столбца и вставьте ее в свойство вашего подкласса. Затем во время рисования, если у вас есть блок выборки ячеек, очистите title на menuItem что он обычно использовал бы для рендеринга, вызывал super (чтобы получить маленькие стрелки для всплывающего окна), затем извлекал суррогатную ячейку и выполнял ее рисование. Вот как выглядит код:

@interface AppDelegate : NSObject <NSApplicationDelegate, NSMenuDelegate, NSTableViewDelegate>

@property (assign) IBOutlet NSTableColumn *popUpColumn;
@property (assign) IBOutlet NSTableColumn *surrogateColumn;

// ...snip...

@end

@interface SOPopUpButtonCell : NSPopUpButtonCell

typedef NSTextFieldCell* (^CellFetcher)();
@property (nonatomic, copy, readwrite) CellFetcher cellFetcherBlock;

@end

@implementation AppDelegate

// ...snip...

- (NSCell *)tableView:(NSTableView *)tableView dataCellForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
    if (nil == tableColumn || self.popUpColumn != tableColumn)
        return nil;

    SOPopUpButtonCell* defaultCell = (SOPopUpButtonCell*)[tableColumn dataCellForRow: row];
    const NSUInteger columnIndex = [[tableView tableColumns] indexOfObject: self.surrogateColumn];
    CellFetcher f = ^{
        return (NSTextFieldCell*)[tableView preparedCellAtColumn: columnIndex row: row];
    };
    defaultCell.cellFetcherBlock = f;

    return defaultCell;
}

@end

@implementation SOPopUpButtonCell

- (void)setCellFetcherBlock:(CellFetcher)cellFetcherBlock
{
    if (_cellFetcherBlock != cellFetcherBlock)
    {
        if (_cellFetcherBlock) 
            Block_release(_cellFetcherBlock);

        _cellFetcherBlock = cellFetcherBlock ? Block_copy(cellFetcherBlock) : nil;
    }
}

- (void)dealloc
{
    if (_cellFetcherBlock) 
        Block_release(_cellFetcherBlock);
    [super dealloc];
}

- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{
    CellFetcher f = self.cellFetcherBlock;
    if (f)
        self.menuItem.title = @"";

    [super drawWithFrame:cellFrame inView:controlView];

    if (f)
        NSTextFieldCell* surrogateCell = f();
        [surrogateCell drawWithFrame: cellFrame inView: controlView];
}

@end

Я должен признать, что это заставляет меня чувствовать себя немного грязным, но похоже, что работа сделана. Я разместил весь код, включая xib со всеми связанными привязками на github: Пример проекта

Надеюсь, это поможет.

Другие вопросы по тегам