【问题标题】:Making a window pop in and out of the edge of the screen使窗口弹出和弹出屏幕边缘
【发布时间】:2010-01-03 08:49:00
【问题描述】:

我正在尝试用 Objective-C 为我的 Mac 重新编写一个用于 Windows 的应用程序,并且我希望能够做一些类似于 Mac 的热门角落的事情。如果我将鼠标移动到屏幕的左侧,它将使一个窗口可见,如果我将它移到窗口位置之外,则窗口将再次隐藏。 (窗口将被推到屏幕的左侧)。

有谁知道我在哪里可以找到一些演示代码(或参考)来说明如何执行此操作,或者至少如何判断鼠标在哪里,即使当前应用程序不在顶部。 (不知道怎么写,太习惯于 Windows 世界了)。

谢谢

-布拉德

【问题讨论】:

  • 这听起来更像是 Quicksilver 的书架和剪贴板历史记录以及(可选)Adium 的联系人列表之类的窗口,当鼠标碰到该边缘时,它们可以在屏幕的一侧进出。你是这么想的吗?
  • 是的,我就是这么想的。

标签: objective-c cocoa macos


【解决方案1】:

您将希望在屏幕边缘实现一个不可见的窗口,并设置窗口顺序,使其始终位于顶部。然后,您可以在此窗口中侦听鼠标移动的事件。

要将窗口设置为不可见并位于顶部,请使用如下调用来创建窗口子类:

[self setBackgroundColor:[NSColor clearColor]];
[self setExcludedFromWindowsMenu:YES];
[self setCanHide:NO];
[self setLevel:NSScreenSaverWindowLevel];
[self setAlphaValue:0.0f];
[self setOpaque:NO];
[self orderFrontRegardless];

然后,打开鼠标移动事件,

[self setAcceptsMouseMovedEvents:YES];

将导致窗口调用:

- (void)mouseMoved:(NSEvent *)theEvent
{
   NSLog(@"mouse moved into invisible window.");
}

所以希望这足以让你开始。

-肯

【讨论】:

  • 难题的另一部分是使窗口真正弹出和弹出。您需要将窗口内容视图的直接子视图放入另一个视图,并使 that 成为内容视图。将 view-within-the-content-view 的自动调整大小掩码设置为不调整大小并将其原点装订到内容视图的左边缘(在您的示例中)。要隐藏窗口,请将窗口大小调整为 1 像素宽。要显示它,请恢复其适当的大小。如果您希望窗口可调整大小,则需要在显示/隐藏窗口后切换视图的自动调整大小掩码。
  • 之所以不简单地将窗口移入和移出是因为用户可能在窗口将移出的区域中有另一个屏幕。然后,您没有隐藏窗口,只是将其移动到另一个屏幕——这不是用户想要的。 (至于在这种情况下用户如何显示窗口,用户可以让窗口向上或向下越过小屏幕的边缘,让他有一些东西可以点击。)
  • 一些我忘记覆盖窗口的东西:[self setHasShadow:NO];和 [self setIgnoresMouseEvents:NO];所以你的隐形窗口实际上并没有阻止点击。我 99% 确定您仍然会收到鼠标移动事件。
  • 补充一下 Peter 所说的,Cocoa 不允许窗口完全脱离屏幕。这就是为什么你不能只是简单地滑动它。然而,有一个 hacky 解决方法。 Cocoa 在 NSWindow 的 constrainFrameRect:toScreen: 中做了限制。因此,如果您覆盖此例程,则可以使窗口离开屏幕。这可能会使操作更容易,而无需实际调整窗口大小。不过不确定。
  • 谢谢大家的意见...我很感激。
【解决方案2】:

这是 AutoHidingWindow - SlidingWindow 的子类,当鼠标碰到屏幕边缘时会弹出。欢迎反馈。

@interface ActivationWindow : NSWindow
{
    AutoHidingWindow *_activationDelegate;
    NSTrackingArea *_trackingArea;
}

