NSStatusBarButton поддерживает выделение

В OS X 10.10 большая часть NSStatusItem устарела в пользу свойства button, которое состоит из NSStatusBarButton. Он должен работать как обычная кнопка, но, к сожалению, методы cell и setCell в NSStatusButton также устарели. В результате этого я изо всех сил пытаюсь найти способ подсветки кнопки после щелчка (обычно кнопка подсвечивается мышью вниз и не высвечивается мышью вверх. Я хочу, чтобы она была подсвечена после мыши).

Вызов [NSStatusButton setHighlighted:] в его действии не работает, потому что кажется, что он не высвечивается сразу после того, как мышь встала. С другой стороны, использование задержки для ее вызова в следующем цикле, то есть [self performSelector: withDelay:], вызывает подсветку, чтобы она выглядела довольно неприглядно. Он работает, но выглядит не очень хорошо.

Настройка типа кнопки на NSToggleButton полностью удаляет выделение и вместо этого выделяет изображение шаблона, которое было нечетным.

Это были единственные методы, о которых я мог думать. В любом случае, чтобы переопределить поведение мыши с помощью NSButtonCell?

Ответ 1

Я добавил subview в элемент состояния, и внутри этого представления я добавил обработчики событий для mouseDown и т.д., которые вызвали [[statusItem button] highlight: true]. Как выясняется setHighlighted: не делает то же самое, что выделить:.

NSArray *array = [NSArray arrayWithObjects:[statusItem button], [self statusItemView], nil];
[[[statusItem button] superview] setSubviews:array];
//Highlight like so:
[[statusItem button] highlight:true];

EDIT: по El Capitan этот метод больше не работает, и ни statusItem.button.highlight = true, ни 😀🔫

Ответ 2

Вот еще один вариант. Не устанавливайте свойство NSStatusItem action. Вместо этого добавьте локальный монитор событий:

[NSEvent addLocalMonitorForEventsMatchingMask:(NSLeftMouseDown | NSRightMouseDown)
                                      handler:^NSEvent *(NSEvent *event) {
                                          if (event.window == self.statusItem.button.window) {
                                              [self itemClicked];
                                              return nil;
                                          }
                                          return event;
                                      }];

Затем в -itemClicked выделите кнопку с помощью метода highlight::

- (void)itemClicked {
    [self.statusItem.button highlight:YES];
    // Do other stuff 
}

Чтобы отключить только кнопку вызова highlight:NO, где вам нужно.

Ответ 3

Свифт 3 версии Ответ Манфреда Урбана. Работает на Эль Капитан.

extension NSStatusBarButton {

   public override func mouseDown(_ event: NSEvent) {

        if (event.modifierFlags.contains(NSControlKeyMask)) {
            self.rightMouseDown(event)
            return
        }

        self.highlight(true)

        (self.target as? TrivialTargetClass)?.togglePopover()
    }
}

Не забудьте указать, что при необходимости кнопки выделяют свойство false.

Ответ 4

TL; DR: Любой экземпляр NSButtonNSImage с template=YES) внутри свойства NSStatusItem визуально выглядит точно так же, как NSStatusBarButton. Вы можете управлять свойством highlight, как хотите.

Если вы хотите вручную управлять подсветкой своего NSStatusItem и в то же время получить все преимущества от использования NSStatusBarButton, вы можете использовать несколько иной подход. Вы можете создать свой собственный экземпляр NSButton, у которого есть собственное свойство highlight, которое полностью находится под вашим контролем. Затем вам нужно создать экземпляр NSImage и установить его свойство template в YES. Затем вы должны добавить этот button в [NSStatusItem view] (да, который есть softly deprecated) или даже как subview для созданной системы [NSStatusItem button]. После этого вы должны нарисовать фон NSStatusItem вручную с помощью [NSStatusItem drawStatusBarBackgroundInRect:withHighlight:] (который также устарел, ох).

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

Ответ 5

Борясь с этой проблемой, я обнаружил, что перезапись mouseDown: в категории на NSStatusBarButton работает:

#import "MUTargetClass.h"

@implementation NSStatusBarButton (Additions)

- (void)mouseDown:(NSEvent *)theEvent
{
    // Relay CTRL+Click to perform a right click action
    if(theEvent.modifierFlags & NSControlKeyMask)
    {
        [self rightMouseDown:theEvent];
        return;
    }

    // Handle highlighting
    [self setHighlighted:YES];

    // Perform action on target
    [(MUTargetClass *)self.target actionSelector:self];
}

@end

MUTargetClass мог бы тогда реализовать:

#import "NSStatusBarButton+Additions.h"

@implementation MUTargetClass

[…]
    self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
    NSStatusBarButton *button = [self.statusItem button];
    [button setTarget:self];
[…]

- (void)actionSelector:(id)sender
{
    // Whatever behavior a click on the button should invoke
}

[…]
    // Reset button highlighting status when done
    [[self.statusItem button] setHighlighted:NO];
[…]

@end

Обратите внимание, что функциональность CTRL + нажатия кнопки теряется в mouseDown: -override. Как показано выше, его можно восстановить, передав событие на rightMouseDown:.

Простейший способ иметь вызванное действие будет чем-то вроде строк [self.target performSelector:self.action] в категории и [self.statusItem setAction:@selector(actionSelector:)] в целевом классе, однако этот может вызвать утечку в проектах ARC.

Изменить: Это работает и на El Capitan.

Ответ 6

Одна идея - swizzling isHighlighted of NSStatusBarButtonCell и возвращает произвольное значение. https://gist.github.com/questbeat/3244601c8dc779484076

Но проблема в том, разрешает ли Apple обзор команды такой подход или нет...

Ответ 7

Ответ Луке велик. Я просто использую мою реализацию на основе этого.

NSButton *button = [[NSButton alloc] initWithFrame:self.statusItem.button.frame];
button.alphaValue = 0;
NSArray *array = @[self.statusItem.button, button];
self.statusItem.button.superview.subviews = array;

[button sendActionOn:(NSLeftMouseDownMask | NSRightMouseDownMask)];
button.target = self;
button.action = @selector(statusItemClicked);

Ответ 8

Если вы планируете выделение кнопки для последующего выполнения в основном потоке, все, кажется, работает. Это работает и на El Capitan.

    if self.appPopover.shown {
        self.appPopover.performClose(sender)
    } else {
        if let button = statusItem.button {
            // set the button highlighted property to true
            dispatch_async(dispatch_get_main_queue(), {
                button.highlighted = true
            })

            appPopover.showRelativeToRect(button.bounds, ofView: button, preferredEdge: NSRectEdge.MinY)
        }
    }

Это работает, но вы можете заметить небольшое мерцание из-за того, что состояние кнопки изменяется от ON на OFF, а затем снова включено. ВЫКЛ происходит из-за всплывающего дисплея. Поэтому, чтобы исправить это, просто переместите вызов appPopover.showRelativeToRect() внутри блока dispatch_async(), сразу после установки кнопки подсвечивается.

Ответ 9

Решение Антона прекрасное. Здесь он находится в Swift 3:

NSEvent.addLocalMonitorForEvents(matching: .leftMouseDown) { [weak self] event in
    if event.window == self?.statusItem.button?.window {
        // Your action:
        self?.togglePopover(self?.statusItem.button)
        return nil
    }

    return event
}

Я добавил наблюдателя для NSApplicationWillResignActive, чтобы закрыть popover и установить кнопку isHighlighted на false.