Outils pour utilisateurs

Outils du site


issue182:python

Being from Texas, most of us here have a tendency to “change horses in midstream” as the saying goes, and this month, I’m afraid I’m going to have to do just that. I was planning to continue my article from last month (FCM 181) on understanding styles and themes for ttk and Tkinter, and in fact started down that path. However, in working with a user of PAGE for the last few days, trying to solve a problem, I realized that there is an issue out there, lurking and waiting to bite every programmer in their proverbial backsides who uses images and Tkinter in their program creation process. That issue is that we, as programmers, do not know in advance where our end users will run our programs from. Will it be from the Desktop, which is becoming more and more popular? Will it be from the folder that contains the source code? Even if we state how the program should be run, that doesn’t mean that our ultimate end users will pay attention to our requests/demands, and if things don’t fit their expectations, watch out for the complaints. So, in order to try to help us all be mindful of this pending issue, I’ve decided to press pause for a month and continue our discussion on Styles and Themes next month.

Comme la plupart de ceux qui viennent du Texas, nous avons tendance à « changer de cheval en cours de route », comme le dit le proverbe, et ce mois-ci, je crains de devoir le faire. J'avais l'intention de poursuivre mon article du mois dernier (dans le FCM n° 181) sur la compréhension des styles et des thèmes pour ttk et Tkinter, et j'ai en fait commencé sur cette voie. Cependant, en travaillant avec un utilisateur de PAGE ces derniers jours, pour essayer de résoudre un problème, j'ai réalisé qu'il y a un problème, qui rôde en attendant de mordre chaque programmeur dans son derrière proverbial qui utilise des images et Tkinter dans son processus de création de programme. Ce problème est que nous, en tant que programmeurs, ne savons pas à l'avance à partir de quel endroit nos utilisateurs finaux exécuteront nos programmes. Est-ce que ce sera à partir du bureau, ce qui devient de plus en plus populaire ? Sera-t-il lancé à partir du dossier qui contient le code source ? Même si nous indiquons comment le programme doit être exécuté, cela ne signifie pas que nos utilisateurs finaux prêteront attention à nos demandes/exigences, et si les choses ne correspondent pas à leurs attentes, attention aux plaintes. Ainsi, afin d'essayer de nous aider tous à être attentifs à cette question en suspens, j'ai décidé de faire une pause pendant un mois et de poursuivre notre discussion sur les styles et les thèmes le mois prochain.

So what brought up this “issue”? Well, the user was trying to use a demo program that I wrote that “shows off” just a few of the capabilities of the widgets that PAGE supports. These can be broken down into 3 groups. There are the “standard” Tk widgets like standard buttons, labels and so on. Then there are the ttk Themed widgets, like the TButton, TLabel, Treeview, TCombobox, etc. Finally there are the “enhanced” widgets that usually have scrollbars built in, like the ScrolledListbox or the ScrolledText widgets. More times than not, these “enhanced” widgets are based on the “standard” Tk widgets, but a handful are based on a ttk widget. Widgets like Tk Radiobuttons and Checkbuttons have a way to change the look of how the widget appears when the program is run, which can involve using custom graphic images, one for the On state and one for the Off state, in addition to a few extra attribute settings. This allows a “normally ugly and looks like Windows 95” Tkinter program to look clean and fresh and receives a bunch of “Oohs” and “Ahhs” from the end users (and other programmers as well). So when we want to use graphics in our Tkinter programs, we have to be mindful of the location where the program will “live”, and where the ultimate end user will attempt to execute our program from.

Alors, qu'est-ce qui a soulevé ce « problème » ? Eh bien, l'utilisateur essayait d'utiliser un programme de démonstration que j'ai écrit et qui « montre avec brillance » seulement quelques-unes des capacités des widgets que PAGE supporte. Ceux-ci peuvent être divisés en 3 groupes. Il y a les widgets Tk « standards » comme les boutons standards, les étiquettes et ainsi de suite. Ensuite, il y a les widgets thématiques de Ttk, comme TButton, TLabel, Treeview, TCombobox, etc. Enfin, il y a les widgets « améliorés » qui intègrent généralement des barres de défilement, comme les widgets ScrolledListbox ou ScrolledText. La plupart du temps, ces widgets « améliorés » sont basés sur les widgets Tk « standards », mais certains sont basés sur un widget Ttk. Les widgets tels que les Radiobuttons et Checkbuttons de Tk peuvent modifier l'apparence du widget lorsque le programme est exécuté, ce qui peut impliquer l'utilisation d'images graphiques personnalisées, une pour l'état On et une pour l'état Off, en plus de quelques paramètres d'attributs supplémentaires. Cela permet à un programme Tkinter « normalement laid et ressemblant à Windows 95 » d'avoir un aspect propre et frais et de recevoir un tas de « Oh » et de « Ah » de la part des utilisateurs finaux (et des autres programmeurs également). Ainsi, lorsque nous voulons utiliser des graphiques dans nos programmes Tkinter, nous devons être attentifs à l'endroit où le programme « vivra », et où l'utilisateur final tentera d'exécuter notre programme.