- (ActivationWindow*)initWithDelegate:(AutoHidingWindow*)activationDelegate;

@property (assign) AutoHidingWindow *activationDelegate;
@property (retain) NSTrackingArea *trackingArea;

- (void)adjustWindowFrame;
- (void)adjustTrackingArea;

@end

@interface AutoHidingWindow ()

- (void)autoShow;
- (void)autoHide;

@end 


@implementation AutoHidingWindow

- (id)initWithContentRect:(NSRect) contentRect 
                styleMask:(unsigned int) styleMask 
                  backing:(NSBackingStoreType) backingType 
                    defer:(BOOL) flag
{

    if ((self = [super initWithContentRect:contentRect
                                 styleMask:NSBorderlessWindowMask 
                                   backing:backingType
                                     defer:flag])) {
        _activationWindow = [[ActivationWindow alloc] initWithDelegate:self];
    }

    return self;
}

@synthesize activationWindow = _activationWindow;

- (void)dealloc
{
    [_activationWindow release], _activationWindow = nil;

    [super dealloc];
}

- (void)makeKeyAndOrderFront:(id)sender
{
    [super makeKeyAndOrderFront:sender];

}

- (void)autoShow
{
    [self makeKeyAndOrderFront:self];

    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(autoHide) object:nil];
    [self performSelector:@selector(autoHide) withObject:nil afterDelay:2];
}

- (void)autoHide
{
    NSPoint mouseLocation = [NSEvent mouseLocation];
    NSRect windowFrame = [self frame];

    if (NSPointInRect(mouseLocation, windowFrame)) {
        [self performSelector:@selector(autoHide) withObject:nil afterDelay:2];
    }
    else {
        [self orderOut:self];
    }
}

@end


@implementation ActivationWindow 

- (ActivationWindow*)initWithDelegate:(AutoHidingWindow*)activationDelegate
{   
    if ((self = [super initWithContentRect:[[NSScreen mainScreen] frame]
                                 styleMask:NSBorderlessWindowMask
                                   backing:NSBackingStoreBuffered
                                     defer:NO]) != nil) {
        _activationDelegate = activationDelegate;

        [self setBackgroundColor:[NSColor clearColor]];
        [self setExcludedFromWindowsMenu:YES];
        [self setCanHide:NO];
        [self setHasShadow:NO];
        [self setLevel:NSScreenSaverWindowLevel];
        [self setAlphaValue:0.0];
        [self setIgnoresMouseEvents:YES];
        [self setOpaque:NO];
        [self orderFrontRegardless];

        [self adjustWindowFrame];
        [self.activationDelegate addObserver:self
                                 forKeyPath:@"slidingEdge"
                                    options:0
                                    context:@"slidingEdge"];
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(screenParametersChanged:) 
                                                     name:NSApplicationDidChangeScreenParametersNotification 
                                                   object:nil];     
    }

    return self;
}

@synthesize activationDelegate = _activationDelegate;
@synthesize trackingArea = _trackingArea;

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if ([@"slidingEdge" isEqual:context]) {
        [self adjustTrackingArea];
    }
    else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}


- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];

    [self.activationDelegate removeObserver:self forKeyPath:@"slidingEdge"];
    _activationDelegate = nil;

    [_trackingArea release], _trackingArea = nil;

    [super dealloc];
}

- (void)screenParametersChanged:(NSNotification *)notification
{
    [self adjustWindowFrame];
}

- (void)adjustWindowFrame
{
    NSScreen *mainScreen = [NSScreen mainScreen];
    CGFloat menuBarHeight = [NSMenuView menuBarHeight];
    NSRect windowFrame = [mainScreen frame];

    windowFrame.size.height -= menuBarHeight;

    [self setFrame:windowFrame display:NO];
    [self adjustTrackingArea];
}

