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.Während ich das Semiconductor Toolkit aktualisierte, suchte ich nach einer besseren Möglichkeit, die Recall-Funktion neu zu erstellen.
Öffnen Sie die Vertriebsplattform in JMP . Schauen Sie sich die kleine Gruppe von Schaltflächen auf der rechten Seite an – insbesondere die Schaltfläche „Abrufen“. Es ist überall. Fast jede Plattform in JMP hat es. Es ist in der DNA der JMP-Designsprache verankert. Denken Sie jetzt einen Moment darüber nach, wie oft dieser kleine Knopf Ihre Hüfte in der Hitze Ihres täglichen Kampfes mit Daten gerettet hat. Eine Menge, oder? Dies ist wahrscheinlich eine der Funktionen von JMP, die am meisten als selbstverständlich angesehen wird. Aber ich würde sagen, dass es eine der unzähligen kleinen Details ist, die JMP so nützlich machen, wie es ist.
Nun, JSL-Skripter, denken Sie einen Moment darüber nach, diese Recall-Funktion in einem benutzerdefinierten Add-In zu replizieren. Es ist eine ziemlich fortgeschrittene Aufgabe – die Art und Weise, wie ich sie bei den meisten Leuten (mich eingeschlossen) gesehen habe, würde das Erstellen einer Variablen in einem benutzerdefinierten oder globalen Namensraum für jedes GUI-Element beinhalten, das Sie abrufen möchten. Dann gibt es die Unterstützungsfunktionen zum Sammeln der Informationen von jedem GUI-Element zur Laufzeit und diejenigen zum Zurückschreiben der Informationen, wenn die Schaltfläche „Abrufen“ gedrückt wird.Es. Ist. A. Königlich. Schmerz.Das Einrichten einer Rückruffunktion in JMP-Skripten und benutzerdefinierten Anwendungen ist wahrscheinlich eine der mühsamsten Aufgaben überhaupt. Für ein oder zwei Komponenten in einer GUI ist das keine große Sache, aber der Standardprozess lässt sich nicht gut skalieren. Und doch verleiht eine Rückruffunktion einem Add-In zu diesem Zweck einen ebenmäßigen Schliff und eine Benutzerfreundlichkeit, die es auf eine neue Ebene der Professionalität hebt. Benutzer lieben es, wenn ein Add-In über eine Rückruffunktion verfügt. Und um ehrlich zu sein: Ich hasse es, es zu programmieren.
Wir können es wieder aufbauen
Lassen Sie mich etwas zurückhalten. Die Idee, die in diesem Beitrag behandelt wird, entstand Anfang 2016. Ich hatte gerade mit der Aktualisierung begonnen Halbleiter-Toolkit (STK) auf Version 2. Aus verschiedenen Gründen, auf die ich in einem anderen Blogbeitrag eingehen werde, habe ich beschlossen, das gesamte Projekt nach zu migrieren Anwendungsersteller In JMP .Das bedeutete, viel an der Codebasis und den GUIs herumzubasteln, die Dinge aufzuräumen, den Code zu standardisieren, wo immer es möglich war, und einige neue Funktionen hinzuzufügen, die von Kollegen beigesteuert wurden (nochmals vielen Dank, Leute!). Leider hat diese Arbeit den gesamten vorhandenen Code für Recall aus Version 1 zerstört. Angesichts der Aufgabe, den Recall-Code für alle Plattformen im Add-In neu zu erstellen, dachte ich: „Es muss einen anderen Weg geben, dieses Problem anzugehen.“ Es muss einen Weg geben, all diese sich wiederholende Codierung zu reduzieren.“
Etwa zur gleichen Zeit habe ich untersucht, wie man Assoziative Arrays (AAs) in JSL für einige andere Funktionen im STK verwenden kann. Ich sehe nicht, dass Leute sie häufig verwenden, aber AAs können ein unglaublich leistungsfähiges Werkzeug in Ihrem Skript-Arsenal sein. Im Rahmen dieser Diskussion handelt es sich bei einer AA um eine besondere Art von Liste. Sie können es durchsuchen, Dinge hineinlegen, Dinge herausnehmen usw. Das Besondere an ihnen ist, dass jeder Eintrag in der Liste aus zwei Teilen besteht, einem Schlüssel und einem Wert. Es gibt einige großartige Blogbeiträge und Ressourcen zum Thema AAs ( JMP-Dokumentation Beim Konvertieren einer Liste assoziativer Arrays in eine Datentabelle JSL-Tipp: Eindeutige Werte in einer Liste finden ), auf die ich verweise, wenn ich mit AAs arbeite. Sie decken einen Großteil des Materials über AAs ab und haben einige großartige Beispiele. Als ich auf meinen ursprünglichen Problempunkt zurückkam, kam mir der Gedanke, dass der Name der globalen Variablen und ihr Inhalt wahrscheinlich in ein Schlüssel-Wert-Paar in einer AA übersetzt werden könnten. Als meine Muse des Augenblicks fragte ich mich, ob ich ein assoziatives Array verwenden könnte, um einen besseren Rückruf zu erstellen.
OK, um zu verstehen, was ich gedacht habe, schauen wir uns an, wie das normalerweise gemacht wird. Zur Implementierung von Recall sind zwei grundlegende Vorgänge erforderlich. Wenn der Benutzer eine auslösende Aktion ausführt, z. B. auf „OK“ klickt, muss der Inhalt aller abzurufenden GUI-Elemente, z. B. Listenfelder, Optionsfelder, Textfelder usw., abgerufen und auf irgendeine Weise gespeichert werden. Wenn der Benutzer dann auf die Schaltfläche „Abrufen“ klickt, müssen die gespeicherten Inhalte an die entsprechenden Stellen in der GUI zurückgeschoben werden. Im Mittelpunkt dieser beiden Vorgänge stehen zwei JSL-Nachrichten: << get() Und << set() . Abhängig vom GUI-Element gibt es davon unterschiedliche Varianten (fragen Sie mich nicht warum). – Ich habe kein JSL geschrieben – Ich benutze das Zeug einfach), aber im Grunde sind sie alle nur Varianten dieser beiden und machen die gleichen Dinge. Der Skriptindex in JMP (Hilfe > Skriptindex) ist eine hervorragende Möglichkeit, den Überblick darüber zu behalten, welches zu welchem GUI-Element gehört.
Meine AA-basierte Lösung muss also eine Reihe von Dingen tun. Erstens muss ich es nutzen können << get() Und << set() in all ihren Variationen. Ich muss ihm sagen können, welche GUI-Elemente ich speichern möchte. Und ich muss einen Ort haben, an dem ich diese Informationen speichern kann, damit sie nicht verschwinden, wenn die Ausführung des Add-Ins abgeschlossen ist. Das bedeutet, dass der überarbeitete Arbeitsablauf die Erstellung einer AA anhand einer Liste der GUI-Elemente umfassen würde, die ich wiederherstellen möchte. Ich müsste diese AA entweder in den globalen oder einen benutzerdefinierten Namespace einfügen (mehr dazu gleich). Als ich über die Erstellung der AA nachdachte, kam mir die Frage: „Wenn ich die GUI-Elemente als Schlüssel in meiner AA verwende, was würde ich dann für die Werte in meinen Schlüssel-Wert-Paaren verwenden?“ Mir wurde klar, dass ich AAs aufgrund der Art und Weise, wie sie funktionieren, einfach leer lassen könnte, d. h. „ Leer() „als meinen Wert, aber aus irgendeinem Grund fühlte sich das wie eine Verschwendung einer Chance an.“ Als ich weiter darüber nachdachte, wurde mir klar, dass ich, wenn ich die Standardwerte für die GUI-Elemente in die AA-Schlüssel-Wert-Paare einfügen würde, tatsächlich eine „Reset“-Funktion einrichten könnte (die die GUI auf ihre Standardwerte zurücksetzen würde). Bundesland) mit dem gleichen Code! Ich könnte zwei Funktionen zum Preis von einer unterstützen! Und wer liebt nicht einen guten Two-Fer?
Ja, Namespaces sind nützlich
Zurück zum Thema Namensräume für eine Sekunde. Namensräume mögen für den Uneingeweihten wie dieses mystische, mysteriöse Ding erscheinen, von dem sie hören JMP Discovery Summit oder gelegentlich in einem Blogbeitrag. Lassen Sie mich diese Blase jetzt platzen lassen – sie sind nicht so besonders. Sie sind jedoch unglaublich nützlich und die besten JSL-Skripter haben gelernt, sie effektiv zu nutzen. (Um es klarzustellen, ich zähle mich nicht zu dieser Gruppe, aber ich esse gelegentlich mit ihnen zu Mittag im SAS-Hauptquartier oder beim Discovery Summit … vielleicht zählt das also etwas.)
Um Ihnen ein Bild davon zu geben, was ein Namespace ist und was er tut, werfen wir einen Blick auf Wörterbücher. Wenn Sie etwas hineinschreiben Englisch und ein Wort nachschlagen müssten, würden Sie ein verwenden Englisch Wörterbuch. Wenn Sie anfangen würden, dort zu arbeiten Französisch und ein Wort nachschlagen müssten, würden Sie a verwenden Französisch Wörterbuch. Dank der normannischen Eroberung im Jahr 1066 kam das gleiche Wort manchmal in beiden Wörterbüchern vor, hatte jedoch leicht unterschiedliche Bedeutungen. Dies wäre im Allgemeinen kein Problem, da Sie über das Dokument verfügen Kontext um Ihnen zu helfen. Wenn Sie an einem arbeiten Französisch Dokument, Sie greifen nach dem Französisch Wörterbuch – kein Problem. Es ist nicht einmal ein Problem, wenn Sie im selben Dokument eine Mischung aus Französisch und Englisch verwenden, solange Sie welche haben Kontext Hinweise, die Ihnen helfen, herauszufinden, nach welchem Wörterbuch Sie greifen müssen.Was hat das mit JSL zu tun? Namespaces sind wie Wörterbücher. Dies sind Orte, an denen Sie benutzerdefinierte Variablen und Funktionen einfügen können, die JMP zusätzlichen Kontext bieten, um zu verstehen, was Sie tun möchten. Im STK verwende ich beispielsweise einen Funktionsnamen annotateWindow() an mehreren Orten. Tatsächlich verwende ich eine Funktion mit diesem Namen in jedem Teil der Software, in dem ich etwas in ein vorhandenes Fenster zeichne.Insgesamt habe ich wahrscheinlich ein halbes Dutzend Funktionen mit dem Namen annotateWindow() . Der Nachteil besteht darin, dass jede Version etwas anderes macht. Da jedoch jeder in seinem eigenen Namensraum existiert, stellt dies kein Problem dar. Auch wenn ich zwei verschiedene GUIs gleichzeitig geöffnet habe. JMP weiß, dass ich es erwarte, wenn ich in einem Namespace arbeite annotateWindow() Ich muss eine Sache tun, und wenn ich in einer anderen bin, erwarte ich, dass sie etwas anderes bewirkt. Diese Strategie vereinfachte den GUI-Designprozess im STK erheblich. Mit Namespaces können Sie also bereitstellen Kontext Damit JMP weiß, welche Version einer Funktion in einem bestimmten Skript verwendet werden soll. Sie können auch im Speicher vorhanden sein, nachdem das Add-In, das sie erstellt hat, geschlossen wurde. Und es sind diese beiden Punkte, die sie für unsere Recall-Lösung besonders nützlich machen.
Es ist Zeit zu programmieren
Okay, genug der Theorie – lasst uns etwas Code hacken. Außerdem wird der Rest mehr Sinn ergeben, wenn ich es im Nachhinein erkläre. Erstens bin ich ein Befürworter der Verwendung von Bibliotheken in meinen Skripten, da dies in den meisten Entwicklungssituationen die beste Vorgehensweise ist. Die meisten Programmiersprachen können das – JSL auch. Daher wird meine Recall-Lösung in Form einer Bibliothek erstellt. Um die Bibliothek in einer Anwendung oder einem Skript zu verwenden, muss der Entwickler lediglich eine verwenden enthalten() Argument am Anfang des Skripts mit einem Dateipfad zur Bibliothek.
Zweitens bin ich ein Fan von Chatfunktionen. Es ist wirklich praktisch, wenn das Skript Ihnen bei jedem Schritt sagt, was es tut. Aber ich möchte auch in der Lage sein, meine gesprächigen Funktionen mitzuteilen schalten Sie es wenn ich mit dem Testen und Codieren fertig bin. (Geschwätzige Funktionen können Ihre Codeausführung verlangsamen, wenn Sie nicht vorsichtig sind.) Wenn man hinschaut die eigentliche Bibliothek Im Dateiaustausch wird viel geschrieben () Und zeigen() Argumente. Diese sind in einem enthalten If(verbose == 1, <…>) Anweisung, mit der ich sie durch Übergeben von a ausschalten kann ausführlich = 0 Befehl an die Funktion. Ich verwende dieses Konstrukt häufig in meinen Skripten und füge normalerweise einfach das ein ausführlich Variable oben im Skript, damit ich die ausführliche Protokollierung für das gesamte Skript ein- und ausschalten kann, indem ich einen Parameter anpasse. Um den Code hier kurz zu halten, habe ich diese aus den folgenden Snippets entfernt. Die fertige Bibliothek finden Sie im Funktionsbibliothek abrufen .
Die erste Funktion in der Bibliothek wird aufgerufen genArrays() . Diese Funktion erstellt eine AA aus einer Liste von Elementen in der GUI und einer Liste ihrer Standardwerte. Die Funktion erstellt tatsächlich zwei Kopien der AA – eine zur Unterstützung der aufgerufenen Recall-Funktionen ::Benutzereinstellungen und eine zur Unterstützung der Reset-Funktionen, genannt ::Standardeinstellungen . Diese beiden Arrays werden im globalen Namespace gespeichert. Der " :: Der Operator „“ am Anfang eines Variablennamens bewirkt dies. Das andere, was mir auffällt, ist, dass ich ein verwende Eval(Parse()) Struktur. Dies ist eine gängige Strategie zum Aufbau dynamischer JSL, die bei der Verwendung von Substitutionsschemata möglicherweise umständlich ist. Das Letzte, was mir auffällt, ist, dass ich sperren die Benutzereinstellungsvariable. Dadurch soll verhindert werden, dass der AA oder einer der Werte versehentlich überschrieben wird.
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"
)
);
);
Nehmen wir uns einen Moment Zeit und schauen wir uns an, was in den Listen steht, die zur Erstellung der AA verwendet werden. Als ich überlegte, wie ich dieses Problem angehen sollte, musste ich mich mit dem Problem auseinandersetzen, dass es mehrere Varianten davon gibt << get() Und << set() Argumente, mit denen ich umgehen musste. Meine Idee bestand darin, dem Schlüsselteil des Schlüssel-Wert-Paares in der AA einen Header hinzuzufügen. Dabei handelt es sich um einen oder zwei Buchstaben, die vor dem Schlüssel (dem GUI-Element) stehen. Für GUI-Elemente, die verwenden << set() der Header ist s^ . Der << Elemente festlegen() Und << Text festlegen() verwenden si^ Und st^ jeweils. Wenn ich eine GUI hätte (wir nennen sie „ ordentlichGUI “) mit einem Textfeld ( tb1 ), ein Zahlenfeld ( nb1 ) und ein Listenfeld ( lb1 ) würde die Objektliste so aussehen:
objects = {st^tb1, s^nb1, si^lb1};
Wenn wir sagen würden, dass wir möchten, dass diese bestimmte Werte haben, wenn das Skript ausgeführt wird oder wenn der Benutzer eine Reset-Taste drückt, könnten wir das einrichten Werte Liste mit dem, was wir wollten. Ich werde ein verwenden Leer() Befehl für das Listenfeld, um den Inhalt zu löschen.
values = {“hello world”, 42, Empty()};
Da wir nun unsere beiden Listen haben, können wir die verwenden GenArrays() Funktion zum Generieren der Arrays wie folgt:
GenArrays(objects, values, “neatGUI”);
Dadurch werden die beiden AAs im globalen Namespace erstellt. Bei der Erstellung würden beide so aussehen:
[s^nb1=>42, si^lb1=>Empty(), st^tb1=>”hello world”]
Jetzt müssen wir nur noch die nächsten drei Funktionen mit demselben GUI-Namen versehen ( ordentlichGUI ) und es wird sich um den Rest kümmern! Schauen wir uns an, was sie tun.
Die erste Funktion, die tatsächlich mit der GUI interagiert, ist resetRoles() . Diese Funktion ist im Grunde eine Schleife, die jeden Schlüssel und Wert in der Standard-AA abruft und die Informationen verwendet, um die GUI-Elemente auf die Standardwerte zurückzusetzen.Da es sich bei AAs um „spezielle“ Listen handelt, müssen wir unterschiedliche Anweisungen verwenden, um mit ihnen zu arbeiten. Anstatt also einfach den ersten Wert in der AA mithilfe eines Index abzurufen, verwende ich den << Zuerst Nachricht. Von hier aus trennt es nur noch den Header-Teil vom GUI-Element im Schlüssel.Diese Teile werden an eine Logik übergeben, daher verwende ich das Recht << set() Nachricht mit dem Wert von der AA für den aktuellen Schlüssel. Schließlich wird dies alles mit einem anderen ausgeführt Eval(Parse()) Streit. Der gesamte Vorgang wiederholt sich dann für jedes Schlüssel-Wert-Paar in der 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()
);
,
);
);
Die anderen beiden Funktionen, storeRoles() Und RecallRoles() , funktionieren auf ähnliche Weise mit ein paar kleinen Unterschieden. Erstens operieren beide auf der Benutzer AA statt Standard AA erstellt von GenArrays () . Zweite, storeRoles() Verwendet << get() Nachrichten und überschreibt die Wert Teil des Schlüssel-Wert-Paares für ein GUI-Element, damit es für einen späteren Abruf gespeichert wird. Es ist auch ein Code enthalten, um die Benutzer-AA während der Bearbeitung zu entsperren und wieder zu sperren. Zuletzt die RecallRoles () Funktion verwendet die << Anhängen() Nachricht mit dem si^ Header anstelle des << set() Nachricht. Anstatt den Code hier einzugeben, verweise ich Sie auf den Bibliothek im Dateiaustausch .
Nach einigem Nachdenken, Codieren, großzügiger Anwendung der Entf-Taste (Rücktaste für diejenigen, die in der Windows-Welt feststecken … mein Beileid) und etwas mehr Codierung habe ich gelernt, dass es durchaus möglich ist, eine Reihe von Funktionen zu erstellen, die Recall/Reset unterstützen unter Verwendung eines assoziativen Arrays. Im Nachhinein betrachtet ist dieser ganze Prozess für eine einfache GUI mit zwei oder drei Elementen zugegebenermaßen etwas langwierig Rube-Goldberg-artig . Stellen Sie sich jedoch eine GUI mit 10 oder 20 Elementen vor. Dieser Ansatz lässt sich viel besser skalieren als der herkömmliche Ansatz. Sie müssen lediglich eine Liste der Elemente, die Sie abrufen/zurücksetzen möchten, und deren Anfangswerte im Auge behalten. Und dafür muss man nicht einmal einen starken österreichischen Akzent haben oder gegen einen machthungrigen Gouverneur um die Kontrolle über den Mars kämpfen.