在Mac上的Cocoa中,我想检测何时移动,调整大小或重新绘制属于另一个应用程序的窗口。我怎么能这样做?
答案 0 :(得分:43)
您需要使用位于ApplicationServices框架内的可访问性API,它们是plain-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
非常重要。
有了观察者并添加了通知请求后,您现在想要将观察者附加到您的runloop,这样您就可以以异步方式(或者实际上)发送这些通知:
CFRunLoopAddSource( [[NSRunLoop currentRunLoop] getCFRunLoop],
AXObserverGetRunLoopSource(observer),
kCFRunLoopDefaultMode );
AXUIElementRef
是CoreFoundation风格的对象,因此您需要使用CFRelease()
来彻底处理它们。例如,对于清洁度,一旦获得frontWindow元素,就会使用CFRelease(app)
,因为您将不再需要该应用程序。
关于垃圾收集的注意事项:要将AXUIElementRef保留为成员变量,请将其声明为:
__strong AXUIElementRef frontWindow;
这指示垃圾收集器跟踪对它的引用。分配时,为了与GC和非GC兼容,请使用:
frontWindow = (AXUIElementRef) CFMakeCollectable( CFRetain(theElement) );
答案 1 :(得分:3)
进一步研究出现了“Quartz Display Services”
我需要的有趣功能是CGRegisterScreenRefreshCallback。