I had run into this a long time ago when I created a program for my own use, never really expecting to share it with anyone. The program uses a large number of graphic files, all .png files which are located in a sub-folder of the main source code. When the program was executed directly from the source code folder, everything worked just fine. When I tried to create a link from the Desktop that would call Python with the full path of the source code, it failed. I finally tracked it down to the path to the graphic files “couldn’t be found”. I tried a number of solutions without success until I stumbled upon a somewhat messy fix, which was to create a complete and fully qualified path which started with “/home/greg/…”. Of course, I hardcoded this in my early development, to save time and energy and eventual hair loss from my pulling them out in great handfuls. I went on with my development, not really thinking of the fact that by doing it that way, not only could I not share the program with anyone, but I couldn’t even move the program from its hard-coded location. My mind, at the time, was focused on getting the thing to work, not the sloppy programming methods I was employing. Other things came up and development of this program, which worked for the limited things I wanted to accomplish, was shelved and all of what I had learned went on that shelf as well.

J'ai rencontré ce problème il y a longtemps, lorsque j'ai créé un programme pour mon usage personnel, sans vraiment penser le partager avec qui que ce soit. Le programme utilise un grand nombre de fichiers graphiques, tous des fichiers .png qui se trouvent dans un sous-dossier du code source principal. Lorsque le programme était exécuté directement à partir du dossier du code source, tout fonctionnait parfaitement. Lorsque j'ai essayé de créer un lien à partir du bureau qui appellerait Python avec le chemin complet du code source, cela a échoué. J'ai finalement trouvé que le chemin des fichiers graphiques était « introuvable ». J'ai essayé un certain nombre de solutions sans succès jusqu'à ce que je tombe sur une solution un peu compliquée, qui consistait à créer un chemin complet et entièrement qualifié commençant par « /home/greg/… ». Bien sûr, j'ai codé cela en dur au début de mon développement, pour économiser du temps et de l'énergie, ainsi qu'une éventuelle perte de cheveux à force de les arracher par poignées. J'ai poursuivi mon développement, sans vraiment penser au fait qu'en procédant de cette manière, non seulement je ne pouvais pas partager le programme avec qui que ce soit, mais je ne pouvais même pas déplacer le programme de son emplacement codé en dur. À l'époque, mon esprit était concentré sur son fonctionnement et non sur les méthodes de programmation bâclées que j'employais. D'autres choses sont arrivées et le développement de ce programme, qui fonctionnait pour les choses limitées que je voulais accomplir, a été mis de côté avec tout ce que j'avais appris aussi.

Jumping back to the current issue, this was one of the problems that was plaguing this user when he tried to run the demo on his Raspberry Pi from his desktop. There was one graphic in the program (while there were a number of other graphics there as well), that was causing the program to fail for him. Actually there were other images that were also a problem, but this was the first one that the program tried to load. After a long troubleshooting session, I finally realized what was going on and asked the user to try to run the program from the source code folder. He had some other issues that prevented it, but when push came to shove, it would run correctly. Trying to remember what the ticklers in my old brain were trying to tell me about the past lessons learned, I attempted to come up with a properly “pythonic” method to dynamically set a path statement that would keep everything happy, no matter where the program was started from, and no matter where it existed. Digging into the dusty resources that I had here and out on the Internet about the issue, I threw together a very quick and VERY dirty demonstration that not only showed the issue but presented some helpful information (albeit redundant in nature) to someone wondering about a quick but clean method around the problem.

