【问题标题】:Gracefully gain and resign background app focus for dialog优雅地获得和退出后台应用程序焦点以进行对话
【发布时间】:2012-06-08 14:29:04
【问题描述】:

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

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

  • 用户在其工作区的某处打开 Finder 窗口
  • 然后用户单击他们的桌面,使窗口失去焦点。
  • 用户重命名桌面上的文件
  • 我的应用程序使用SetFrontProcessWithOptions() 使自己聚焦
  • 用户在对话框中点击OK,我的应用使用SetFrontProcessWithOptions()聚焦Finder
  • 当 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...
    }
}

【问题讨论】:

  • 您是否考虑过使用 AppleScript 在 Finder 的上下文中显示警报,而不是完全切换您的应用程序?
  • 亚历克斯,你还在吗?

标签: objective-c cocoa nswindow


【解决方案1】:

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

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

最后,-[NSRunningApplication activateWithOptions:]SetFrontProcessWithOptions() 的现代替代品。


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

的方法
tell application "Finder"
    activate
    select the desktop's window
end tell

在 Objective-C 中,我看到了两个选项。首先,使用 Scripting Bridge API:

[self.finder activate];
[(FinderWindow*)self.finder.desktop.containerWindow select];

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

其次,你可以使用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 */;

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

【讨论】:

  • 肯,我很清楚你描述的行为。网络上的其他人也对这种奇怪的行为发表了评论。为什么 [NSApp hide] 会有所帮助?它仍然是一样的——任何打开的窗口都会被激活。我没有问题切换进程,我(以及亚历克斯)对桌面的行为有问题,没有重新获得焦点。因为,对于我的应用程序来说,Finder 在我的应用程序被激活之前和之后具有相同的焦点是至关重要的。您用 Cmd-Tab 行为描述的内容也不足为奇,但对我没有帮助。
  • 两件事:首先,-[NSApp hide:] 似乎有助于 Finder 在当前空间没有窗口但在另一个情况下打开窗口的情况。其次,我的观点是停止尝试对事物进行微观管理。如果隐藏当前应用程序导致 Finder 激活其窗口之一,那么这可能是适合您情况的正确行为。而且更简单。
  • 如果我们问的问题是:如果桌面在进程切换之前有它,我如何将焦点重新放在桌面上,那么你怎么能争辩:好吧,也许你不需要这个因为 OSX 不喜欢按照你想要的方式去做。你在争论我们不要求的解决方案。还是我误读了您的答案,并且那里对我们的问题有隐藏的答案?我没看到。
  • 我用您询问的具体内容(AppleScript 的 Objective-C 代码)更新了我的答案。
  • Ken 你是说“选择桌面的窗口”脚本在 10.6 中不起作用吗?这很奇怪。但是 10.6 无论如何都没有原来的问题,即从 Finder 切换然后再回到它,例如使用 Cmd-Tab,不会像在 10.7 及更高版本中那样改变焦点。因此,无论如何,这整个讨论仅与 10.7 及更高版本相关。同意?对于 10.7,我们希望大家都同意,我们仍然没有完整的解决方案,因为我们无法可靠地测试桌面是否有焦点,这要感谢 radr:9406282。
【解决方案2】:

如果桌面上没有选择,恐怕亚历克斯的解决方案不起作用。相反,这个 AppleScript 应该更可靠地判断窗口或桌面是否具有焦点:

tell application "Finder"
    get insertion location
end tell

如果有焦点,它将返回桌面文件夹的路径。

虽然这样做还有两个问题:

  1. 如果用户打开了桌面窗口,则上述脚本返回相同的信息。 IE。人们无法区分桌面和具有焦点的桌面窗口。如果也有解决方案就好了。
  2. 自 10.7 以来 Finder 中存在一个错误(10.8.2 中仍然存在,Apple 已知(请参阅http://openradar.appspot.com/9406282),导致 Finder 报告有关最近打开的窗口的错误信息,甚至使上述脚本也不可靠。

要重新选择桌面,我现在自己找到了解决方案:

tell application "Finder"
    activate
    select the desktop's window
end tell

MacScripter.net 上也有一个关于此的线程,我在其中对选项进行了更多解释:http://macscripter.net/viewtopic.php?pid=160529

【讨论】:

  • 这作为一个单独的问题可能会更好,在上面放置一个赏金,链接回这个问题。但是,这里有足够的扩展可能的解决方案,我将不理会这个答案(它被标记了)。
【解决方案3】:

为什么不调用实际的 Apple Script:

NSDictionary *errorInfoDictionary = nil;
NSAppleScript *script = [[NSAppleScript alloc] initWithContentsOfURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"FocusOnFinder" ofType:@"scpt"]] error:&errorInfoDictionary];

// OR

// NSAppleScript *script = [[NSAppleScript alloc] initWithSource:@"tell application \"Finder\"\n activate\n select the desktop's window\n end tell"];

if (!errorInfoDictionary)
{
    if (![script executeAndReturnError:&errorInfoDictionary])
    {
        NSLog(@"%@", [errorInfoDictionary description]);
    }
}
else
    NSLog(@"%@", [errorInfoDictionary description]);

FocusOnFinder.scpt 的内容

tell application "Finder"
   activate
   select the desktop's window
end tell

如果 Finder 未运行或已最小化,则没有效果。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-07-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多