通过拖动其他视图上方的 NSView 来移动 NSWindow

Move NSWindow by dragging NSView that is above other views

我有一个包含 NSTableView 和 NSVisualEffectView 的 macOS 应用程序。视觉效果视图就像window底部的一个栏,它在table视图中(包含一些buttons/etc..)。

无论如何,如果我想通过拖动视觉效果视图来移动 NSWindow,只有当 table 视图不在视觉效果视图下方时才有效。我希望视觉效果视图位于 table 视图上方的原因是,当用户滚动浏览 table 视图内容时,我可以获得很好的模糊效果。

但是,当视觉效果视图在table视图上方时,mouse/drag/etc事件不会被注册。相反,它们被传递到 table 视图。我怎样才能阻止这种情况发生?

我尝试子类化 NSVisualEffectView,但我尝试的一切都失败了。这是我的代码:

#import <Cocoa/Cocoa.h>

@interface BottomMainBar : NSVisualEffectView {

}

@end

实现代码如下:

#import "BottomMainBar.h"

@implementation BottomMainBar

/// DRAW RECT METHOD ///

-(void)drawRect:(NSRect)dirtyRect {
    [super drawRect:dirtyRect];

    [self setWantsLayer:YES];

    [self.window setMovableByWindowBackground:YES];

    [self setAcceptsTouchEvents:YES];

    [self registeredDraggedTypes];
}

/// OTHER METHODS ///

-(BOOL)mouseDownCanMoveWindow {
    return YES;
}

-(BOOL)acceptsFirstMouse:(NSEvent *)event {
    return YES;
}

-(void)mouseDown:(NSEvent *)event {}
-(void)mouseDragged:(NSEvent *)event {}
-(void)mouseUp:(NSEvent *)event {}
-(void)mouseEntered:(NSEvent *)event {}
-(void)mouseExited:(NSEvent *)event {}

@end

我试过都没有用,我怎样才能阻止视觉效果视图将鼠标事件传递到它下面的层?

丹,谢谢你的时间。

最后我设法找到了解决方案,幸运的是它不涉及任何库或开源代码(显然也没有私有 api)。

问题

我有一个 NSVisualEffectView 横跨我的视图控制器的宽度,它有 38 像素高。它位于我的视图控制器的顶部。它充当包含一些按钮和标签的自定义工具栏。它位于显示各种内容(图像、视频、文本等)的 NSTableView 上方。

我将视觉效果视图放在 table 视图上方,因为我想在用户滚动 table 视图时有一个很好的模糊效果。这个问题是,视觉效果视图上的鼠标按下事件被传递到 table 视图并且 NOT 整体 NSWindow。这导致用户在单击并拖动视觉效果视图时无法拖动和移动 NSWindow(因为鼠标按下事件未传递给 window)。

我注意到视觉效果 DID 的前 10px 将鼠标按下事件传递给 window 而不是 table 视图。这是因为 window 的标题栏大约有 10-15 像素高。但是我的视觉效果视图是38px高,所以我的视觉效果视图的下半部分无法移动 window.

解决方法

解决方案涉及制作两个子class,一个用于视觉效果视图,另一个用于NSWindow。视觉效果视图的 subclass 只是将鼠标按下事件传递给 nextResponder(可以是 table 视图或 window - 取决于大小window 标题栏)。

Header代码(视觉效果查看class):

#import <Cocoa/Cocoa.h>

@interface TopMainBar : NSVisualEffectView {

}

@end

实现代码(视觉效果查看class):

#import "TopMainBar.h"

@implementation TopMainBar

/// INIT WITH FRAME ///

-(id)initWithFrame:(NSRect)frameRect {

    if ((self = [super initWithFrame:frameRect])) {
        [self setWantsLayer:YES];
        [self.window setMovableByWindowBackground:YES];
    }

    return self;
}

/// MOUSE METHODS ///

-(void)mouseDown:(NSEvent *)event {
    [self.window mouseDown:event];
}

@end

window 的子class 涉及将 window 标题栏变成工具栏,这实际上增加了标题栏的大小(并且碰巧增加了它到大约 38 像素,这正是我所需要的)。理想的解决方案是能够将标题栏高度增加到任何自定义大小,但这是不可能的,因此工具栏解决方案是唯一的方法。

因为标题栏的大小增加了,所以所有鼠标按下事件都不会传递给 window 而不是 table 视图。这使用户能够从视觉效果视图的任何部分拖动 window。

Header代码(Windowclass):

#import <Cocoa/Cocoa.h>

@interface CustomWindow : NSWindowController <NSWindowDelegate> {

}

// UI methods.
-(BOOL)isWindowFullScreen;

@end

实现代码(Windowclass):

#import "CustomWindow.h"

@interface CustomWindow ()

@end

@implementation CustomWindow

/// WINDOW DID LOAD ///

-(void)windowDidLoad {
    [super windowDidLoad];

    // Ensure this window is the current selected one.
    [self.window makeKeyAndOrderFront:self];

    // Ensure the window can be moved.
    [self.window setMovableByWindowBackground:YES];

    // Set the window title bar options.
    self.window.titleVisibility = NSWindowTitleHidden;
    self.window.titlebarAppearsTransparent = YES;
    self.window.styleMask |= (NSWindowStyleMaskFullSizeContentView | NSWindowStyleMaskUnifiedTitleAndToolbar | NSWindowStyleMaskTitled);
    self.window.movableByWindowBackground = YES;
    self.window.toolbar.showsBaselineSeparator = NO;
    self.window.toolbar.fullScreenAccessoryView.hidden = YES;
    self.window.toolbar.visible = ![self isWindowFullScreen];
}

/// UI METHODS ///

-(BOOL)isWindowFullScreen {
    return (([self.window styleMask] & NSWindowStyleMaskFullScreen) == NSWindowStyleMaskFullScreen);
}

/// WINDOW METHODS ///

-(void)windowWillEnterFullScreen:(NSNotification *)notification {
    self.window.toolbar.visible = NO;
}

-(void)windowDidEnterFullScreen:(NSNotification *)notification {
    self.window.toolbar.visible = NO;
}

-(void)windowWillExitFullScreen:(NSNotification *)notification {
    self.window.toolbar.visible = YES;
}

-(void)windowDidExitFullScreen:(NSNotification *)notification {
    self.window.toolbar.visible = YES;
}

/// OTHER METHODS ///

-(BOOL)mouseDownCanMoveWindow {
    return YES;
}

@end

在自定义 window class 中,您可以看到我正在根据 window 的全屏状态更改工具栏可见性。这是为了在 window 进入全屏模式时停止出现标题栏并覆盖我的自定义视觉效果视图。

为了使其正常工作,您需要向 window 添加一个空工具栏,您可以在界面生成器中通过拖放 NSToolbar object , 到你的 window.

确保将 window 连接到 window 委托,否则将不会调用全屏委托方法。

结论

此解决方案涉及通过将标题栏更改为工具栏来增加标题栏的大小。从视觉效果视图 class 传递的鼠标按下事件随后由 window(不是它后面的任何其他视图)读取,因此 window 可以移动。