【问题标题】:Move NSWindow by dragging NSView that is above other views通过拖动位于其他视图上方的 NSView 来移动 NSWindow
【发布时间】:2017-12-01 15:06:01
【问题描述】:

我有一个包含 NSTableView 和 NSVisualEffectView 的 macOS 应用程序。视觉效果视图就像窗口底部的一个栏,它位于表格视图中(包含一些按钮/等)。

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

但是,当视觉效果视图在表格视图上方时,鼠标/拖动/等事件不会被注册。相反,它们被传递到表视图。我怎样才能阻止这种情况发生?

我尝试继承 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

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

感谢您抽出宝贵时间,丹。

【问题讨论】:

  • 你能不能在 NSVisualEffectView 上放一个透明的 NSView 来处理鼠标事件?作为奖励,如果这也不起作用,您就会知道事件处理问题实际上与 VEV 无关。
  • 我不知道这会导致您的具体问题,但文档警告不要覆盖 -drawRect:
  • 感谢您的帮助@Ssswift - 最后我尝试了许多不同的东西,从自定义 NSWindow 类到 Apple 提供的不同 NSWindow 类型。最后我发现,我可以让鼠标按下事件传递到窗口标题栏,所以一件事导致了另一件事,我想出了一个解决方案,但无论如何感谢您的帮助。我将在几秒钟内发布我的解决方案(未使用任何库或私有 API)。

标签: objective-c macos cocoa nsview nswindow


【解决方案1】:

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

问题

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

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

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

解决方案

解决方案涉及创建两个子类,一个用于视觉效果视图,另一个用于NSWindow。视觉效果视图的子类,只需将鼠标按下事件传递给nextResponder(可以是表格视图或窗口 - 取决于窗口标题栏的大小)。

头代码(视觉效果视图类):

#import <Cocoa/Cocoa.h>

@interface TopMainBar : NSVisualEffectView {

}

@end

实现代码(视觉效果视图类):

#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

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

因为增加了标题栏的大小,所以所有的鼠标按下事件都不会传递给窗口,也不会传递给表格视图。这使用户可以从视觉效果视图的任何部分拖动窗口。

头代码(Window类):

#import <Cocoa/Cocoa.h>

@interface CustomWindow : NSWindowController <NSWindowDelegate> {

}

// UI methods.
-(BOOL)isWindowFullScreen;

@end

实现代码(Window类):

#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

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

为此,您需要在窗口中添加一个空工具栏,您可以在界面构建器中通过将NSToolbar 对象拖放到窗口中来完成此操作。

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

结论

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

【讨论】:

  • 我仍然会避免在这里覆盖drawRect:。它可能是无害的,或者它可能会在下周破裂。它甚至不是与绘图相关的代码,所以应该没有理由从那里调用它。
  • @Ssswift 那我该如何绕过呢?
  • 我不确定您所说的“绕过”是什么意思。您已经在窗口控制器中设置了window.movableByWindowBackground。另一个标志是应该在init,或viewWillMoveToWindow,或其他一些与创建相关的方法中设置的东西,而不是绘图。
  • @Ssswift 是的,我明白了,仔细查看文档,我只能找到一个 init 方法initWithFrame,所以我改用它并省略了drawRect。 (并更新了我的答案)。
猜你喜欢
  • 2015-09-06
  • 2013-11-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多