NSStatusBarButton保持高亮

16

自 OS X 10.10 以来,NSStatusItem 的大部分内容已被弃用,取而代之的是属性,它由NSStatusBarButton组成。它应该像普通按钮一样工作,但不幸的是,NSStatusButton中的cellsetCell方法也已被弃用。因此,我正在努力找到一种方法在其单击后保持按钮高亮(通常情况下,鼠标按下时按钮会突出显示,鼠标松开时则取消突出显示。我想在鼠标松开后将其保持突出显示)。

调用[NSStatusButton setHighlighted:]在其动作中不起作用,因为它似乎会在鼠标松开后取消突出显示。另一方面,在下一个循环中使用延迟调用它,即[self performSelector: withDelay:],会导致突出显示以一种相当不美观的方式闪烁。它可以工作,但看起来不好。

将按钮类型设置为NSToggleButton会完全移除突出显示,并突出显示模板图像,这很奇怪。

这些是我能想到的唯一方法。有没有办法覆盖此NSButtonCell mouseUp行为?


你在哪里看到 NSStatusItem 已经被弃用了? - JamesWebbTelescopeAlien
@NulledPointer https://developer.apple.com/library/prerelease/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSStatusItem_Class/index.html#//apple_ref/doc/uid/TP40004118 - Luke
4
NSStatusItem并未被弃用,但是它的几个方法在10.10中已经被弃用。 - ctpenrose
10个回答

15

这里还有一个选项。不要设置NSStatusItemaction属性,而是添加本地事件监听器:

这里有另外一个选择,不要设置NSStatusItemaction属性,而是添加一个本地事件监视器:

[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


1
我四处搜寻,这是唯一的答案。谢谢。 - mxcl
1
这个在Catalina系统还是有效的。一旦匹配到窗口,不要忘记 return nil - Vojto

7

这是 Manfred Urban 的答案的 Swift 3 版本。适用于 El Capitan。

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。


也适用于Sierra。 - rsfinn
我实际上并没有指望这能够奏效,因为扩展不应该用于覆盖行为。 但是,在Mojave上确实可以使用override。 - Ryan H

4

在自己处理这个问题时,我发现在 NSStatusBarButton 的分类中覆盖 mouseDown: 方法是有效的:

#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's highlighting status when done
    [[self.statusItem button] setHighlighted:NO];
[…]

@end

请注意,在 mouseDown: 重写中,CTRL+单击按钮的功能会丢失。如上所示,可以通过将事件传递到 rightMouseDown: 来恢复它。
更简单的调用操作方式是在类别中使用类似 [self.target performSelector:self.action] 的方法,在目标类中使用 [self.statusItem setAction:@selector(actionSelector:)]。然而,在 ARC 项目中,这可能会导致 泄漏
编辑:在 El Capitan 上也可以使用此方法。

@xhacker:在 OS X 10.11 上测试通过。 - Manfred Urban

4

我给状态菜单项添加了一个子视图,在这个视图内部,我又添加了鼠标事件处理程序(mouseDown等),它们会调用[[statusItem button] highlight:true]。实际上,setHighlighted:和highlight:并不是相同的东西。

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

编辑:从El Capitan开始,这种方法不再起作用,statusItem.button.highlight = true也不行。


3
TL;DR: 在 NSStatusItem 属性中,任何带有 template=YESNSImageNSButton 实例在外观上看起来与 NSStatusBarButton 完全相同。 您可以根据自己的需要控制它们的 highlight 属性。
如果您想手动控制 NSStatusItem 的高亮,并同时获得使用 NSStatusBarButton 的所有外观优势,则可以使用略微不同的方法。 您可以创建自己的 NSButton 实例,它具有完全受您控制的 highlight 属性。 然后,您必须创建 NSImage 实例,并将其 template 属性设置为 YES。 然后,您必须将此 button 添加到 [NSStatusItem view](是的,这是softly deprecated),甚至作为子视图添加到系统创建的 [NSStatusItem button] 中。之后,您必须手动绘制 NSStatusItem 的背景,使用 [NSStatusItem drawStatusBarBackgroundInRect:withHighlight:] (这也是已经弃用了)。
通过这种方法,您可以结合完全控制您的 NSStatusItem 的外观和自动设置按钮图像的样式。

2
我花了一整天的时间尝试绕过新API,但幸亏看到了你的评论,我尝试使用弃用的 drawStatusBarBackgroundInRect: withHighlight: API,我认为它表现得更好。我相当确定苹果仍然在他们的菜单栏图标中使用已弃用的API,因为高亮部分保持不变。非常奇怪的是,苹果只会在新的NSStatusBarButton中闪烁高亮。 - iMaddin

2

Anton的解决方案非常完美。以下是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事件,以关闭弹出窗口并将按钮的isHighlighted属性设置为false


1

1
这是@erikvdplas回答的Swift 5版本。我在macOS 11.4 Big Sur上进行了测试。
extension NSStatusBarButton {
    public override func mouseDown(with event: NSEvent) {
        super.mouseDown(with: event)
        self.highlight((NSApp.delegate as! YourAppDelegateClass).popover.isShown)
    }
}

0
如果你安排在主线程的后续执行中突出显示按钮,一切似乎都可以解决。这在El Capitan上也适用。
    if self.appPopover.shown {
        self.appPopover.performClose(sender)
    } else {
        if let button = statusItem.button {
            // set the button's highlighted property to true
            dispatch_async(dispatch_get_main_queue(), {
                button.highlighted = true
            })

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

这个确实可以工作,但是你可能会注意到由于按钮状态从ON变为OFF然后再变为ON而导致的轻微闪烁。 OFF发生是因为弹出窗口显示。所以,要解决这个问题,只需将appPopover.showRelativeToRect()调用移动到dispatch_async()块内,在设置按钮高亮后立即进行。


1
闪烁仍然存在。 - Juri Noga

0

Luke的回答很好。我只是基于他的回答分享我的实现。

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);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接