- (void)adjustTrackingArea
{
    NSView *contentView = [self contentView];
    NSRect trackingRect = contentView.bounds;   
    CGRectEdge slidingEdge = self.activationDelegate.slidingEdge;
    CGFloat trackingRectSize = 2.0;

    switch (slidingEdge) {
        case CGRectMaxXEdge:
            trackingRect.origin.x = trackingRect.origin.x + trackingRect.size.width - trackingRectSize;
            trackingRect.size.width = trackingRectSize;
            break;

        case CGRectMaxYEdge:
            trackingRect.origin.y = trackingRect.origin.y + trackingRect.size.height - trackingRectSize;
            trackingRect.size.height = trackingRectSize;
            break;

        case CGRectMinXEdge:
            trackingRect.origin.x = 0;
            trackingRect.size.width = trackingRectSize;
            break;

        case CGRectMinYEdge:
        default:
            trackingRect.origin.y = 0;
            trackingRect.size.height = trackingRectSize;
    }


    NSTrackingAreaOptions options =
    NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved |
    NSTrackingActiveAlways |
    NSTrackingEnabledDuringMouseDrag;

    NSTrackingArea *trackingArea = self.trackingArea;

    if (trackingArea != nil) {
        [contentView removeTrackingArea:trackingArea];
    }

    trackingArea = [[NSTrackingArea alloc] initWithRect:trackingRect
                                                options:options
                                                  owner:self
                                               userInfo:nil];

    [contentView addTrackingArea:trackingArea];

    self.trackingArea = [trackingArea autorelease];
}

- (void)mouseEntered:(NSEvent *)theEvent
{
    [self.activationDelegate autoShow];
}


- (void)mouseMoved:(NSEvent *)theEvent
{
    [self.activationDelegate autoShow];
}

- (void)mouseExited:(NSEvent *)theEvent
{
}

@end

