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。 這種方法的擴展性比傳統方法好得多。 您只需追蹤要呼叫/重置的內容及其初始值的清單。 而且,你甚至不需要操著濃重的奧地利口音,也不需要與渴望權力的州長爭奪火星的控制權來做到這一點。