【问题标题】:Change mouse cursor over inactive NSWindow在非活动的 NSWindow 上更改鼠标光标
【发布时间】:2014-03-06 07:20:14
【问题描述】:

我将 NSWindow 子类化,并且我有一个 MYWindow 类实现以下方法:

-(void)resetCursorRects {
    NSImage *image = [NSImage imageNamed:@"cursor.png"];
    [image setSize:NSMakeSize(32, 32)];
    NSCursor *cursor = [[NSCursor alloc] initWithImage:image hotSpot:NSMakePoint(1, 1)];
    [super resetCursorRects];    
    [self addCursorRect:[self bounds] cursor:cursor];
}

这将改变整个窗口的光标,我将看到 cursor.png 而不是默认的鼠标指针。问题是这仅在 MYWindow 设置为关键窗口时才有效,这当然不是微不足道的。

在我的项目开始时,我只有一个主窗口,但现在我需要有两个不同的 MYWindow。两个窗口的问题是无法将两者都设置为关键窗口,因此自定义鼠标指针仅显示在活动窗口上。我需要单击另一个窗口才能使光标出现。

有没有办法解决这个问题?所以我在两个窗口上都有一个自定义光标?

编辑:尝试过 NSTrackingArea

我将此添加到我的内容视图的 init 方法中:

self.trackingArea = [[NSTrackingArea alloc] initWithRect:[self frame] options: (NSTrackingCursorUpdate | NSTrackingActiveAlways | NSTrackingMouseMoved) owner:self userInfo:nil];
[self addTrackingArea:self.trackingArea];

然后我覆盖了 cursorUpdate: 像这样:

-(void)cursorUpdate:(NSEvent *)event {
    NSLog(@"event : %@", event);
    [[NSCursor crosshairCursor] set];
}

当包含 NSImageView 派生类的 NSWindow 是关键窗口时,这会使 crosshairCursor 显示。但是,如果我将应用程序中的另一个 NSWindow 设为关键窗口,则光标会再次返回标准光标。我做错了吗?

【问题讨论】:

标签: macos cocoa mouse mouseover nswindow


【解决方案1】:

我为这个问题苦苦挣扎了很长一段时间,我认为只有一种方法可以将鼠标光标更改为非活动应用程序(在非前景窗口上)。这是 hacky 和神奇的方式。

在调用漂亮标准之前:

[[NSCursor pointingHandCursor] push];

你必须打电话:

void CGSSetConnectionProperty(int, int, int, int);
int CGSCreateCString(char *);
int CGSCreateBoolean(BOOL);
int _CGSDefaultConnection();
void CGSReleaseObj(int);
int propertyString, boolVal;

propertyString = CGSCreateCString("SetsCursorInBackground");
boolVal = CGSCreateBoolean(TRUE);
CGSSetConnectionProperty(_CGSDefaultConnection(), _CGSDefaultConnection(), propertyString, boolVal);
CGSReleaseObj(propertyString);
CGSReleaseObj(boolVal);

或者,如果您使用的是 Swift:

把这个放在你的YourApp-Bridging-Header.h:

typedef int CGSConnectionID;
CGError CGSSetConnectionProperty(CGSConnectionID cid, CGSConnectionID targetCID, CFStringRef key, CFTypeRef value);
int _CGSDefaultConnection();

然后调用:

let propertyString = CFStringCreateWithCString(kCFAllocatorDefault, "SetsCursorInBackground", 0)
CGSSetConnectionProperty(_CGSDefaultConnection(), _CGSDefaultConnection(), propertyString, kCFBooleanTrue)

【讨论】:

  • 如果您没有桥接头怎么办。你如何在 Swift 中声明这些行?
  • 我相信你只需要bridging header(为什么不呢?)因为没有其他方法(IMO)可以在 Swift 代码中导入这些函数。或者您可以将此代码放入动态库(用 C 或 Objective-C 编写)中,然后将其导入 Swift 代码中。
  • 我想没有理由不拥有一个。到目前为止,我的应用程序完全使用 Swift,但我无法完成其中的这一部分。剩下的唯一难题就是让 CGAssociateMouseAndMouseCursorPosition 也可以在后台工作。
  • 我的应用也是。我不认为在非主线程中使用 UI 方法是个好主意......但也许像 'dispatch_sync' 这样的东西可以提供帮助。
  • 这种方法还有效吗?应该包含哪个框架或库来完成这项工作?我收到以下链接器错误:_CGSCreateBoolean、_CGSCreateCString、_CGSReleaseObj at OS X 10.11
