优雅地获取并重新设置后台应用程序焦点以进行对话

Ale*_*hol 9 cocoa objective-c nswindow

我正在开发一个小型Mac应用程序,它通常会在后台无形地运行.但是,当用户在其桌面或Finder中的其他位置重命名文件时,应用程序的主要功能将发挥作用.发生这种情况时,我想提供一个类似于用户通过Finder更改文件扩展名时出现的对话框.因为这需要我的应用程序获得最前面的焦点(而不是Finder),所以当用户在我的对话框中点击"OK"时,我想将Finder作为最前面的应用程序返回.

我目前正在使用Apple的Process Manager功能SetFrontProcessWithOptions(),但在以下场景中遇到了麻烦:

  • 用户在其工作区中的某个位置打开Finder窗口
  • 然后用户点击他们的桌面,使窗口失焦.
  • 用户在其桌面上重命名文件
  • 我的应用程序使自己专注于使用 SetFrontProcessWithOptions()
  • 用户点击OK对话框,我的应用程序使用FinderSetFrontProcessWithOptions()
  • 当Finder重新聚焦时,它会聚焦用户之前打开的窗口,尽管事实上当Finder之前最前面时它没有聚焦.

如果在重命名桌面上的文件之前在其他空间中打开Finder窗口,这会非常烦人:在这种情况下,在对话框中点击"确定"会导致Finder自动切换空格并返回窗口.

这只是因为SetFrontProcessWithOptions()函数的性质,它只能聚焦给定应用程序的窗口.由于桌面显然不算作窗口,因此该功能会找到另一个要关注的窗口,尽管用户以前没有关注该窗口.

如果有人对如何进行某种基于对话的事情有更好的想法,那将是很好的,甚至可能根本不需要聚焦和不专注Finder.

编辑:我发现在大多数情况下修复此行为有点丑陋的方法,但它涉及脚本桥,并且在重命名其中的项目时它不会重新聚焦桌面.这是我执行此操作的代码:

FinderApplication * app = [SBApplication applicationWithBundleIdentifier:@"com.apple.finder"];
if (!app || ![app isRunning]) {
    SetFrontProcessWithOptions(&processSerial, kSetFrontProcessFrontWindowOnly);
    return;
}
SBElementArray * selArray = app.selection.get;
if ([selArray count] == 0) {
    SetFrontProcessWithOptions(&processSerial, kSetFrontProcessFrontWindowOnly);
    return;
} else {
    FinderWindow * window = [[[selArray objectAtIndex:0] container].get containerWindow].get;
    if ([window isKindOfClass:NSClassFromString(@"FinderFinderWindow")]) {
        SetFrontProcessWithOptions(&processSerial, kSetFrontProcessFrontWindowOnly);
    } else {
        // TODO: this is where I'd insert code to select the item
        // on the desktop...
    }
}
Run Code Online (Sandbox Code Playgroud)

Ken*_*ses 2

您是否考虑过简单地调用-[NSApp hide:nil]?也就是说,让系统关心如何重新激活以前活动的应用程序,并确保您的应用程序不再活动。

顺便说一句,您观察到的行为(即 Finder 在接收到活动状态时激活窗口而不是桌面)与您使用 Command-Tab 离开然后返回时发生的情况相同。或者关闭 Command-Tab,然后隐藏您切换到的应用程序。因此,这可能被认为是正确的行为。另一方面,在我的测试中,当 Finder 专注于桌面但在不同空间上有一个窗口时,隐藏前台应用程序不会切换到其他空间。它会执行您想要的操作:激活 Finder,并将焦点集中在桌面上。

最后,-[NSRunningApplication activateWithOptions:]SetFrontProcessWithOptions().


如果您真正想要的是一种运行相当于

tell application "Finder"
    activate
    select the desktop's window
end tell
Run Code Online (Sandbox Code Playgroud)

从 Objective-C 中,我看到了两种选择。首先,使用 Scripting Bridge API:

[self.finder activate];
[(FinderWindow*)self.finder.desktop.containerWindow select];
Run Code Online (Sandbox Code Playgroud)

(我假设您已经掌握了使用 Scripting Bridge 和 Finder 的基础知识。如果没有,Apple 有一个ScriptingBridgeFinder 示例。出于某种原因,它位于旧文档部分。)

其次,你可以使用NSAppleScript

NSAppleScript* s = [[NSAppleScript alloc] initWithSource:@"tell application \"Finder\"\nactivate\nselect the desktop's window\nend tell"];
NSDictionary* err = nil;
if (![s executeAndReturnError:&err])
    /* handle error */;
Run Code Online (Sandbox Code Playgroud)

不过,在我对 Snow Leopard 的测试中,这两种方法都无法在以桌面为中心的情况下切换到 Finder。但是,您的 AppleScript 代码也没有从 AppleScript 编辑器运行。