【问题标题】:How to get multiple properties from objects in JXA?如何从 JXA 中的对象中获取多个属性?
【发布时间】:2019-12-04 15:04:38
【问题描述】:

在 JXA 中有没有一种方法可以通过一次调用从多个对象中获取多个属性?

例如,我想从菜单项中获取 nameenabled 属性,可以对每个单独的属性执行如下操作:

Application("System Events").processes.byName('Finder').menuBars[0].menuBarItems.name()
Application("System Events").processes.byName('Finder').menuBars[0].menuBarItems.enabled()

但是是否可以通过单个函数调用来获取它们?比如:

Application("System Events").processes.byName('Finder').menuBars[0].menuBarItems.select('name', 'enabled')

我知道,我可以遍历 menuBarItems 并从 .properties() 方法中收集属性,但是这种方法太慢了,这就是我寻找其他选项的原因。

更新

我正在寻找更好的性能,而不是更好的语法,即我希望在一次调用 System Events 时检索属性。

【问题讨论】:

  • 不,虽然 Apple 事件 IPC 和 SQL 都是基于查询的,但没有直接等效于 SQL 的 SELECT a,b,c,… FROM…。单个 Apple 事件可以从多个对象中获取单个属性,但不能同时获取多个属性。 (您始终可以从多个对象中获取 properties 属性,尽管这可能有其自身的性能开销。)This 可能有助于澄清(警告各种 AE 功能在 AppleScript 和节点自动化中正常工作但在 JXA 中被破坏)。
  • @foo 我也得出一个结论,即这是不可能的,但我认为也许我遗漏了一些东西,因为文档远非完美。感谢您确认
  • 没有技术原因它无法完成,但 AppleScript 无法表达它,因此应用程序不提供它。你能做的最好的事情是get name of <all-elements>,然后get enabled of <all-elements>,然后将这些列表压缩在一起,但是AS 和JXA 对此都很痛苦。例如在 Python 中,我会使用 from appscript import *; ref = app('System Events').processes['Finder'].menu_bars[1].menu_bar_items; zip(ref.name(), ref.enabled()),它只在两个 Apple 事件中提供 [('Apple', True), ('Finder', True), ('File', True), ('Edit', True),…]。但我认为 CJK 的 C API 解决方案会更快。
  • 当然,另外一个警告是,任何直接操作 GUI 控件的代码都不可避免地是棘手的、脆弱的、不太便携的,并且涉及安全性的 PITA 不断增加。您永远不会说您要解决的问题是什么,但如果它可以在不通过系统事件或较低级别的辅助功能 API 借助 GUI 脚本的情况下完成,那么几乎可以肯定最好这样做。
  • @foo 这是 UI 测试,我同意它是 PITA :)

标签: macos applescript osascript javascript-automation


【解决方案1】:

我可能会这样做:

sys = Application('com.apple.systemevents');
FinderProc = sys.processes['Finder'];
FinderMenuBarItems = FinderProc.menuBars[0].menuBarItems();


Array.from(FinderMenuBarItems,x=>[x.name(),x.enabled()]);

通过首先将object 转换为array,这允许一个map 每个元素并一次性检索所需的属性。为了便于阅读,代码分为几行。

编辑: 添加于 2019 年 7 月 27 日

根据您对 Objective-C 实现的评论,我今天有一些时间来编写 JSObjc 脚本。它与上面的 vanilla JXA 版本做同样的事情,而且,是的,它显然进行了多个函数调用,这是必要的。但它执行这些功能的级别低于系统事件(此处根本不涉及),因此希望您会发现它的性能更高。

ObjC.import('ApplicationServices');
ObjC.import('CoreFoundation');
ObjC.import('Foundation');
ObjC.import('AppKit');

var err = {
    '-25211':'APIDisabled',
    '-25206':'ActionUnsupported',
    '-25205':'AttributeUnsupported',
    '-25204':'CannotComplete',
    '-25200':'Failure',
    '-25201':'IllegalArgument',
    '-25202':'InvalidUIElement',
    '-25203':'InvalidUIElementObserver',
    '-25212':'NoValue',
    '-25214':'NotEnoughPrecision',
    '-25208':'NotImplemented',
    '-25209':'NotificationAlreadyRegistered',
    '-25210':'NotificationNotRegistered',
    '-25207':'NotificationUnsupported',
    '-25213':'ParameterizedAttributeUnsupported',
         '0':'Success' 
};

var unwrap = ObjC.deepUnwrap.bind(ObjC);
var bind = ObjC.bindFunction.bind(ObjC);

bind('CFMakeCollectable', [ 'id', [ 'void *' ] ]);
Ref.prototype.nsObject = function() {
    return unwrap($.CFMakeCollectable(this[0]));
}

function getAttrValue(AXUIElement, AXAttrName) {
    var e;
    var _AXAttrValue = Ref();

    e = $.AXUIElementCopyAttributeValue(AXUIElement,
                                        AXAttrName,
                                        _AXAttrValue);
    if (err[e]!='Success') return err[e];

    return _AXAttrValue.nsObject();
}

function getAttrValues(AXUIElement, AXAttrNames){
    var e;
    var _AXAttrValues = Ref();

    e = $.AXUIElementCopyMultipleAttributeValues(AXUIElement,
                                                 AXAttrNames,
                                                 0,
                                                 _AXAttrValues);
    if (err[e]!='Success') return err[e];

    return _AXAttrValues.nsObject();
}

function getAttrNames(AXUIElement) {
    var e;
    var _AXAttrNames = Ref();

    e = $.AXUIElementCopyAttributeNames(AXUIElement, _AXAttrNames);
    if (err[e]!='Success') return err[e];

    return _AXAttrNames.nsObject();
}


(() => {
    const pid_1        = $.NSWorkspace.sharedWorkspace
                                      .frontmostApplication
                                      .processIdentifier;   
    const appElement   = $.AXUIElementCreateApplication(pid_1);
    const menuBar      = getAttrValue(appElement,"AXMenuBar");
    const menuBarItems = getAttrValue(menuBar, "AXChildren");

    return menuBarItems.map(x => {
        return getAttrValues(x, ["AXTitle", "AXEnabled"]);
    });
})();

【讨论】:

  • 但这仍然会分别调用nameenabled。我的目标不是拥有更好的语法,而是出于性能原因减少对System Events 的调用次数
  • 您的问题根本不清楚。您在这里的评论非常清楚,应该是您为主要问题选择的措辞。我现在对这个问题的理解是,不可能减少拨打电话的次数。 JavaScript objects 是通过引用传递的,因此每次检索 property 时都会对其进行评估。如果您关心性能,最好使用 Objective-C 框架在较低级别进行 API 调用。
  • 好的,我更新了问题以使其更清晰。至于 Objective-C,我很乐意使用它,但似乎不使用脚本桥就无法从它访问外部应用程序,这将导致相同的性能问题。
  • 我添加了脚本的 JSObjC 版本作为概念验证,希望您会发现它有用。我同意,如果一个人只想使用 Objective-C 来使用脚本桥来执行 AppleScript 并调用 系统事件,那将是徒劳的。谢天谢地,这没有必要。
  • 谢谢!与纯 JXA 版本相比,这似乎要快得多。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-19
  • 2020-03-02
  • 2019-09-06
  • 1970-01-01
  • 2016-09-27
相关资源
最近更新 更多