Pour en revenir à la question actuelle, c'était l'un des problèmes qui se posait à cet utilisateur lorsqu'il essayait d'exécuter la démo sur son Raspberry Pi à partir de son bureau. Il y avait un fichier graphique dans le programme (et il y avait un certain nombre d'autres fichiers graphiques), qui causait l'échec de son programme. En fait, d'autres images posaient également problème, mais celle-ci était la première que le programme essayait de charger. Après une longue session de dépannage, j'ai finalement compris ce qui se passait et j'ai demandé à l'utilisateur d'essayer d'exécuter le programme à partir du dossier du code source. D'autres problèmes l'en empêchaient, mais en fin de compte, le programme s'est exécuté correctement.

En essayant de me souvenir de ce que les chatouilles de mon vieux cerveau essayaient de me dire à propos des leçons apprises dans le passé, j'ai essayé de trouver une méthode proprement « pythonique » pour définir dynamiquement une déclaration de chemin d'accès qui rendrait tout le monde heureux, quel que soit le point de départ du programme et quel que soit son emplacement. En creusant dans les ressources poussiéreuses que j'avais ici et sur Internet à propos de ce problème, j'ai mis au point une démonstration très rapide et très sale qui non seulement montrait le problème mais présentait des informations utiles (bien que redondantes par nature) à quelqu'un qui s'interrogeait sur une méthode rapide mais propre pour contourner le problème.

The Tool Since I was trying to create a GUI tool to easily point out the issue, of course I created a quick GUI in PAGE, not really trying to adhere to good GUI creation concepts. I decided on 6 different (but very similar) Python library calls to help determine the information that will need to be presented in order to run a Python script not only from the source code folder, but also from /home and /Desktop, and wherever else anyone could possibly think of to attempt to run the program. Of course, the GUI would need to provide a quick graphic representation showing either the success or failure of loading an image into a Tkinter widget as well as a quick indication of what each of the 6 different function calls returned, so I (or anyone else) could quickly decide on a solution. When run directly from the source code folder, you can see that no matter which of the 6 system calls I made, they all returned the same information, which, on many levels, was quite a comfort for me. However, when the program is run from /home or /Desktop, the difference is shown.

L'outil

Puisque j'essayais de créer un outil avec une interface utilisateur graphique (GUI) pour indiquer le problème facilement, j'ai, bien sûr, rapidement créé une GUI en PAGE, sans vraiment essayer d'adhérer aux bons concepts de création d'une GUI.

J'ai opté pour 6 appels de bibliothèque Python différents (mais très similaires) pour aider à déterminer les informations qui devront être présentées afin d'exécuter un script Python non seulement à partir du dossier du code source, mais aussi à partir de /home et /Bureau, et de partout où l'on pourrait penser à essayer d'exécuter le programme.

Bien sûr, l'interface graphique devrait fournir une représentation graphique rapide montrant le succès ou l'échec du chargement d'une image dans un widget Tkinter ainsi qu'une indication rapide de ce que chacun des 6 appels de fonction différents a retourné, afin que je (ou n'importe qui d'autre) puisse rapidement décider d'une solution.

Lorsqu'il est exécuté directement à partir du dossier du code source, vous pouvez voir que, quel que soit celui des 6 appels système que j'ai faits, ils ont tous retourné la même information, ce qui, à bien des égards, m'a réconforté. Cependant, lorsque le programme est exécuté depuis /home ou /Bureau, des différences apparaîssent.

Three of the system calls ended up returning the same information, and the other three returned a different result, but all three of those were consistent in their returned data. Here (previous page, top right) is a quick look and description of each. Honestly there are dozens more ways out there to do this, so if you want to try others, feel free. I’m just here to get the thought processes flowing. So there are 4 functions from the os.path library and 2 from the pathlib library. When the program is run from a location other than the source folder, the three that return just the location of where the program is run from are: os.path.abspath(filename) pathlib.Path().absolute() os.path.abspath(os.getcwd())

Trois des appels système ont fini par renvoyer les mêmes informations, et les trois autres ont renvoyé un résultat différent, mais tous trois étaient cohérents dans les données qu'ils renvoyaient.

Voici (page précédente, en haut à droite) un aperçu rapide et une description de chacun d'entre eux. Honnêtement, il existe des dizaines d'autres façons de faire, alors si vous voulez en essayer d'autres, n'hésitez pas. Je suis juste là pour faire circuler les idées.

Il y a donc 4 fonctions de la bibliothèque os.path et 2 de la bibliothèque pathlib. Lorsque le programme est exécuté à partir d'un emplacement autre que le dossier source, les trois qui renvoient seulement l'emplacement d'où le programme est exécuté sont :

os.path.abspath(nom du fichier)

pathlib.Path().absolute()

os.path.abspath(os.getcwd())

This means that for this specific use case, these three system calls can’t be used for our purposes. Of the remaining three choices, any one of them returns the full path to our source folder, which is what we really want. We can always assign this to a variable for the path and append the path to our local image folder and the filename. The Code Since this is a PAGE program and a very simple one at that, I decided to put all of the code in a function called “startup” which will get run just before the GUI is actually shown to the user. This includes trying to load both of the images as well as obtaining the path from the 6 system calls and loading a Text widget and the two dynamic text labels. The call to the startup function is the next-to-last line in the main function that PAGE provides. Of course, we have to import the os and pathlib libraries into our program.

Cela signifie que, pour ce cas d'utilisation spécifique, ces trois appels système ne peuvent pas être utilisés pour nos besoins.

Parmi les trois choix restants, chacun d'entre eux renvoie le chemin d'accès complet à notre dossier source, ce qui est ce que nous voulons. Nous pouvons toujours l'affecter à une variable pour le chemin d'accès et ajouter le chemin d'accès à notre dossier d'images local et le nom du fichier.

Le code

Puisqu'il s'agit d'un programme PAGE et d'un programme très simple, j'ai décidé de placer tout le code dans une fonction appelée « startup » qui sera exécutée juste avant que l'interface graphique ne soit montrée à l'utilisateur. Il s'agit d'essayer de charger les deux images, d'obtenir le chemin d'accès à partir des 6 appels système et de charger un widget Texte et les deux étiquettes de texte dynamiques. L'appel à la fonction startup est l'avant-dernière ligne de la fonction principale fournie par PAGE.

Bien sûr, nous devons importer les bibliothèques os et pathlib dans notre programme.

def startup(): import os, pathlib At this point, I assign a variable name (somewhat explicit to what it is) from each of the 6 system calls, and then print the value of each of the variables using a f-string formatted string (top right). After looking at the results when I ran it from /Desktop, I decided on using these two variables to provide what the program thinks is the proper information (localpath and abspath1). The localpath variable (localpath = os.path.dirname(os.path.abspath(file))) is the information we will eventually use. But I’m getting ahead of myself and have spoiled the surprise. Anyway, I then use the .set() method of the two text labels in the form. # Load the two labels with the paths _w1.LocalPath.set(localpath) _w1.ProperPath.set(abspath1)

def startup():

  import os, pathlib

À ce stade, j'assigne un nom de variable (quelque peu explicite sur son rôle) à partir de chacun des 6 appels système, puis j'imprime la valeur de chacune des variables en utilisant une chaîne de caractères au format f-string (en haut à droite).

Après avoir regardé les résultats lorsque je l'ai lancé depuis /Bureau, j'ai décidé d'utiliser ces deux variables pour fournir ce que le programme pense être l'information appropriée (localpath et abspath1). La variable localpath (localpath = os.path.dirname(os.path.abspath(file))) est l'information que nous allons finalement utiliser. Mais je m'avance un peu et j'ai gâché la surprise. Quoi qu'il en soit, j'utilise ensuite la méthode .set() des deux étiquettes de texte dans le formulaire.

# Charger les deux étiquettes avec les chemins

_w1.LocalPath.set(localpath)

_w1.ProperPath.set(abspath1)

At this point, I can define the image name, which includes the path (located from the source directory) and filename. # define the image name imgname = '/images/icons/document.png' Now, we can try to load the images into the two Label widgets that we use to show the graphics. I say “try” since I know that if the program is run from /Desktop, it will fail. We’ll trap that failure and show a messagebox with an error, just to be kind (code shown next page, top right). Please notice that I didn’t worry about any possible errors with the “localpath” , since it’s a fully qualified path and as long as: • I type in the correct graphic filename and • The graphic exists, then there won’t be a problem loading that graphic. I know that I’m being overly optimistic, but that’s just me.

A ce stade, je peux définir le nom de l'image, qui comprend le chemin (situé depuis le répertoire source) et le nom du fichier.

# définir le nom de l'image

imgname = '/images/icons/document.png'

Maintenant, nous pouvons essayer de charger les images dans les deux widgets Label que nous utilisons pour afficher les graphiques. Je dis « essayer » car je sais que si le programme est exécuté depuis /Bureau, il échouera. Nous piégerons cet échec et afficherons une boîte de message avec une erreur, juste pour être gentil (code montré page suivante, en haut à droite).

Remarquez que je ne me suis pas soucié d'éventuelles erreurs avec « localpath »“, puisqu'il s'agit d'un chemin entièrement qualifié et tant que : ••je saisis le nom de fichier correct du graphique et que ••le graphique existe, alors il n'y aura pas de problème pour charger ce graphique. Je sais que je suis trop optimiste, mais c'est juste moi.

The last thing I do is load the ScrolledText widget with the resulting outputs of each of the variables. We have to use the .insert() method, with the position that we want to add the text and then the data we want to insert. Since each line ends with a “\n”, and since the Text widget remembers the last place something was placed, it’s just an easy job to use the Tk.END (PAGE now imports the Tk.Constants module) to define the “where”, and the f-string formatted data as the “what”, and let Tkinter deal with the “how” (shown bottom left). That’s it. So the bottom line is: if you are going to combine Tkinter and Graphics and Python, you probably should define the fully qualified path as the output from a call to os.path.dirname(os.path.abspath(file)) .

La dernière chose que je fais est de charger le widget ScrolledText avec les résultats de chacune des variables. Nous devons utiliser la méthode .insert(), avec la position à laquelle nous voulons ajouter le texte, puis les données que nous voulons insérer. Puisque chaque ligne se termine par « \n », et puisque le widget Text se souvient du dernier endroit où quelque chose a été placé, il est facile d'utiliser le module Tk.END (PAGE importe maintenant le module Tk.Constants) pour définir le « où », et les données formatées en f-string comme le « quoi », et laisser Tkinter s'occuper du « comment » (montré en bas à gauche).

C'est tout. Au bout du compte : si vous allez combiner Tkinter et Graphics et Python, vous devriez probablement définir le chemin entièrement qualifié comme la sortie d'un appel à os.path.dirname(os.path.abspath(file)) .

Quick Update I did a quick writeup to show the results to the user as well as Don (The author of PAGE). Don responded quickly with a fix in the form of a new alpha version of PAGE for me to test. The result of this new cut provides yet another option for PAGE users (and can be used by other Python programmers as well. In PAGE, any embedded graphics included at design time are handled in the GUI file. His fix looks something like this… _script = sys.argv[0] _location = os.path.dirname(_script) Since this is a global variable by default, the _location variable is available to the rest of the project as projectname._location. This makes it simple to handle images within the _support module. If you wish to create a simple global for an image path in your project, you can do something like this… location = test1._location global ImageDir ImageDir = os.path.join(location, “images”, “icons”)

Mise à jour rapide

J'ai fait une rapide mise au point pour montrer les résultats à l'utilisateur ainsi qu'à Don (l'auteur de PAGE). Don a répondu rapidement avec une correction sous la forme d'une nouvelle version alpha de PAGE pour que je puisse la tester. Le résultat de cette nouvelle coupe fournit encore une autre option pour les utilisateurs de PAGE (et peut être utilisé par d'autres programmeurs Python également). Dans PAGE, tout fichier graphique intégré inclus au moment de la conception est géré dans le fichier GUI. Son correctif ressemble à quelque chose comme ceci : _script = sys.argv[0]