【讨论】:

    【解决方案3】:

    您可以看看我们在 Visor 项目中是如何做到的: http://github.com/binaryage/visor/blob/master/src/Visor.m#L1025-1063

    【讨论】:

    • 这个项目发生了什么?
    • 链接失效。看不到相关性。
    【解决方案4】:

    这就是我想出的。感谢 Peter 提供上述提示。

       @interface SlidingWindow : NSWindow
        {
            CGRectEdge _slidingEdge;
            NSView *_wrapperView;
        }
    
    
        @property (nonatomic, assign) CGRectEdge slidingEdge;
        @property (nonatomic, retain) NSView *wrapperView;
    
        -(id)initWithContentRect:(NSRect) contentRect 
                       styleMask:(unsigned int) styleMask 
                         backing:(NSBackingStoreType) backingType 
                           defer:(BOOL) flag;
    
        - (NSView*)wrapperViewWithFrame:(NSRect)bounds;
    
        - (BOOL)mayOrderOut;
    
        @end
    
        @interface SlidingWindow ()
    
        - (void)adjustWrapperView;
    
        - (void)setWindowWidth:(NSNumber*)width;
        - (void)setWindowHeight:(NSNumber*)height;
    
        @end
    
    
        @implementation SlidingWindow
    
    
    @synthesize slidingEdge = _slidingEdge;
    @synthesize wrapperView = _wrapperView;
    
    
    - (id)initWithContentRect:(NSRect) contentRect 
                    styleMask:(unsigned int) styleMask 
                      backing:(NSBackingStoreType) backingType 
                        defer:(BOOL) flag
    {
    
        if ((self = [super initWithContentRect:contentRect
                                     styleMask:NSBorderlessWindowMask 
                                       backing:backingType
                                         defer:flag])) {
            /* May want to setup some other options, 
             like transparent background or something */
    
            [self setSlidingEdge:CGRectMaxYEdge];
            [self setHidesOnDeactivate:YES];
            [self setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces];
        }
    
        return self;
    }
    
    - (NSView*)wrapperViewWithFrame:(NSRect)bounds
    {
        return [[[NSView alloc] initWithFrame:bounds] autorelease];
    }
    
    - (void)adjustWrapperView
    {
        if (self.wrapperView == nil) {
            NSRect frame = [self frame];
            NSRect bounds = NSMakeRect(0, 0, frame.size.width, frame.size.height);
            NSView *wrapperView = [self wrapperViewWithFrame:bounds];
            NSArray *subviews =  [[[[self contentView] subviews] copy] autorelease];
    
            for (NSView *view in subviews) {
                [wrapperView addSubview:view];
            }
    
            [wrapperView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
            [[self contentView] addSubview:wrapperView];
    
            self.wrapperView = wrapperView;
        }
    
        switch (self.slidingEdge) {
            case CGRectMaxXEdge:
                [self.wrapperView setAutoresizingMask:(NSViewHeightSizable | NSViewMaxXMargin)];
                break;
    
            case CGRectMaxYEdge:
                [self.wrapperView setAutoresizingMask:(NSViewWidthSizable | NSViewMaxYMargin)];
                break;
    
            case CGRectMinXEdge:
                [self.wrapperView setAutoresizingMask:(NSViewHeightSizable | NSViewMinXMargin)];
                break;
    
            case CGRectMinYEdge:
            default:
                [self.wrapperView setAutoresizingMask:(NSViewWidthSizable | NSViewMinYMargin)];
        }
    }
    
    - (void)makeKeyAndOrderFront:(id)sender
    {
        [self adjustWrapperView];
    
        if ([self isVisible]) {
            [super makeKeyAndOrderFront:sender];
        }
        else {
            NSRect screenRect = [[NSScreen menubarScreen] visibleFrame];
            NSRect windowRect = [self frame];
    
            CGFloat x;
            CGFloat y;
            NSRect startWindowRect;
            NSRect endWindowRect;
    
            switch (self.slidingEdge) {
                case CGRectMinXEdge:
                    x = 0;
                    y = (screenRect.size.height - windowRect.size.height) / 2 + screenRect.origin.y;
                    startWindowRect = NSMakeRect(x - windowRect.size.width, y, 0, windowRect.size.height);
                    break;
    
                case CGRectMinYEdge:
                    x = (screenRect.size.width - windowRect.size.width) / 2 + screenRect.origin.x;
                    y = 0;
                    startWindowRect = NSMakeRect(x, y - windowRect.size.height, windowRect.size.width, 0);
                    break;
    
                case CGRectMaxXEdge:
                    x = screenRect.size.width - windowRect.size.width + screenRect.origin.x;
                    y = (screenRect.size.height - windowRect.size.height) / 2 + screenRect.origin.y;
                    startWindowRect = NSMakeRect(x + windowRect.size.width, y, 0, windowRect.size.height);
                    break;
    
                case CGRectMaxYEdge:
                default:
                    x = (screenRect.size.width - windowRect.size.width) / 2 + screenRect.origin.x;
                    y = screenRect.size.height - windowRect.size.height + screenRect.origin.y;
                    startWindowRect = NSMakeRect(x, y + windowRect.size.height, windowRect.size.width, 0);
            }
    
            endWindowRect = NSMakeRect(x, y, windowRect.size.width, windowRect.size.height);
    
            [self setFrame:startWindowRect display:NO animate:NO];
    
            [super makeKeyAndOrderFront:sender];
    
            [self setFrame:endWindowRect display:YES animate:YES];
    
            [self performSelector:@selector(makeResizable)
                       withObject:nil
                       afterDelay:1];
        }
    }
    
    - (void)makeResizable
    {
        NSView *wrapperView = self.wrapperView;
        NSRect frame = [self frame];
        NSRect bounds = NSMakeRect(0, 0, frame.size.width, frame.size.height);
    
        [wrapperView setFrame:bounds];
        [wrapperView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
    }
    
    - (void)orderOut:(id)sender
    {
        [self adjustWrapperView];
    
        NSRect startWindowRect = [self frame];
        NSRect endWindowRect;
    
        switch (self.slidingEdge) {
            case CGRectMinXEdge:
                endWindowRect = NSMakeRect(startWindowRect.origin.x, 
                                           startWindowRect.origin.y,
                                           0,
                                           startWindowRect.size.height);
                break;
    
            case CGRectMinYEdge:
                endWindowRect = NSMakeRect(startWindowRect.origin.x, 
                                           startWindowRect.origin.y, 
                                           startWindowRect.size.width, 
                                           0);
                break;
    
            case CGRectMaxXEdge:
                endWindowRect = NSMakeRect(startWindowRect.origin.x + startWindowRect.size.width, 
                                           startWindowRect.origin.y,
                                           0,
                                           startWindowRect.size.height);
                break;
    
            case CGRectMaxYEdge:
            default:
                endWindowRect = NSMakeRect(startWindowRect.origin.x, 
                                           startWindowRect.origin.y + startWindowRect.size.height, 
                                           startWindowRect.size.width, 
                                           0);
        }
    
        [self setFrame:endWindowRect display:YES animate:YES];
    
        switch (self.slidingEdge) {
            case CGRectMaxXEdge:
            case CGRectMinXEdge:
                if (startWindowRect.size.width > 0) {
                    [self performSelector:@selector(setWindowWidth:)
                               withObject:[NSNumber numberWithDouble:startWindowRect.size.width]
                               afterDelay:0];
                }
                break;
    
            case CGRectMaxYEdge:
            case CGRectMinYEdge:
            default:
                if (startWindowRect.size.height > 0) {
                    [self performSelector:@selector(setWindowHeight:)
                               withObject:[NSNumber numberWithDouble:startWindowRect.size.height]
                               afterDelay:0];
                }
        }
    
        [super orderOut:sender];
    }
    
    - (void)setWindowWidth:(NSNumber*)width
    {
        NSRect startWindowRect = [self frame];
        NSRect endWindowRect = NSMakeRect(startWindowRect.origin.x, 
                                          startWindowRect.origin.y, 
                                          [width doubleValue],
                                          startWindowRect.size.height);
    
        [self setFrame:endWindowRect display:NO animate:NO];    
    }
    
    - (void)setWindowHeight:(NSNumber*)height
    {
        NSRect startWindowRect = [self frame];
        NSRect endWindowRect = NSMakeRect(startWindowRect.origin.x, 
                                          startWindowRect.origin.y, 
                                          startWindowRect.size.width, 
                                          [height doubleValue]);
    
        [self setFrame:endWindowRect display:NO animate:NO];    
    }
    
    - (void)resignKeyWindow
    {
        [self orderOut:self];
    
        [super resignKeyWindow];
    }
    
    - (BOOL)canBecomeKeyWindow
    {
        return YES;
    }
    
    - (void)performClose:(id)sender
    {
        [self close];
    }
    
    - (void)dealloc
    {
        [_wrapperView release], _wrapperView = nil;
    
        [super dealloc];
    }
    
    @end
    
    
    @implementation NSScreen (MenubarScreen)
    
    + (NSScreen*)menubarScreen
    {
        NSArray *screens = [self screens];
    
        if ([screens count] > 0) {
            return [screens objectAtIndex:0];
        }
    
        return nil;
    }
    @end
    

    【讨论】:

    • 刚刚注意到我在每个地方都混合了最小值和最大值,但在 -adjustWrapperView 中
    • @PierreBernard 您是否修复了答案中的最小值和最大值,或者它仍然是向后的.. 另外,这个@implementation 没有@interface,它不是完全正确 i> 明确其中需要包含的内容……例如,没有定义任何 slidingEdge,等等。
    • @alexgray 我现在用正确的最小/最大值更新了代码
    • 酷。我唯一需要注意的是 + (NSScreen*)menubarScreen 的类别。我想说这不是必需的,因为您可以直接调用 [NSScreen mainScreen],不是吗?
    • 来自文档:“主屏幕不一定是包含菜单栏的同一屏幕,或者其原点位于 (0, 0)。主屏幕是指包含以下窗口的屏幕当前正在接收键盘事件。它是主屏幕,因为它是用户最有可能与之交互的屏幕。"
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-29
    相关资源
    最近更新 更多