While updating the Semiconductor Toolkit, I looked for a better way to rebuild the Recall function.While updating the Semiconductor Toolkit, I looked for a better way to rebuild the Recall function.While updating the Semiconductor Toolkit, I looked for a better way to rebuild the Recall function.While updating the Semiconductor Toolkit, I looked for a better way to rebuild the Recall function.在更新 Semiconductor Toolkit 时,我寻找更好的方法来重建 Recall 功能。
打开分销平台联合管理计划。 看看右边的那一小组按钮——具体来说,是“调用”按钮。 它无处不在。 JMP 中几乎每个平台都有它。 它已融入 JMP 设计语言的 DNA 中。 现在想一想,这个小按钮有多少次在你每天与数据的激烈斗争中拯救了你的芳心。 很多,对吧? 这可能是 JMP 中最被认为理所当然的功能之一。 但我认为,这是使 JMP 如此有用的无数小细节之一。
现在,JSL 脚本编写人员请考虑一下在自定义加载项中复制该 Recall 函数。 这是一项相当高级的任务——我见过大多数人(包括我自己)这样做的方式是在自定义或全局命名空间中为您想要调用的每个 GUI 元素创建一个变量。 然后是在运行时从每个 GUI 元素收集信息的支持函数,以及按下“调用”按钮时写回信息的支持函数。它。 是。 A。 皇家。 疼痛。在 JMP 脚本和自定义应用程序中设置调用功能可能是最费力的任务之一。 对于 GUI 中的一两个组件来说,这不是什么大问题,但标准流程的扩展性不佳。 然而,为了实现这一目标,召回功能赋予了插件更高层次的完善性和易用性,从而将其提升到了一个新的专业水平。 当加载项中具有召回功能时,用户会喜欢它。 而且,坦白地说,我讨厌编码。
我们可以重建它
让我退后一点。 这篇文章讨论的想法始于 2016 年初。 我刚刚开始更新半导体工具包(STK) 到版本 2。 由于各种原因(我将在另一篇博客文章中介绍),我决定将整个项目迁移到应用程序生成器在联合管理计划。这意味着需要对代码库和 GUI 进行大量修改、整理、尽可能标准化代码以及添加同事贡献的一些新功能(再次感谢大家!)。 不幸的是,这项工作破坏了版本 1 中 Recall 的所有现有代码。 面对为加载项中的所有平台重建 Recall 代码的任务,我想:“必须有另一种方法来解决这个问题。 必须有一种方法来减少所有重复编码。”
大约在同一时间,我正在研究如何使用 JSL 中的关联数组 (AA) 来实现 STK 中的一些其他功能。 我没有看到人们经常使用它们,但是 AA 可以成为您的脚本库中一个非常强大的工具。 为了便于讨论,AA 是一种特殊的列表。 你可以搜索它、放入东西、取出东西等等。 它们的特别之处在于列表中的每个条目都有两个部分:键和值。 AA 上有一些很棒的博客文章和资源( JMP 文档关于将关联数组列表转换为数据表JSL 提示:在列表中查找唯一值)是我在与 AA 合作时参考的。 他们涵盖了有关 AA 的大量材料,并提供了一些很好的示例。 回到我最初的痛点,我想到全局变量名称及其内容可能会被转换为 AA 中的键值对。 因此,作为我此刻的缪思,我想知道是否可以使用关联数组来构建更好的召回。
好吧,为了帮助理解我的想法,让我们看看这通常是如何完成的。 实现 Recall 需要两个基本操作。 当用户执行某些触发动作,即单击“确定”时,所有要调用的GUI元素,即列表框、单选框、文本框等都需要以某种方式提取并存储它们的内容。 然后,当用户单击“Recall”按钮时,需要将存储的内容推回到GUI中的适当位置。 这两个操作的核心是一对 JSL 消息: << 获取()和<< 设置() 。 根据 GUI 元素的不同,这些有不同的风格(不要问我为什么–我没有写JSL –我只是使用这些东西)但它们基本上都是这两个的变体并且做相同的事情。 JMP 中的脚本索引(帮助 > 脚本索引)是一种很好的方法,可以清楚地了解哪个脚本与哪个 GUI 元素相匹配。
因此,我的基于 AA 的解决方案必须做很多事情。 首先,我必须能够使用<< 获取()和<< 设置()在它们的所有排列中。 我必须能够告诉它我想要保存哪些 GUI 元素。 而且,我必须有一个地方来保存这些信息,当加载项运行完毕后,这些信息不会消失。 这意味着修改后的工作流程将涉及使用我想要恢复的 GUI 元素列表构建 AA。 我需要将此 AA 放入全局或某个自定义命名空间(稍后会详细介绍)。 当我考虑构建 AA 时,我遇到了一个问题:“如果我使用 GUI 元素作为 AA 中的键,那么我的键值对中的值将使用什么?”我意识到,由于 AA 的工作方式,我可以将它们留空,即使用“空的() “作为我的价值,但出于某种原因,这感觉像是浪费了一个机会。 当我更多地思考这个问题时,我意识到,如果我将 GUI 元素的默认值放入 AA 键值对中,我实际上可以设置一个“重置”功能(这会将 GUI 恢复为其默认值)状态)使用相同的代码! 我可以用一项功能的价格来支持两项功能! 而且,谁不喜欢好的两轮呢?
是的,命名空间很有用
回到问题命名空间一会儿。 对于外行来说,命名空间可能看起来像他们在网站上听到的神秘、神秘的东西。 JMP探索峰会或者偶尔在博客文章中。 现在让我戳破这个泡沫——它们并没有那么特别。 然而,它们非常有用,最好的 JSL 脚本编写者已经学会了如何有效地使用它们。 (需要明确的是,我不认为自己属于那个群体,但我偶尔会在 SAS 总部或探索峰会上与他们一起吃午餐……所以也许这很重要。)
为了让您了解命名空间的含义和用途,让我们看一下字典。 如果你正在写一些东西英语需要查找一个单词,您可以使用英语字典。 如果您要开始工作法语需要查找一个单词,您可以使用法语字典。 有时,由于 1066 年诺曼征服,同一个词可能会出现在两个词典中,但含义可能略有不同。 这通常不会成为问题,因为您有该文档语境来帮助你。 如果您正在开发一个法语文件,你伸手去拿法语字典——没问题。 如果您在同一个文档中混合使用法语和英语,只要您有一些,这甚至不是问题语境帮助您找出要查找的词典的线索。这和JSL有什么关系呢? 命名空间就像字典。 您可以在其中放置自定义变量和函数,为 JMP 提供额外的上下文以了解您想要它执行的操作。 例如,在STK中,我使用函数名注释窗口()在几个地方。 事实上,我在软件的每个部分都使用了这个名称的函数,在现有窗口上绘制一些东西。总共,我可能有六个名为注释窗口() 。 问题是每个版本的功能都略有不同。 但是,因为每个都存在于自己的名称空间中,所以这从来都不是问题。 即使我同时打开两个不同的 GUI。 JMP 知道当我在一个命名空间中工作时,我期望注释窗口()做一件事,而当我在做另一件事时,我希望它能做其他事情。 该策略显着简化了 STK 中的 GUI 设计过程。 所以,命名空间可以让你提供语境让 JMP 了解您希望它在特定脚本中使用哪个版本的函数。 在创建它们的加载项关闭后,它们也可以存在于内存中。 正是这两点使得它们对我们的召回解决方案特别有用。
是时候编码了
好了,理论已经足够了——让我们来破解一些代码。 此外,如果我一边解释一边解释,剩下的部分就会更有意义。 首先,我提倡在脚本中使用库,这是大多数开发情况下的最佳实践。 大多数编程语言都可以做到这一点 – JSL 也可以。 所以,我的Recall解决方案将以库的形式构建。 要在应用程序或脚本中使用该库,开发人员只需使用包括()脚本开头的参数以及库的文件路径。
其次,我喜欢聊天功能。 让脚本告诉您它在每一步中正在做什么,这真的很方便。 但是,我也希望能够告诉我的聊天功能关掉它当我完成测试和编码时。 (如果您不小心,繁琐的函数可能会减慢您的代码执行速度)。 如果你看实际的图书馆在文件交换中,您会看到很多写入()和展示()论据。 这些包含在一个如果(详细 == 1,<…>)声明让我可以通过传递来关闭它们详细 = 0对函数的命令。 我在我的脚本中经常使用这个结构,我通常只会把冗长的变量位于脚本顶部,以便我可以通过调整一个参数来打开和关闭整个脚本的详细日志记录。 为了保持代码简短,我从下面的代码片段中删除了它们。 完整的库可以在以下位置找到调用函数库。
库中的第一个函数称为genArrays() 。 此函数根据 GUI 中的元素列表及其默认值列表创建 AA。 该函数实际上制作了 AA 的两个副本,其中一个用于支持 Recall 函数,称为::<平台名称>用户设置,以及一个支持重置功能的功能,称为::<平台名称>默认值。 这两个数组被保存到全局命名空间中。 这 ” :: ” 变量名前面的运算符可以执行此操作。 另一件需要注意的事情是我正在使用评估(解析())结构。 这是构建动态 JSL 的常见策略,但使用替换方案可能会很麻烦。 最后要注意的是我锁用户设置变量。 这是为了防止 AA 或任何值被意外覆盖。
genArrays = Function( {objects, values, platformName, verbose = 1},
{Default Local},
// Create the defaults Array
defaultString = "::" || platformName || "Defaults = Associative Array( objects, values, Empty() )";
defaultStringName = "::" || platformName || "Defaults";
// Run the default command
Eval( Parse( defaultString ) );
// Create the defaults Array
userString = "::" || platformName || "UserSettings = ::" || platformName || "Defaults";
userStringName = "::" || platformName || "UserSettings";
// Try to create the user settings Array and lock it so it's not overwritten.
Try(
Eval( Parse( userString ) );
Eval( Parse( "Lock Globals( " || platformName || "UserSettings )" ) );
,
Write(
"\!N\!NThe Associative Array \!"" || platformName || "UserSettings\!" is already present in memory and ready for use.\!N\!N"
)
);
);
让我们花点时间看看用于构建 AA 的列表中的内容。 当我在考虑如何解决这个问题时,我必须处理以下问题: << 获取()和<< 设置()我需要处理的争论。 我的想法是在 AA 中的键值对的键部分添加一个标头。 这些是放在键(GUI 元素)前面的一两个字母。 对于使用的 GUI 元素<< 设置()标题是s^ 。 这<< 设置项目()和<< 设置文本()使用巳^和圣^分别。 如果我有一个 GUI(我们称之为“简洁的图形用户界面”) 带有文本框 ( TB1 ),一个数字框( NB1 )和一个列表框(磅1 )对象列表将如下所示:
objects = {st^tb1, s^nb1, si^lb1};
如果我们说我们希望这些在脚本运行时或用户按下重置按钮时具有特定值,我们可以设置价值观列出我们想要的东西。 我将使用一个空的()命令用于清除列表框的内容。
values = {“hello world”, 42, Empty()};
现在我们已经有了两个列表,我们可以使用GenArrays()函数生成数组如下:
GenArrays(objects, values, “neatGUI”);
这将在全局命名空间中创建两个 AA。 在创建时,它们都看起来像这样:
[s^nb1=>42, si^lb1=>Empty(), st^tb1=>”hello world”]
现在,我们所要做的就是为接下来的三个函数提供相同的 GUI 名称(简洁的图形用户界面)它会处理剩下的事情! 让我们看看他们做了什么。
实际与 GUI 交互的第一个函数是重置角色() 。 该函数基本上是一个循环,它提取默认 AA 中的每个键和值,并使用该信息将 GUI 元素重置为默认值。由于 AA 是“特殊”列表,我们需要使用不同的指令来处理它们。 因此,我不只是使用下标获取 AA 中的第一个值,而是使用<< 第一信息。 从这里开始,它只是将标题部分与键中的 GUI 元素分开。这些片段被传递给一些逻辑,所以我使用正确的<< 设置()带有来自 AA 的当前密钥值的消息。 最后,这一切都是使用另一个执行的评估(解析())争论。 然后对 AA 中的每个键值对重复整个过程。
resetRoles = Function( {platformName, verbose = 1},
{Default Local},
// Get the AA for the provided name
arrayName = Eval( Parse( "::" || platformName || "Defaults" ) );
arrayNameString = "::" || platformName || "UserSettings";
// Setup a logical break if an AA isn't provided
Try(
// Start the function
// Check if arrayName is an AA
If( Is Associative Array( arrayName ),
//Initialize the counter
currentKey = arrayName << First;
// Loop through the keys
For( i = 1, i <= N Items( arrayName ), i++,
// Extract the header and the key
stAA = currentkey; // The header-key pair
stType = Word( 1, stAA, "^" ); // The header part
stArg = Word( 2, stAA, "^" ); // The key part
// Match the operation header to the correct operation instruction and build the string
stArgString = Match( stType,
"si", stArg || " << Set Items( " || Char( arrayName[currentkey] ) || " )",
"s", stArg || " << Set( " || Char( arrayName[currentkey] ) || " )",
"st", stArg || " << Set Text( \!"" || Char( arrayName[currentkey] ) || "\!" )"
);
// Eval-Parse the string
Try( Eval( Parse( stArgString ) ) );
// Get the next key in the AA
nextkey = arrayName << Next( currentKey );
// set it as the target key for the next loop
currentkey = nextkey;
);
,
// Stop execution if not an AA.
Throw()
);
,
);
);
另外两个函数,存储角色()和召回角色() ,以类似的方式工作,但有一些细微的差别。 首先,它们都运行在用户AA 代替默认GenArrays 创建的 AA () 。 第二,存储角色()用途<< 获取()消息并覆盖价值GUI 元素的键值对的一部分,因此可以存储它以供以后调用。 还包含一些在编辑期间解锁和重新锁定用户 AA 的代码。 最后,回忆角色()函数使用<< 追加()消息与巳^标题而不是<< 设置()信息。 我不会将代码放在这里,而是建议您参考文件交换中的库。
因此,经过一番思考、编码、删除键的自由应用(对于那些困在 Windows 世界中的人来说,退格键……我的哀悼),以及更多的编码,我了解到完全有可能构建一组支持调用/重置的函数使用关联数组。 事后看来,对于一个简单的两元素或三元素 GUI,整个过程确实有点鲁布-戈德堡式。 但是,想象一下一个包含 10 或 20 个元素的 GUI。 这种方法的扩展性比传统方法好得多。 您只需跟踪要调用/重置的内容及其初始值的列表。 而且,你甚至不需要操着浓重的奥地利口音,也不需要与渴望权力的州长争夺火星的控制权来做到这一点。