【问题标题】:How to get frame for NSStatusItem如何获取 NSStatusItem 的框架
【发布时间】:2011-03-24 01:38:10
【问题描述】:

在我将 NSStatusItem 添加到 Cocoa 的状态栏后,是否可以获得它的框架?当我的应用程序启动时,我正在向系统状态栏添加一个项目,并且想知道它的位置,这是可能的。

【问题讨论】:

    标签: cocoa nsstatusitem nsstatusbar


    【解决方案1】:

    以下似乎可行 - 我已经看到类似的 iOS 应用程序解决方案,据说它们允许提交到应用商店,因为您仍在使用标准 SDK 方法。

        NSRect frame = [[statusBarItem valueForKey:@"window"] frame];
    

    【讨论】:

    • 这很有效,并且在您没有为状态项设置自定义视图时很有用。
    • 这假定 NSStatusItem(假设这就是 statusBarItem 是什么)具有 KVC 可以用作 window 属性的东西。不能保证是这样。如果/当它消失时,你会得到一个异常(这个键不符合 KVC)。我也不会指望通过 App Store 审查——如果他们还没有,他们可能有一天会开始检查你如何使用 KVC(寻找这样的用法,你正在访问私有方法/ivars)。
    • 这是一个私有 API 调用,可能会导致您被 App Store 审核流程拒绝吗?
    • 它不被视为私有 API 调用,因为它仅使用键值范式。它过去曾在 App Store 应用程序中使用过,但这并不意味着它将来会通过审核。
    • 如果使用这种技术(在 10.12 下对我有用),最好将 valueForKey 调用包装在 @try@catch 块中。这样,如果密钥失效,您的应用就不会崩溃!
    【解决方案2】:

    在 10.10 中,NSStatusItem 有一个 button 属性,用于在不设置自定义视图的情况下获取状态项位置。

    NSStatusBarButton *statusBarButton = [myStatusItem button];
    NSRect rectInWindow = [statusBarButton convertRect:[statusBarButton bounds] toView:nil];
    NSRect screenRect = [[statusBarButton window] convertRectToScreen:rectInWindow];
    NSLog(@"%@", NSStringFromRect(screenRect));
    

    【讨论】:

    • 很棒的提示,但是 10.10 还不是很流行。 :(
    【解决方案3】:

    你可以在swift中使用statusItem.button.superview?.window?.frame

    【讨论】:

    • 在 Objective C 中转换为 statusItem.button.superview.window.frame!谢谢,这是此页面上最直接的答案。
    【解决方案4】:

    如果您在状态项上设置了自定义视图:

    NSRect statusRect = [[statusItem view] frame];
    NSLog(@"%@", [NSString stringWithFormat:@"%.1fx%.1f",statusRect.size.width, statusRect.size.height]);
    

    否则,我认为不可能使用可用且记录在案的 API。

    编辑:并入 cmets。

    【讨论】:

    • 对我不起作用。 NSStatusItem 似乎没有默认视图,因此 [statusItem view] 返回 null。
    • 正如文档所说,“返回在状态栏中接收者位置显示的自定义视图。”,而不是 NSStatusItem 的视图。
    • 这仅适用于您在状态项上设置了自定义视图。
    • 所有这些 cmets 都是正确的,但这(加上一些坐标转换,因为视图的 frame 是相对于它的超级视图,而不是屏幕)是找到状态项所在位置的唯一方法仅使用公共和记录在案的 API 在屏幕上显示。
    【解决方案5】:

    可以在没有任何私有 API 的情况下执行此操作。这是 NSScreen 的一个类别。这使用图像分析来定位菜单栏上状态项的图像。幸运的是,计算机真的很快。 :)

    只要你知道状态项的图像是什么样的,并且可以将它作为 NSImage 传入,这个方法应该可以找到它。

    适用于暗模式和常规模式。请注意,您传入的图像必须是黑色的。彩色图像可能效果不佳。

    @implementation NSScreen (LTStatusItemLocator)
    
    // Find the location of IMG on the screen's status bar.
    // If the image is not found, returns NSZeroPoint
    - (NSPoint)originOfStatusItemWithImage:(NSImage *)IMG
    {
        CGColorSpaceRef     csK = CGColorSpaceCreateDeviceGray();
        NSPoint             ret = NSZeroPoint;
        CGDirectDisplayID   screenID = 0;
        CGImageRef          displayImg = NULL;
        CGImageRef          compareImg = NULL;
        CGRect              screenRect = CGRectZero;
        CGRect              barRect = CGRectZero;
        uint8_t             *bm_bar = NULL;
        uint8_t             *bm_bar_ptr;
        uint8_t             *bm_compare = NULL;
        uint8_t             *bm_compare_ptr;
        size_t              bm_compare_w, bm_compare_h;
        BOOL                inverted = NO;
        int                 numberOfScanLines = 0;
        CGFloat             *meanValues = NULL;
    
        int                 presumptiveMatchIdx = -1;
        CGFloat             presumptiveMatchMeanVal = 999;
    
    
        // If the computer is set to Dark Mode, set the "inverted" flag
        NSDictionary *globalPrefs = [[NSUserDefaults standardUserDefaults] persistentDomainForName:NSGlobalDomain];
        id style = globalPrefs[@"AppleInterfaceStyle"];
        if ([style isKindOfClass:[NSString class]]) {
            inverted = (NSOrderedSame == [style caseInsensitiveCompare:@"dark"]);
        }
    
        screenID = (CGDirectDisplayID)[self.deviceDescription[@"NSScreenNumber"] integerValue];
    
        screenRect = CGDisplayBounds(screenID);
    
        // Get the menubar rect
        barRect = CGRectMake(0, 0, screenRect.size.width, 22);
    
        displayImg = CGDisplayCreateImageForRect(screenID, barRect);
        if (!displayImg) {
            NSLog(@"Unable to create image from display");
            CGColorSpaceRelease(csK);
            return ret; // I would normally use goto(bail) here, but this is public code so let's not ruffle any feathers
        }
    
        size_t bar_w = CGImageGetWidth(displayImg);
        size_t bar_h = CGImageGetHeight(displayImg);
    
        // Determine scale factor based on the CGImageRef we got back from the display
        CGFloat scaleFactor = (CGFloat)bar_h / (CGFloat)22;
    
        // Greyscale bitmap for menu bar
        bm_bar = malloc(1 * bar_w * bar_h);
        {
            CGContextRef bmCxt = NULL;
    
            bmCxt = CGBitmapContextCreate(bm_bar, bar_w, bar_h, 8, 1 * bar_w, csK, kCGBitmapAlphaInfoMask&kCGImageAlphaNone);
    
            // Draw the menu bar in grey
            CGContextDrawImage(bmCxt, CGRectMake(0, 0, bar_w, bar_h), displayImg);
    
            uint8_t minVal = 0xff;
            uint8_t maxVal = 0x00;
            // Walk the bitmap
            uint64_t running = 0;
            for (int yi = bar_h / 2; yi == bar_h / 2; yi++)
            {
                bm_bar_ptr = bm_bar + (bar_w * yi);
                for (int xi = 0; xi < bar_w; xi++)
                {
                    uint8_t v = *bm_bar_ptr++;
                    if (v < minVal) minVal = v;
                    if (v > maxVal) maxVal = v;
                    running += v;
                }
            }
            running /= bar_w;
            uint8_t threshold = minVal + ((maxVal - minVal) / 2);
            //threshold = running;
    
    
            // Walk the bitmap
            bm_bar_ptr = bm_bar;
            for (int yi = 0; yi < bar_h; yi++)
            {
                for (int xi = 0; xi < bar_w; xi++)
                {
                    // Threshold all the pixels. Values > 50% go white, values <= 50% go black
                    // (opposite if Dark Mode)
    
                    // Could unroll this loop as an optimization, but probably not worthwhile
                    *bm_bar_ptr = (*bm_bar_ptr > threshold) ? (inverted?0x00:0xff) : (inverted?0xff:0x00);
                    bm_bar_ptr++;
                }
            }
    
    
            CGImageRelease(displayImg);
            displayImg = CGBitmapContextCreateImage(bmCxt);
    
            CGContextRelease(bmCxt);
        }
    
    
        {
            CGContextRef bmCxt = NULL;
            CGImageRef img_cg = NULL;
    
            bm_compare_w = scaleFactor * IMG.size.width;
            bm_compare_h = scaleFactor * 22;
    
            // Create out comparison bitmap - the image that was passed in
            bmCxt = CGBitmapContextCreate(NULL, bm_compare_w, bm_compare_h, 8, 1 * bm_compare_w, csK, kCGBitmapAlphaInfoMask&kCGImageAlphaNone);
    
            CGContextSetBlendMode(bmCxt, kCGBlendModeNormal);
    
            NSRect imgRect_og = NSMakeRect(0,0,IMG.size.width,IMG.size.height);
            NSRect imgRect = imgRect_og;
            img_cg = [IMG CGImageForProposedRect:&imgRect context:nil hints:nil];
    
            CGContextClearRect(bmCxt, imgRect);
            CGContextSetFillColorWithColor(bmCxt, [NSColor whiteColor].CGColor);
            CGContextFillRect(bmCxt, CGRectMake(0,0,9999,9999));
    
            CGContextScaleCTM(bmCxt, scaleFactor, scaleFactor);
            CGContextTranslateCTM(bmCxt, 0, (22. - IMG.size.height) / 2.);
    
            // Draw the image in grey
            CGContextSetFillColorWithColor(bmCxt, [NSColor blackColor].CGColor);
            CGContextDrawImage(bmCxt, imgRect, img_cg);
    
            compareImg = CGBitmapContextCreateImage(bmCxt);
    
    
            CGContextRelease(bmCxt);
        }
    
    
    
    
        {
            // We start at the right of the menu bar, and scan left until we find a good match
            int numberOfScanLines = barRect.size.width - IMG.size.width;
    
            bm_compare = malloc(1 * bm_compare_w * bm_compare_h);
            // We use the meanValues buffer to keep track of how well the image matched for each point in the scan
            meanValues = calloc(sizeof(CGFloat), numberOfScanLines);
    
            // Walk the menubar image from right to left, pixel by pixel
            for (int scanx = 0; scanx < numberOfScanLines; scanx++)
            {
    
                // Optimization, if we recently found a really good match, bail on the loop and return it
                if ((presumptiveMatchIdx >= 0) && (scanx > (presumptiveMatchIdx + 5))) {
                    break;
                }
    
                CGFloat xOffset = numberOfScanLines - scanx;
                CGRect displayRect = CGRectMake(xOffset * scaleFactor, 0, IMG.size.width * scaleFactor, 22. * scaleFactor);
                CGImageRef displayCrop = CGImageCreateWithImageInRect(displayImg, displayRect);
    
                CGContextRef compareCxt = CGBitmapContextCreate(bm_compare, bm_compare_w, bm_compare_h, 8, 1 * bm_compare_w, csK, kCGBitmapAlphaInfoMask&kCGImageAlphaNone);
                CGContextSetBlendMode(compareCxt, kCGBlendModeCopy);
    
                // Draw the image from our menubar
                CGContextDrawImage(compareCxt, CGRectMake(0,0,IMG.size.width * scaleFactor, 22. * scaleFactor), displayCrop);
    
                // Blend mode difference is like an XOR
                CGContextSetBlendMode(compareCxt, kCGBlendModeDifference);
    
                // Draw the test image. Because of blend mode, if we end up with a black image we matched perfectly
                CGContextDrawImage(compareCxt, CGRectMake(0,0,IMG.size.width * scaleFactor, 22. * scaleFactor), compareImg);
    
                CGContextFlush(compareCxt);
    
                // Walk through the result image, to determine overall blackness
                bm_compare_ptr = bm_compare;
                for (int i = 0; i < bm_compare_w * bm_compare_h; i++)
                {
                    meanValues[scanx] += (CGFloat)(*bm_compare_ptr);
                    bm_compare_ptr++;
                }
                meanValues[scanx] /= (255. * (CGFloat)(bm_compare_w * bm_compare_h));
    
                // If the image is very dark, it matched well. If the average pixel value is < 0.07, we consider this
                // a presumptive match. Mark it as such, but continue looking to see if there's an even better match.
                if (meanValues[scanx] < 0.07) {
                    if (meanValues[scanx] < presumptiveMatchMeanVal) {
                        presumptiveMatchMeanVal = meanValues[scanx];
                        presumptiveMatchIdx = scanx;
                    }
                }
    
                CGImageRelease(displayCrop);
                CGContextRelease(compareCxt);
    
            }
        }
    
    
        // After we're done scanning the whole menubar (or we bailed because we found a good match),
        // return the origin point.
        // If we didn't match well enough, return NSZeroPoint
        if (presumptiveMatchIdx >= 0) {
            ret = CGPointMake(CGRectGetMaxX(self.frame), CGRectGetMaxY(self.frame));
            ret.x -= (IMG.size.width + presumptiveMatchIdx);
            ret.y -= 22;
        }
    
    
        CGImageRelease(displayImg);
        CGImageRelease(compareImg);
        CGColorSpaceRelease(csK);
    
        if (bm_bar) free(bm_bar);
        if (bm_compare) free(bm_compare);
        if (meanValues) free(meanValues);
    
        return ret;
    }
    
    @end
    

    【讨论】:

      【解决方案6】:

      你可以像这样破解窗口 ivar:

      @interface NSStatusItem (Hack)
      
      - (NSRect)hackFrame;
      
      @end
      
      @implementation NSStatusItem (Hack)
      
      - (NSRect)hackFrame
      {
          int objSize = class_getInstanceSize( [NSObject class] ) ;
          id * _ffWindow = (void *)self + objSize + sizeof(NSStatusBar*) + sizeof(CGFloat) ;
          NSWindow * window = *_ffWindow ;
      
          return [window frame] ;
      }
      
      @end
      

      这对于没有自定义视图的状态项很有用。

      在 Lion 上测试

      【讨论】:

      • 我在过去也使用过这个技巧。现在,10 年后,似乎有更好的方法。如果此时是正确的方式,不知道为什么要否决这个答案。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-03-12
      • 2013-10-04
      • 1970-01-01
      • 2020-10-24
      • 2014-06-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多