2011-03-24 28 views
8

在將Cocoa中的狀態欄添加到狀態欄後,可以獲得NSStatusItem的框架嗎?當我的應用程序啓動時,我將一個項目添加到系統狀態欄,並想知道它的位置,可能。如何獲取NSStatusItem的框架

回答

1

如果已設置自定義視圖上的狀態項:

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

否則,我不認爲這是可能的使用可用的和記錄的API。

編輯:註冊意見。

+2

沒有爲我工作。 NSStatusItem似乎沒有默認視圖,因此[statusItem視圖]返回null。 – blutfink 2013-01-07 00:35:41

+0

正如文檔所述,「返回狀態欄中接收器位置顯示的自定義視圖」,而不是NSStatusItem的視圖。 – 2013-02-13 04:50:32

+1

只有在狀態項目上設置了自定義視圖時,此功能纔有效。 – Fabian 2013-11-10 01:01:22

-3

你能砍窗口伊娃這樣的:

@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 

這是一個沒有自定義視圖狀態項很有用。

測試在獅子

25

以下似乎工作 - 我已經看到了iOS應用程序類似的解決方案,並推測他們,因爲你仍然在使用標準SDK方法允許提交給應用程序商店。

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

這可以工作,並且在您尚未設置狀態項目的自定義視圖時非常有用。 – Fabian 2013-11-10 01:21:15

+1

這假設NSStatusItem(假設這是'statusBarItem'的)是KVC可以用作'window'屬性的東西。這並不保證是這種情況。如果/當這種情況消失,你會得到一個異常(不是KVC兼容的)。我也不會指望這個通過App Store審查,如果他們還沒有,他們可能有一天會開始檢查你如何使用KVC(尋找這樣的用法,你在哪裏訪問私有方法/ ivars)。 – 2014-07-01 04:07:45

+1

這是一個私人的API調用,可能會讓你從App Store審查過程中被拒絕嗎? – 2014-07-28 01:04:50

24

隨着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)); 
+0

棒極了,但10.10還不是很受歡迎。 :( – 2014-07-28 01:02:46

0

沒有任何私人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