_location = os.path.dirname(_script)

Comme il s'agit d'une variable globale par défaut, la variable _location est disponible pour le reste du projet en tant que projectname._location. Cela simplifie la gestion des images dans le module _support. Si vous souhaitez créer une simple variable globale pour un chemin d'accès à une image dans votre projet, vous pouvez faire quelque chose comme ceci :

  location = test1._location
  global ImageDir
  ImageDir = os.path.join(location, "images", "icons")
  

Then when you need to assign an image, you can use a simple definition anywhere within the project _support file. my_file = os.path.join(ImageDir, “folder.png”) This is not limited to PAGE or images. Assume you want to use a database in your Python script. You must point the program at the database file. By using this method, it gives you a quick way to set the path to the database without worrying about where the user is running your program from (image is shown on the next page, top left). I have added my project and code to my github repository at https://github.com/gregwa1953/FCM-182-Python . I promise I will REALLY try to continue the Tkinter Styles and Themes discussion next month! Until next time, as always; stay safe, healthy, positive and creative!

Ensuite, lorsque vous avez besoin d'assigner une image, vous pouvez utiliser une simple définition n'importe où dans le fichier _support du projet.

mon_fichier = os.path.join(ImageDir, “dossier.png”)

Cela n'est pas limité aux PAGE ou aux images. Supposons que vous vouliez utiliser une base de données dans votre script Python. Vous devez faire pointer le programme vers le fichier de la base de données. En utilisant cette méthode, vous disposez d'un moyen rapide de définir le chemin d'accès à la base de données sans vous soucier de l'endroit d'où l'utilisateur exécute votre programme (l'image est présentée sur la page suivante, en haut à gauche).

J'ai ajouté mon projet et mon code à mon dépôt github à https://github.com/gregwa1953/FCM-182-Python.

Je promets que je vais VRAIMENT essayer de continuer la discussion sur les Styles et Thèmes de Tkinter le mois prochain !

Jusqu'à la prochaine fois, comme toujours : restez en sécurité, en bonne santé, positif et créatif !

issue182/python.txt · Dernière modification : 2022/06/30 15:51 de andre_domenech