14

NSStatusItemmacosxのココアについて質問があります。スニペットと呼ばれるMacアプリを見ると(http://snippetsapp.com/のムービーを参照)。ステータスバーアイコンをクリックすると、アイコンのすぐ下に完全に位置合わせされたビュー/パネルまたはウィンドウが表示されることがわかります。

NSWindow私の質問は...このアプリのようにあなたを配置する場所への位置を計算する方法は?

私は以下を試しました:

  1. サブクラスNSMenu
  2. メニューの最初の項目のビューポペリを設定します(機能しましたが十分です)
  3. これにアイコンの代わりにaddSubviewを使用すると機能しNSStatusItemましたが、20pxより高くなることはできませんでした
4

7 に答える 7

11

NSStatusItemにビューを与えてから、そのビューのウィンドウのフレームを取得します。これは技術的にはUndocumentedGoodnessとしてカウントされるため、いつか壊れても驚かないでください(たとえば、ウィンドウをオフスクリーンのままにし始めた場合など)。

「20pxより重くできなかった」とはどういう意味かわかりません。

于 2009-08-19T19:37:31.283 に答える
8

カスタムビューの手間をかけずにこれを行うために、私は次のことを試しました(それは機能します)。ステータスアイテムのアクションとして設定されているメソッド、つまりユーザーがステータスアイテムをクリックしたときに呼び出されるメソッドでは、ステータスアイテムのフレームを次の方法で取得できます。

[[[NSApp currentEvent] window] frame]

私のための御馳走を動作します

于 2012-03-29T14:18:43.123 に答える
4

とを指定するNSMenuItemと、NSWindow次のようにメニュー項目のすぐ下にウィンドウを中央に配置するポイントを取得できます。

fileprivate var centerBelowMenuItem: CGPoint {
    guard let window = window, let barButton = statusItem.button else { return .zero }
    let rectInWindow = barButton.convert(barButton.bounds, to: nil)
    let screenRect = barButton.window?.convertToScreen(rectInWindow) ?? .zero
    // We now have the menu item rect on the screen.
    // Let's do some basic math to center our window to this point.
    let centerX = screenRect.origin.x-(window.frame.size.width-barButton.bounds.width)/2
    return CGPoint(x: centerX, y: screenRect.origin.y)
}

文書化されていないAPIは必要ありません。

于 2018-04-29T15:16:14.267 に答える
3

多分私のために働く別の解決策(swift4.1):

   let yourStatusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)

   let frameOrigin = yourStatusItem.button?.window?.frame.origin
   let yourPoint = CGPoint(x: (frameOrigin?.x)!, y: (frameOrigin?.y)! - 22)


   yourWindow?.setFrameOrigin(yourPoint)
于 2018-07-05T23:15:44.327 に答える
1

このアプリはMattのMAAttachedWindowを使用しているようです。同じレイアウトと位置のサンプルアプリケーションがあります。

于 2009-08-25T00:56:28.727 に答える
0

Apple NSStatusItemクラスリファレンスから:

カスタムビューを設定すると、NSStatusItemで定義されている他のすべての外観と動作の設定が上書きされます。カスタムビューは、それ自体を描画し、マウスクリックの処理やアクションメッセージの送信などの独自の動作を提供する役割を果たします。

于 2011-11-11T08:54:52.010 に答える
0

注:少なくともNSStatusItemを見つける目的では、これを使用しないでください。

私がこれを投稿したとき、このクレイジーな画像マッチング技術は、文書化されていないAPIなしでこの問題を解決する唯一の方法でした。ここで、Oskarのソリューションを使用する必要があります。


画像分析を使用してメニューバーのステータス項目を検索する場合は、これを正確に実行するNSScreenのカテゴリを次に示します。

この方法でそれを行うのはおかしなことに思えるかもしれませんが、高速で比較的小さく、文書化されていないAPIなしでステータスアイテムを見つける唯一の方法です。

ステータスアイテムの現在の画像を渡すと、このメソッドはそれを見つける必要があります。

@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
于 2016-06-28T21:05:54.380 に答える