优雅地获得和退出对话的后台应用程序焦点



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

我目前正在使用苹果的Process Manager功能SetFrontProcessWithOptions(),但在以下情况下遇到了麻烦:

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

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

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

如果有人对如何做这样一种基于对话框的事情有更好的想法,那就太好了,甚至可能根本不需要聚焦和取消聚焦Finder。

EDIT:我发现了一种在很大程度上修复这种行为的方法,但它涉及到脚本桥,并且在重命名项目时不会重新聚焦桌面。这是我的代码:

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...
    }
}

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

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

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


如果你真正想要的只是一种运行的方法

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

从Objective-C,我看到了两个选项。首先,使用脚本桥API:

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

(我想你已经掌握了使用ScriptingBridge和Finder的基本知识。如果没有,苹果有一个ScriptingBridgeFinder示例。出于某种原因,它在遗留文档部分。(

其次,您可以使用NSAppleScript:

NSAppleScript* s = [[NSAppleScript alloc] initWithSource:@"tell application "Finder"nactivatenselect the desktop's windownend tell"];
NSDictionary* err = nil;
if (![s executeAndReturnError:&err])
    /* handle error */;

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

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

tell application "Finder"
    get insertion location
end tell

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

尽管这仍然有两个问题:

  1. 如果用户打开了桌面窗口,则上述脚本将返回相同的信息。也就是说,人们无法区分桌面和具有焦点的桌面窗口。如果能有一个解决方案,那就太好了
  2. Finder自10.7以来就有一个错误(10.8.2中仍然存在,苹果公司已知((请参阅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

为什么不调用实际的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 activaten select the desktop's windown 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未运行或已最小化,则不会产生任何影响。

相关内容