在非活动状态下更改NSWindow上的鼠标指针

8

我已经创建了一个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成为关键窗口时,这将使十字准线显示出来。但是如果我让应用程序中的另一个NSWindow成为关键窗口,则光标会再次返回标准光标。我做错了什么吗?


相关问题 → https://dev59.com/8b_qa4cB1Zd3GeqPBSVK - tyirvine
3个回答

6

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

在调用标准函数之前:

[[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中声明这些行? - MAH
我认为你只需要有一个“桥接头文件”(为什么不呢?)因为在我的看法中,没有其他方法可以在Swift代码中导入这些函数。或者你可以将这段代码放在一个动态库中(用C或Objective-C编写),然后在Swift代码中导入它。 - Valentin Shergin
没有理由不拥有一个我想。我的应用程序到目前为止完全使用Swift编写,但我无法完成其中的一部分。谜题中唯一剩下的部分是使CGAssociateMouseAndMouseCursorPosition在后台也能正常工作。 - MAH
我的应用程序也是如此。我认为在非主线程中使用UI方法不是一个好主意...但是也许像“dispatch_sync”这样的东西可以帮助解决问题。 - Valentin Shergin
是的,我大部分同意,但有一些例外情况是完全必要的。例如,如果您的应用程序旨在在所有其他内容上方绘制清晰窗口,而不会影响其他任何内容,则可以通过使用CGEventTap和此处提到的hack的组合来实现。否则,您就没有机会了。 - MAH
1
这种方法还有效吗?应该包含哪个框架或库才能使其正常工作?我在OS X 10.11上遇到了链接器错误:_CGSCreateBoolean,_CGSCreateCString,_CGSReleaseObj。 - Jurlie

4
只要您不需要在应用程序处于非活动状态时也改变光标,就可以添加更改光标的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上也发生。我将在我的问题中更新我的发现。 - www.jensolsson.se
谢谢!现在经过编辑,这已经非常接近工作了,只剩下一个问题需要解决。 现在它可以在我的两个NSWindows中正确显示光标,但是一旦我单击其中一个NSWindows,光标就会恢复为标准光标。如果我进出窗口,它会再次正确。但我还需要修复这个小故障。 - www.jensolsson.se
你的窗口中有哪些视图?你的视图上还有cursorUpdate方法吗? - Wil Shipley
它们都是NSImageView的子类,我删除了cursorUpdate:方法。在子类中,我实现了viewWillMoveToWindow(使用你的示例中的someSetup内容),mouseEntered(与你的示例完全相同),mouseExited(与你的示例完全相同)。就是这样。 - www.jensolsson.se

4
现在我终于找到了一个有效的解决方法。我不知道这是否会在未来给我带来麻烦,但至少在测试时似乎可以正常工作。
感谢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吗?) - sqweek

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接