【解决方案2】:

现在我终于找到了一个可行的解决方案。我不知道这是否会在将来咬我的尾巴,但至少这在测试时似乎有效。

感谢 Wil 的示例,它让我成功了一半。但只有当我最终将它与 resetCursorRects 结合起来并且还在每个视图中使用特定光标定义了一个光标矩形时。这花了我很长时间才弄清楚,我不知道解决方案是否是最佳的(欢迎提出改进建议)

下面是最终使它对我有用的完整示例(self.cursor 是光标的一个实例)

- (void)viewWillMoveToWindow:(NSWindow *)newWindow {
    NSTrackingArea *const trackingArea = [[NSTrackingArea alloc] initWithRect:NSZeroRect options:(NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveAlways | NSTrackingInVisibleRect) owner:self userInfo:nil];
    [self addTrackingArea:trackingArea];
    [self.window invalidateCursorRectsForView:self];
}

- (void)resetCursorRects {
    [super resetCursorRects];
    [self addCursorRect:self.bounds cursor:self.cursor];
}

- (void)mouseEntered:(NSEvent *)theEvent {
    [super mouseEntered:theEvent];
    [self.cursor push];
}

- (void)mouseExited:(NSEvent *)theEvent {
    [super mouseExited:theEvent];
    [self.cursor pop];
}

【讨论】:

  • 我是 OS X 的新手,但从我读过的内容来看,addCursorRect 为整个矩形定义了一个特定的光标 - 如果您想在鼠标移动到不同区域时更改光标怎么办?假设我不能预先定义所有的矩形。我想您可以更改 self.cursor 并调用 resetCursorRects... 覆盖 windowDidBecomeKeywindowDidBecomeMain 也调用 [self.cursor push] 似乎是一种替代解决方案,尽管单击窗户。 (你甚至需要使用光标矩形的push 吗?)
【解决方案3】:

您应该能够添加一个更改光标的 NSTrackingArea,只要您不希望它在应用处于非活动状态时也发生更改(这基本上是不可能的)。


编辑:

我能够使用以下代码来实现这一点:

- (vod)someSetup;
{
    NSTrackingArea *const trackingArea = [[NSTrackingArea alloc] initWithRect:NSZeroRect options: (NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingInVisibleRect) owner:self userInfo:nil];
    [self.view addTrackingArea:trackingArea];
}

- (void)mouseEntered:(NSEvent *)theEvent;
{
    [[NSCursor IBeamCursor] push];
}

- (void)mouseExited:(NSEvent *)theEvent;
{
    [[NSCursor IBeamCursor] pop];
}

【讨论】:

  • 我猜你指的是NSTrackingArea?我现在已经尝试过了,但我无法让它工作。我只对在应用程序处于活动状态时更改光标感兴趣,但我需要它也发生在应用程序内的非活动 NSWindows 上。我会用我的发现更新我的问题。
  • 谢谢!现在这非常接近编辑后的工作,只剩下一件事要解决。现在它在我的两个 NSWindows 中正确显示了正确的光标,但是只要我单击其中一个 NSWindows,光标就会恢复为标准光标。如果我然后进出窗户,它会再次正确。但我也需要修复这个小故障。
  • 您的窗口中有哪些类型的视图?视图上还有 cursorUpdate: 方法吗?
  • 都是NSImageView的子类,我去掉了cursorUpdate:方法。在子类中,我实现 viewWillMoveToWindow (在您的示例中使用 someSetup 的内容)、mouseEntered (与您的示例完全相同)、mouseExited (与您的示例完全相同)。就是这样。
猜你喜欢
  • 1970-01-01
  • 2011-06-24
  • 1970-01-01
  • 1970-01-01
  • 2010-11-16
  • 2010-11-15
  • 1970-01-01
  • 2012-09-22
  • 1970-01-01
相关资源
最近更新 更多