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.Lors de la mise à jour du Semiconductor Toolkit, j'ai cherché un meilleur moyen de reconstruire la fonction Recall.
Ouvrez la plateforme de distribution dans JMP . Regardez ce petit groupe de boutons sur la droite – en particulier le bouton « Rappel ». Il y en a partout. Presque toutes les plates-formes de JMP l'ont. C'est ancré dans l'ADN du langage de conception JMP. Pensez maintenant une seconde à la fréquence à laquelle ce petit bouton a sauvé votre fanny dans le feu de votre bataille quotidienne avec les données. Beaucoup, non ? Il s’agit probablement de l’une des fonctionnalités de JMP les plus considérées comme allant de soi. Mais je dirais que c'est l'une des innombrables petites attentions qui rendent JMP aussi utile qu'il l'est.
Maintenant, scripteurs JSL, réfléchissez une seconde à la réplication de cette fonction de rappel dans un complément personnalisé. C'est une tâche assez avancée – la façon dont j'ai vu la plupart des gens le faire (moi y compris) impliquerait de créer une variable dans un espace de noms personnalisé ou global pour chaque élément de l'interface graphique que vous souhaitez rappeler. Ensuite, il y a les fonctions de support pour collecter les informations de chaque élément de l'interface graphique au moment de l'exécution et celles pour réécrire les informations lorsque le bouton Rappel est enfoncé.Il. Est. UN. Royal. Douleur.La configuration d'une fonctionnalité de rappel dans les scripts JMP et les applications personnalisées est probablement l'une des tâches les plus laborieuses. Pour un ou deux composants dans une interface graphique, ce n'est pas grave, mais le processus standard n'évolue pas bien. Et pourtant, pour cet effort, une capacité de rappel confère un niveau de finition et une facilité d'utilisation à un complément qui l'élève à un nouveau niveau de professionnalisme. Les utilisateurs adorent quand il existe une capacité de rappel dans un complément. Et pour être franc, je déteste le coder.
Nous pouvons le reconstruire
Permettez-moi de revenir un peu en arrière. L’idée dont parle cet article a commencé début 2016. Je venais de commencer à mettre à jour le Boîte à outils pour les semi-conducteurs (STK) vers la version 2. Pour diverses raisons que j'aborderai dans un autre article de blog, j'ai décidé de migrer l'ensemble du projet vers Générateur d'applications dans JMP .Cela signifiait beaucoup de bricolage avec la base de code et les interfaces graphiques, du rangement, la standardisation du code autant que possible et l'ajout de nouvelles fonctionnalités apportées par des collègues (merci encore les gars !). Malheureusement, ce travail a cassé tout le code existant pour Recall de la version 1. Face à la tâche de reconstruire le code Recall pour toutes les plates-formes du complément, j'ai pensé : « Il doit y avoir une autre façon d'aborder ce problème. Il doit y avoir un moyen de réduire tout ce codage répétitif.
À peu près à la même époque, j'étudiais comment utiliser les tableaux associatifs (AA) en JSL pour d'autres fonctionnalités du STK. Je ne vois pas beaucoup de gens les utiliser, mais les AA peuvent être un outil incroyablement puissant dans votre arsenal de scripts. Pour les besoins de cette discussion, une AA est un type particulier de liste. Vous pouvez le rechercher, y insérer, en retirer, etc. Ce qui les rend spéciaux est que chaque entrée de la liste comporte deux parties, une clé et une valeur. Il existe d’excellents articles de blog et ressources sur les AA ( Documentation JMP Sur la conversion d'une liste de tableaux associatifs en table de données Astuce JSL : recherche de valeurs uniques dans une liste ) auquel je fais référence lorsque je travaille avec des AA. Ils couvrent une grande partie du matériel sur les AA et contiennent d'excellents exemples. Pour en revenir à mon problème initial, il m'est venu à l'esprit que le nom de la variable globale et son contenu pourraient probablement être traduits en une paire clé-valeur dans un AA. Alors, étant ma muse du moment, je me demandais si je pouvais utiliser un tableau associatif pour construire un meilleur rappel.
OK, pour vous aider à comprendre ce que je pensais, regardons comment cela se fait normalement. Deux opérations de base sont nécessaires pour implémenter Recall. Lorsque l'utilisateur effectue une action de déclenchement, c'est-à-dire qu'il clique sur « OK », tous les éléments de l'interface graphique, c'est-à-dire les zones de liste, les zones radio, les zones de texte, etc., à rappeler doivent voir leur contenu extrait et stocké d'une manière ou d'une autre. Ensuite, lorsque l'utilisateur clique sur le bouton « Rappeler », le contenu stocké doit être repoussé aux endroits appropriés dans l'interface graphique. Au cœur de ces deux opérations se trouvent une paire de messages JSL : << obtenir() et << définir() . Il en existe différentes versions en fonction de l'élément GUI (ne me demandez pas pourquoi – Je n'ai pas écrit JSL – J'utilise juste ces trucs) mais ce ne sont fondamentalement que des variantes de ces deux-là et font les mêmes choses. L'index de script dans JMP (Aide > Index de script) est un excellent moyen de savoir lequel correspond à quel élément de l'interface graphique.
Ainsi, ma solution basée sur AA doit faire un certain nombre de choses. Premièrement, je dois pouvoir utiliser << obtenir() et << définir() dans toutes leurs permutations. Je dois pouvoir lui dire quels éléments de l'interface graphique je souhaite enregistrer. Et je dois avoir un endroit pour enregistrer ces informations qui ne disparaissent pas une fois le complément exécuté. Cela signifie que le flux de travail révisé impliquerait la création d'un AA à l'aide d'une liste des éléments de l'interface graphique que je souhaite restaurer. J'aurais besoin de mettre cet AA dans l'espace de noms Global ou dans un espace de noms personnalisé (plus d'informations à ce sujet dans une seconde). Alors que je réfléchissais à la construction de l'AA, je suis arrivé à une question : « Si j'utilise les éléments de l'interface graphique comme clés dans mon AA, que devrais-je utiliser pour les valeurs de mes paires clé-valeur ? J'ai réalisé qu'en raison de la façon dont fonctionnent les AA, je pouvais simplement les laisser vides, c'est-à-dire utiliser " Vide() " comme ma valeur, mais cela me semblait être une telle perte d'opportunité pour une raison quelconque. En y réfléchissant davantage, j'ai réalisé que, si je devais mettre les valeurs par défaut des éléments de l'interface graphique dans les paires clé-valeur AA, je pourrais en fait configurer une fonction « Réinitialiser » (qui restaurerait l'interface graphique à sa valeur par défaut). état) en utilisant le même code ! Je pourrais prendre en charge deux fonctionnalités pour le prix d’une ! Et qui n’aime pas un bon deux fers ?
Oui, les espaces de noms sont utiles
Revenons à la question de espaces de noms pour une seconde. Les espaces de noms, pour les non-initiés, peuvent ressembler à cette chose mystique et mystérieuse dont ils entendent parler à plusieurs reprises. Sommet découverte JMP ou occasionnellement sur un article de blog. Laissez-moi faire éclater cette bulle tout de suite – elles ne sont pas si spéciales. Ils sont cependant incroyablement utiles et les meilleurs scripteurs JSL ont appris à les utiliser efficacement. (Pour être clair, je ne me compte pas dans ce groupe, mais je déjeune occasionnellement avec eux au siège de SAS ou au Discovery Summit… alors peut-être que cela compte pour quelque chose.)
Pour vous donner une idée de ce qu'est et de ce que fait un espace de noms, regardons les dictionnaires. Si vous écrivez quelque chose dans Anglais et que vous deviez rechercher un mot, vous utiliseriez un Anglais Dictionnaire. Si vous deviez commencer à travailler Français et que vous deviez rechercher un mot, vous utiliseriez un Français dictionnaire. Parfois, grâce à la conquête normande de 1066, le même mot pouvait apparaître dans les deux dictionnaires mais avec des significations légèrement différentes. Cela ne posera généralement pas de problème car vous disposez du document contexte pour vous aider. Si vous travaillez sur un Français document, vous atteignez le Français dictionnaire – pas de problème. Ce n'est même pas un problème si vous avez un mélange de français et d'anglais dans le même document à condition d'en avoir contexte des indices pour vous aider à déterminer quel dictionnaire utiliser.Qu'est-ce que cela a à voir avec JSL ? Les espaces de noms sont comme des dictionnaires. Ce sont des endroits où vous pouvez placer des variables et des fonctions personnalisées qui donnent à JMP un contexte supplémentaire pour comprendre ce que vous voulez qu'il fasse. Par exemple, dans le STK, j'utilise un nom de fonction annoterFenêtre() à plusieurs endroits. En fait, j'utilise une fonction portant ce nom dans chaque partie du logiciel où je dessine quelque chose sur une fenêtre existante.Au total, j'ai probablement une demi-douzaine de fonctions portant le nom annoterFenêtre() . Le problème est que chaque version fait quelque chose de légèrement différent. Mais comme chacun existe dans son propre espace de noms, cela ne pose jamais de problème. Même si j'ai deux interfaces graphiques différentes ouvertes en même temps. JMP sait que lorsque je travaille dans un espace de noms, j'attends annoterFenêtre() faire une chose et quand je suis dans une autre, je m'attends à ce qu'il fasse autre chose. Cette stratégie a considérablement simplifié le processus de conception de l'interface graphique dans le STK. Ainsi, les espaces de noms vous permettent de fournir contexte pour que JMP sache quelle version d'une fonction vous souhaitez qu'il utilise dans un script particulier. Ils peuvent également exister en mémoire après la fermeture du complément qui les a créés. Et ce sont ces deux points qui les rendent particulièrement utiles à notre solution Recall.
Il est temps de coder
OK, assez de théorie – piratons du code. D’ailleurs, le reste aura plus de sens si je l’explique au fur et à mesure. Premièrement, je préconise l'utilisation de bibliothèques dans mes scripts, c'est la meilleure pratique dans la plupart des situations de développement. La plupart des langages de programmation peuvent le faire – JSL le peut aussi. Ainsi, ma solution Recall sera construite sous la forme d’une bibliothèque. Pour utiliser la bibliothèque dans une application ou un script, le développeur doit simplement utiliser un inclure() argument au début du script avec un chemin de fichier vers la bibliothèque.
Deuxièmement, je suis fan des fonctions bavardes. C'est vraiment pratique d'avoir le script vous indiquant ce qu'il fait à chaque étape du processus. Mais je veux aussi pouvoir dire à mes fonctions de chat de fermer quand j'ai fini de tester et de coder. (Les fonctions bavardes peuvent ralentir l'exécution de votre code si vous ne faites pas attention). Si tu regardes la bibliothèque actuelle dans File Exchange, vous verrez beaucoup d'écriture () et montrer() arguments. Ceux-ci sont contenus dans un Si(verbeux == 1, <…>) déclaration qui me permet de les désactiver en passant un verbeux = 0 commande à la fonction. J'utilise beaucoup cette construction dans mes scripts, et je mets généralement simplement le verbeux variable en haut du script afin que je puisse activer et désactiver la journalisation détaillée pour l'ensemble du script en ajustant un paramètre. Pour garder le code court ici, je les ai supprimés des extraits ci-dessous. La bibliothèque complétée se trouve dans le Bibliothèque de fonctions de rappel .
La première fonction de la bibliothèque s'appelle genArrays() . Cette fonction crée un AA à partir d'une liste d'éléments dans l'interface graphique et d'une liste de leurs valeurs par défaut. La fonction crée en fait deux copies de l'AA – une pour prendre en charge les fonctions de rappel, appelée ::Paramètres utilisateur , et un pour prendre en charge les fonctions de réinitialisation, appelé ::Par défaut . Ces deux tableaux sont enregistrés dans l'espace de noms Global. Le " :: " L'opérateur " devant le nom d'une variable fait cela. L'autre chose à remarquer est que j'utilise un Éval(analyser()) structure. Il s'agit d'une stratégie courante pour créer du JSL dynamique qui peut s'avérer fastidieuse en utilisant des schémas de substitution. La dernière chose à remarquer, c'est que je verrouillage la variable des paramètres utilisateur. Cela permet d'éviter que l'AA ou l'une des valeurs ne soit accidentellement écrasée.
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"
)
);
);
Prenons une seconde et regardons ce qui se passe dans les listes utilisées pour construire l'AA. Lorsque je cherchais comment aborder ce problème, j'ai dû faire face au fait qu'il existe plusieurs versions du << obtenir() et << définir() arguments que je devais gérer. Ce que j'ai proposé, c'est d'ajouter un en-tête à la partie clé de la paire clé-valeur dans l'AA. Ce sont une ou deux lettres placées devant la clé (l'élément GUI). Pour les éléments GUI qui utilisent << définir() l'en-tête est s^ . Le << Définir les éléments () et << Définir le texte() utiliser si^ et st^ respectivement. Si j'avais une interface graphique (nous l'appellerons " interface graphique soignée "), avec une zone de texte ( tb1 ), une case numérique ( nb1 ) et une zone de liste ( lb1 ) la liste des objets ressemblerait à ceci :
objects = {st^tb1, s^nb1, si^lb1};
Si nous disions que nous voulions que ces valeurs aient des valeurs spécifiques lors de l'exécution du script ou lorsque l'utilisateur appuie sur un bouton Réinitialiser, nous pourrions configurer le valeurs liste avec ce que nous voulions. je vais utiliser un Vide() commande pour la zone de liste pour effacer le contenu.
values = {“hello world”, 42, Empty()};
Maintenant que nous avons nos deux listes, nous pouvons utiliser le GenArrays() fonction pour générer les tableaux comme ceci :
GenArrays(objects, values, “neatGUI”);
Cela créera les deux AA dans l'espace de noms Global. À la création, ils ressembleraient tous les deux à ceci :
[s^nb1=>42, si^lb1=>Empty(), st^tb1=>”hello world”]
Il ne nous reste plus qu'à fournir aux trois fonctions suivantes le même nom d'interface graphique ( interface graphique soignée ) et il s'occupe du reste ! Regardons ce qu'ils font.
La première des fonctions qui interagit réellement avec l'interface graphique est réinitialiser les rôles() . Cette fonction est essentiellement une boucle qui extrait chaque clé et valeur dans l'AA par défaut et utilise les informations pour réinitialiser les éléments de l'interface graphique aux valeurs par défaut.Puisque les AA sont des listes « spéciales », nous devons utiliser différentes instructions pour travailler avec elles. Ainsi, au lieu d’obtenir simplement la première valeur de l’AA en utilisant un indice, j’utilise le < message. À partir de là, il s'agit simplement de séparer la partie d'en-tête de l'élément GUI dans la clé.Ces pièces sont soumises à une certaine logique, j'utilise donc le droit << définir() message avec la valeur de l’AA pour la clé actuelle. Enfin, tout cela est exécuté en utilisant un autre Éval(analyser()) argument. L'ensemble du processus se répète ensuite pour chaque paire clé-valeur de l'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()
);
,
);
);
Les deux autres fonctions, storeRoles() et rappelRôles() , travaillez de la même manière avec quelques petites différences. Premièrement, ils opèrent tous deux sur le utilisateur AA au lieu du défaut AA créé par GenArrays () . Deuxième, storeRoles() les usages << obtenir() messages et écrase le valeur fait partie de la paire clé-valeur d'un élément de l'interface graphique afin qu'il soit stocké pour un rappel ultérieur. Du code est également inclus pour déverrouiller et reverrouiller l'utilisateur AA pendant l'édition. Enfin, les rôles de rappel () la fonction utilise le << Ajouter() message avec le si^ en-tête au lieu du << définir() message. Plutôt que de mettre le code ici, je vous renvoie au bibliothèque dans l'échange de fichiers .
Ainsi, après quelques réflexions, codage, application libérale de la touche Suppr (retour arrière pour ceux coincés dans le monde Windows… mes condoléances) et un peu plus de codage, j'ai appris qu'il est tout à fait possible de créer un ensemble de fonctions pour prendre en charge le rappel/réinitialisation. en utilisant un tableau associatif. Avec le recul, pour une simple interface graphique à deux ou trois éléments, tout ce processus est, certes, un peu Rube-Goldberg-esque . Mais imaginez une interface graphique avec 10 ou 20 éléments. Cette approche évolue bien mieux que l’approche traditionnelle. Il vous suffit de garder une liste de ce que vous souhaitez rappeler/réinitialiser et de leurs valeurs initiales. Et pour ce faire, vous n'avez même pas besoin d'arborer un fort accent autrichien ou de combattre un gouverneur avide de pouvoir pour le contrôle de Mars.