【发布时间】:2010-10-25 15:02:36
【问题描述】:
在 Mac 上的 Cocoa 中,我想检测属于另一个应用程序的窗口何时移动、调整大小或重新绘制。我该怎么做?
【问题讨论】:
标签: objective-c cocoa macos
在 Mac 上的 Cocoa 中,我想检测属于另一个应用程序的窗口何时移动、调整大小或重新绘制。我该怎么做?
【问题讨论】:
标签: objective-c cocoa macos
您需要使用位于 ApplicationServices 框架内的可访问性 API,它们是纯 C 语言。例如:
首先你创建一个应用对象:
AXUIElementRef app = AXUIElementCreateApplication( targetApplicationProcessID );
然后你从这里得到窗口。您可以请求窗口列表并枚举,也可以获取最前面的窗口(在 AXAttributeConstants.h 中查找您要使用的所有属性名称)。
AXUIElementRef frontWindow = NULL;
AXError err = AXUIElementCopyAttributeValue( app, kAXMainWindowAttribute, &frontWindow );
if ( err != kAXErrorSuccess )
// it failed -- maybe no main window (yet)
现在您可以在此窗口的属性更改时通过 C 回调函数请求通知。这是一个四步过程:
首先你需要一个回调函数来接收通知:
void MyAXObserverCallback( AXObserverRef observer, AXUIElementRef element,
CFStringRef notificationName, void * contextData )
{
// handle the notification appropriately
// when using ObjC, your contextData might be an object, therefore you can do:
SomeObject * obj = (SomeObject *) contextData;
// now do something with obj
}
接下来您需要一个 AXObserverRef,它管理回调例程。这需要与您在上面创建“app”元素时使用的进程 ID 相同:
AXObserverRef observer = NULL;
AXError err = AXObserverCreate( applicationProcessID, MyObserverCallback, &observer );
if ( err != kAXErrorSuccess )
// handle the error
得到您的观察者后,下一步是请求通知某些事情。有关完整列表,请参见 AXNotificationConstants.h,但对于窗口更改,您可能只需要这两个:
AXObserverAddNotification( observer, frontWindow, kAXMovedNotification, self );
AXObserverAddNotification( observer, frontWindow, kAXResizedNotification, self );
请注意,最后一个参数将假定的“self”对象作为 contextData 传递。这不会被保留,所以当这个对象消失时调用AXObserverRemoveNotification 很重要。
获得观察者并添加通知请求后,您现在希望将观察者附加到您的运行循环,以便可以异步方式(或实际上)发送这些通知:
CFRunLoopAddSource( [[NSRunLoop currentRunLoop] getCFRunLoop],
AXObserverGetRunLoopSource(observer),
kCFRunLoopDefaultMode );
AXUIElementRefs 是 CoreFoundation 风格的对象,所以你需要使用CFRelease() 来干净地处理它们。举个例子,为了整洁,一旦你获得了 frontWindow 元素,你就可以使用CFRelease(app),因为你不再需要这个应用程序了。
关于 Garbage-Collection 的说明:要将 AXUIElementRef 保留为成员变量,请按如下方式声明:
__strong AXUIElementRef frontWindow;
这指示垃圾收集器跟踪对它的引用。分配时,为了与 GC 和非 GC 兼容,请使用:
frontWindow = (AXUIElementRef) CFMakeCollectable( CFRetain(theElement) );
【讨论】:
进一步研究出现了“石英显示服务”
我需要的有趣功能是 CGRegisterScreenRefreshCallback。
【讨论】: