issue175:python
Différences
Ci-dessous, les différences entre deux révisions de la page.
Les deux révisions précédentesRévision précédenteProchaine révision | Révision précédente | ||
issue175:python [2021/11/30 18:00] – d52fr | issue175:python [2021/12/03 14:47] (Version actuelle) – andre_domenech | ||
---|---|---|---|
Ligne 4: | Ligne 4: | ||
The author’s blog is at https:// | The author’s blog is at https:// | ||
+ | |||
+ | Il y a quelques semaines, l'un de mes fils d' | ||
+ | |||
+ | J'ai pensé que c' | ||
+ | |||
+ | Le blog de l' | ||
+ | |||
**We’ll be using two external Python libraries. You might already have them. They are requests and PIL (pillow). If you don’t have them, you can use pip to install them. They have to be on your system before you try to run the project. | **We’ll be using two external Python libraries. You might already have them. They are requests and PIL (pillow). If you don’t have them, you can use pip to install them. They have to be on your system before you try to run the project. | ||
Ligne 12: | Ligne 19: | ||
Just in case you don’t have PAGE, you can get it at https:// | Just in case you don’t have PAGE, you can get it at https:// | ||
+ | |||
+ | Nous utiliserons deux bibliothèques Python externes. Vous les avez peut-être déjà. Il s'agit de requests et PIL (pillow). Si vous ne les avez pas, vous pouvez utiliser pip pour les installer. Elles doivent être sur votre système avant que vous n' | ||
+ | |||
+ | Voici à quoi ressemble ma version de son projet pendant une session qui tourne. | ||
+ | |||
+ | J'ai décidé de faire un peu plus d' | ||
+ | |||
+ | Au cas où vous n' | ||
+ | |||
**Let’s look at some of the requirements that we need to keep in mind during the design process. The images that come in from the Internet will be 704x480 pixels. The shortest time between the images is 45 seconds. This information came from the author' | **Let’s look at some of the requirements that we need to keep in mind during the design process. The images that come in from the Internet will be 704x480 pixels. The shortest time between the images is 45 seconds. This information came from the author' | ||
+ | |||
+ | Examinons quelques-unes des exigences que nous devons garder à l' | ||
+ | |||
**So start up PAGE, and resize the default Toplevel designer window to about 777 pixels wide and 657 high. Don’t be too concerned about the actual dimensions of the main form, just get it close. Now move the form to somewhere in the middle of your screen. Set the title attribute in the Attribute Editor to “NasaStills-tk”. While we are in the Attribute Editor, set the background color to “skyblue4”. This is a nice dark bluish gray color. | **So start up PAGE, and resize the default Toplevel designer window to about 777 pixels wide and 657 high. Don’t be too concerned about the actual dimensions of the main form, just get it close. Now move the form to somewhere in the middle of your screen. Set the title attribute in the Attribute Editor to “NasaStills-tk”. While we are in the Attribute Editor, set the background color to “skyblue4”. This is a nice dark bluish gray color. | ||
Ligne 20: | Ligne 39: | ||
Now we will finish the placement of the Frame and Label combo. PAGE has a feature that allows you to select multiple widgets and manipulate them as a group, like centering horizontally or vertically, equally spacing them within their parent, and so on. At this point, we want to just center the frame within the main form horizontally. In the Widget Tree, use the middle mouse button to click on the Frame: Frame1 entry.** | Now we will finish the placement of the Frame and Label combo. PAGE has a feature that allows you to select multiple widgets and manipulate them as a group, like centering horizontally or vertically, equally spacing them within their parent, and so on. At this point, we want to just center the frame within the main form horizontally. In the Widget Tree, use the middle mouse button to click on the Frame: Frame1 entry.** | ||
+ | |||
+ | Lancez donc PAGE, et redimensionnez la fenêtre par défaut du concepteur Toplevel à environ 777 pixels de large et 657 de haut. Ne vous préoccupez pas trop des dimensions réelles du formulaire principal, il suffit qu' | ||
+ | |||
+ | Ensuite, nous allons placer un widget de cadre dans le formulaire Toplevel. Il contiendra le widget Label que nous utiliserons pour afficher l' | ||
+ | |||
+ | Nous allons maintenant terminer le placement de la combinaison Cadre et Étiquette. PAGE dispose d'une fonction qui vous permet de sélectionner plusieurs widgets et de les manipuler en tant que groupe, par exemple en les centrant horizontalement ou verticalement, | ||
+ | |||
**Notice that it turns green. The black resize handles of the Frame in the main form also turn green to let you know you are in the multi-select mode. We need to select only the Frame, since it is the parent of the image label. Now right-click on the Frame/Image combo in the main form. You will see a context menu appear. | **Notice that it turns green. The black resize handles of the Frame in the main form also turn green to let you know you are in the multi-select mode. We need to select only the Frame, since it is the parent of the image label. Now right-click on the Frame/Image combo in the main form. You will see a context menu appear. | ||
Ligne 28: | Ligne 54: | ||
I’ll give you the attributes you will want to set in the grid below.** | I’ll give you the attributes you will want to set in the grid below.** | ||
+ | |||
+ | Remarquez qu'il devient vert. Les poignées de redimensionnement noires du cadre dans le formulaire principal deviennent également vertes pour vous indiquer que vous êtes en mode de sélection multiple. Nous devons sélectionner uniquement le cadre, car il est le parent de l' | ||
+ | |||
+ | Sélectionnez Center Horizontal dans le menu. Le cadre sera maintenant centré horizontalement dans le formulaire. Cliquez sur Unselect MS dans la fenêtre Widget Tree. J'ai réglé le haut de mon cadre à 10 pixels en dessous du haut du formulaire (position Y). Il est temps d' | ||
+ | |||
+ | Nous pouvons maintenant commencer à placer le reste des widgets sur notre formulaire. Nous allons placer le premier des deux boutons de contrôle. Assurez-vous d'en faire un bouton de contrôle Tk, et non un bouton de contrôle ttk Themed. | ||
+ | |||
+ | Dans la grille ci-dessous, je vais vous donner les attributs que vous voudrez définir. | ||
+ | |||
**Some information about these attributes. By setting the Active Background (active bg) and select color attributes, we control the colors when the mouse is over the widget (active bg) as well as the color when the status is Checked (select color). Setting the foreground color to antiquewhite2, | **Some information about these attributes. By setting the Active Background (active bg) and select color attributes, we control the colors when the mouse is over the widget (active bg) as well as the color when the status is Checked (select color). Setting the foreground color to antiquewhite2, | ||
Ligne 37: | Ligne 72: | ||
Remember to make everything line up in the row nicely, and provide a little bit of space between the bottom of the frame and the top of the buttons.** | Remember to make everything line up in the row nicely, and provide a little bit of space between the bottom of the frame and the top of the buttons.** | ||
+ | |||
+ | Quelques informations sur ces attributs. En définissant les attributs Active Background (active bg) et select color, nous contrôlons les couleurs lorsque la souris passe sur le widget (active bg) ainsi que la couleur lorsque l' | ||
+ | |||
+ | Enregistrez à nouveau et nous allons ajouter trois autres boutons. Veillez à ce qu'ils soient bien alignés avec le bouton de contrôle. Le premier sera le bouton Enregistrer, | ||
+ | |||
+ | Ensuite, le bouton Recharger | ||
+ | |||
+ | Enfin, le bouton Quitter. | ||
+ | |||
+ | N' | ||
+ | |||
+ | |||
**Save your project again and we’ll finish up with the last three widgets. First we’ll do another Tk Checkbutton. | **Save your project again and we’ll finish up with the last three widgets. First we’ll do another Tk Checkbutton. | ||
Ligne 47: | Ligne 94: | ||
We’ll start with the imports section.** | We’ll start with the imports section.** | ||
+ | |||
+ | Sauvegardez à nouveau votre projet et nous allons terminer avec les trois derniers widgets. Tout d' | ||
+ | |||
+ | Ensuite, c'est la Spinbox. Vous voudrez lui donner une largeur un peu plus petite que celle qui est proposée par défaut par PAGE. La mienne ne fait que 58 pixels de large. C'est vous qui décidez de sa largeur. Voici les attributs. | ||
+ | |||
+ | Le dernier mais non le moindre, c'est le bouton Set. L' | ||
+ | |||
+ | Sauvegardez votre projet et générez vos modules Python. Ensuite, nous allons travailler sur le code du module _support. | ||
+ | |||
+ | Nous allons commencer par la section des importations. | ||
+ | |||
**import sys | **import sys | ||
Ligne 75: | Ligne 133: | ||
from tkinter import constants** | from tkinter import constants** | ||
+ | |||
+ | import sys | ||
+ | |||
+ | from os.path import exists | ||
+ | |||
+ | from datetime import datetime, timedelta | ||
+ | |||
+ | from pathlib import Path | ||
+ | |||
+ | import requests | ||
+ | |||
+ | from requests.exceptions import Timeout | ||
+ | |||
+ | from PIL import Image, ImageTk | ||
+ | |||
+ | import shutil | ||
+ | |||
+ | Je montre la ligne import sys en maigre (pas en gras), puisque PAGE l'a déjà fait pour nous. | ||
+ | |||
+ | Nous aurons également besoin d' | ||
+ | |||
+ | from tkinter import Spinbox, messagebox | ||
+ | |||
+ | from tkinter import font | ||
+ | |||
+ | from tkinter import filedialog | ||
+ | |||
+ | from tkinter import constants | ||
Ligne 96: | Ligne 182: | ||
The next function in the file should be the init function. PAGE creates this function for us, and again, we need to make a one line addition to it. I’ll put it in bold.** | The next function in the file should be the init function. PAGE creates this function for us, and again, we need to make a one line addition to it. I’ll put it in bold.** | ||
+ | |||
+ | Si vous faites défiler le fichier un peu plus bas, il y a une fonction appelée set_Tk_var(). Nous allons devoir apporter une petite modification à cette fonction, et je vais donc la montrer en gras ci-dessous. En gros, elle fixe la valeur de spinbox à 45 par défaut au démarrage. | ||
+ | |||
+ | def set_Tk_var() : | ||
+ | |||
+ | global spinbox | ||
+ | |||
+ | spinbox = tk.StringVar() | ||
+ | |||
+ | spinbox.set(' | ||
+ | |||
+ | global che47 | ||
+ | |||
+ | che47 = tk.IntVar() | ||
+ | |||
+ | global che48 | ||
+ | |||
+ | che48 = tk.IntVar() | ||
+ | |||
+ | La fonction suivante dans le fichier doit être la fonction init. PAGE crée cette fonction pour nous et, encore une fois, nous devons faire un ajout d'une ligne dans cette fonction. Je vais la mettre en gras. | ||
Ligne 113: | Ligne 219: | ||
A couple of things you might want to be aware of. We have set the checkboxes to unchecked by using the che48.set(0) and che47.set(0). We also set the spinbox to be disabled at startup. When the user checks the “Auto-reload” checkbox, we’ll set it back to a normal state. We also create (if it doesn’t exist) a very simple text file, which will hold the number of the last saved image file.** | A couple of things you might want to be aware of. We have set the checkboxes to unchecked by using the che48.set(0) and che47.set(0). We also set the spinbox to be disabled at startup. When the user checks the “Auto-reload” checkbox, we’ll set it back to a normal state. We also create (if it doesn’t exist) a very simple text file, which will hold the number of the last saved image file.** | ||
+ | |||
+ | def init(top, gui, *args, **kwargs) : | ||
+ | |||
+ | global w, top_level, root | ||
+ | |||
+ | w = gui | ||
+ | |||
+ | top_level = top | ||
+ | |||
+ | root = top | ||
+ | |||
+ | startup() | ||
+ | |||
+ | La fonction startup (à droite) est exécutée juste avant que l' | ||
+ | |||
+ | Il y a deux choses que vous devez savoir. Nous avons défini les cases à cocher comme non cochées en utilisant les variables che48.set(0) et che47.set(0). Nous avons également configuré la spinbox pour qu' | ||
+ | |||
**def on_chkAspect(): | **def on_chkAspect(): | ||
Ligne 141: | Ligne 264: | ||
root.update()** | root.update()** | ||
+ | | ||
+ | def on_chkAspect() : | ||
+ | |||
+ | # print(' | ||
+ | |||
+ | # sys.stdout.flush() | ||
+ | |||
+ | pass | ||
+ | |||
+ | Nous n' | ||
+ | |||
+ | def on_chkTime() : | ||
+ | |||
+ | # print(' | ||
+ | |||
+ | # sys.stdout.flush() | ||
+ | |||
+ | if che48.get() == 1: | ||
+ | |||
+ | w.Spinbox1.configure(state=tk.NORMAL) | ||
+ | |||
+ | else: | ||
+ | |||
+ | w.Spinbox1.configure(state=tk.DISABLED) | ||
+ | |||
+ | refresh_time = spinbox.get() | ||
+ | |||
+ | root.update() | ||
+ | | ||
**Here is the chkTime function I told you about. We check if the checkbox variable is equal to 1. If so, we set the spinbox state to normal. Otherwise set it to disabled. | **Here is the chkTime function I told you about. We check if the checkbox variable is equal to 1. If so, we set the spinbox state to normal. Otherwise set it to disabled. | ||
Ligne 155: | Ligne 307: | ||
The on_btnReload function is the callback function that will reload the image. It calls the get_image_from_web function. If you click the Aspect checkbutton, | The on_btnReload function is the callback function that will reload the image. It calls the get_image_from_web function. If you click the Aspect checkbutton, | ||
+ | |||
+ | Voici la fonction chkTime dont je vous ai parlé. Nous vérifions si la variable checkbox est égale à 1. Si oui, nous mettons l' | ||
+ | |||
+ | def on_btnExit() : | ||
+ | |||
+ | # print(' | ||
+ | |||
+ | # sys.stdout.flush() | ||
+ | |||
+ | destroy_window() | ||
+ | |||
+ | Le on_btnExit est le callback pour le bouton Exit. Il appelle simplement la fonction destroy_window créée par PAGE pour fermer le programme correctement. | ||
+ | |||
+ | La fonction on_btnReload est la fonction de rappel qui va recharger l' | ||
+ | |||
**def on_btnReload(): | **def on_btnReload(): | ||
Ligne 169: | Ligne 336: | ||
The on_btnSet callback function is not needed if you decided not to include the Set button. If you did, and followed the instructions, | The on_btnSet callback function is not needed if you decided not to include the Set button. If you did, and followed the instructions, | ||
+ | |||
+ | def on_btnReload() : | ||
+ | |||
+ | # print(' | ||
+ | |||
+ | # sys.stdout.flush() | ||
+ | |||
+ | global feed_url | ||
+ | |||
+ | get_image_from_web(feed_url) | ||
+ | |||
+ | La fonction de rappel on_btnSave (en haut à droite) va tenter d' | ||
+ | |||
+ | La fonction de rappel on_btnSet n'est pas nécessaire si vous avez décidé de ne pas inclure le bouton Set. Si vous l'avez fait, et que vous avez suivi les instructions, | ||
+ | |||
**def on_btnSet(): | **def on_btnSet(): | ||
Ligne 189: | Ligne 371: | ||
refresh_time = spinbox.get()** | refresh_time = spinbox.get()** | ||
+ | | ||
+ | def on_btnSet() : | ||
+ | |||
+ | # print(' | ||
+ | |||
+ | # sys.stdout.flush() | ||
+ | |||
+ | showinfo(" | ||
+ | |||
+ | La fonction de rappel on_spinChange se déclenche chaque fois que la spinbox est incrémentée ou décrémentée. Elle définit simplement la variable globale refresh_time. C'est un artefact d'une version antérieure que j'ai faite, puisque la fonction de minuterie on_tick obtient actuellement la valeur directement de la spinbox pour définir la prochaine valeur de la minuterie. Je l'ai fourni ici juste comme un exemple de comment obtenir la valeur de la spinbox. | ||
+ | |||
+ | def on_spinChange() : | ||
+ | |||
+ | # print(' | ||
+ | |||
+ | # sys.stdout.flush() | ||
+ | |||
+ | global refresh_time | ||
+ | |||
+ | refresh_time = spinbox.get() | ||
+ | | ||
**The get_image_from_web function (next page, top right) is where the first part of the “magic” happens. We use the requests.get method from the requests library to grab an image from a website. The image is received and saved as a .png image. Then, we verify the width and height of the image. If it is the correct size (704x480), and the aspect ratio checkbutton is not checked, then it is simply saved as a local image. If the aspect ratio checkbutton IS checked, then the PIL library resizes to the 16x9 format before it gets saved. Finally, we set the image attribute of the Label to the image we just downloaded. We also save it to a convenient local file, in case the user wants to save it. | **The get_image_from_web function (next page, top right) is where the first part of the “magic” happens. We use the requests.get method from the requests library to grab an image from a website. The image is received and saved as a .png image. Then, we verify the width and height of the image. If it is the correct size (704x480), and the aspect ratio checkbutton is not checked, then it is simply saved as a local image. If the aspect ratio checkbutton IS checked, then the PIL library resizes to the 16x9 format before it gets saved. Finally, we set the image attribute of the Label to the image we just downloaded. We also save it to a convenient local file, in case the user wants to save it. | ||
The on_tick function (page after next, top right) is where the rest of the “magic” happens, in my mind at least. Here we use the root.after function of Tkinter to create a timer event. The first thing we do is to get the current value of the spinbox (between 45 and 300) and store it in a temporary variable rt (standing for refresh time). It then calls the get_image_from_web function to get the image and refresh the Label. Finally the callback is enabled with the time in milliseconds (rt * 1000) and reset the callback for the next call.** | The on_tick function (page after next, top right) is where the rest of the “magic” happens, in my mind at least. Here we use the root.after function of Tkinter to create a timer event. The first thing we do is to get the current value of the spinbox (between 45 and 300) and store it in a temporary variable rt (standing for refresh time). It then calls the get_image_from_web function to get the image and refresh the Label. Finally the callback is enabled with the time in milliseconds (rt * 1000) and reset the callback for the next call.** | ||
+ | |||
+ | La fonction get_image_from_web (page suivante, en haut à droite) est l' | ||
+ | |||
+ | La fonction on_tick (2 pages plus loin, en haut à droite) est l' | ||
+ | |||
**The showinfo function takes two parameters, title and message, then calls the Tkinter message box to show the message box to the user. We also provide the parent (which in this case will always be root, but that will keep the messagebox over the actual application and set the icon to the INFO icon. | **The showinfo function takes two parameters, title and message, then calls the Tkinter message box to show the message box to the user. We also provide the parent (which in this case will always be root, but that will keep the messagebox over the actual application and set the icon to the INFO icon. | ||
Ligne 207: | Ligne 415: | ||
Finally, we have the centre_screen function, which takes the width and height of our main form (which is provided in the GUI python file that PAGE creates). It then uses the screen width and height to calculate the centre of the screen and form.** | Finally, we have the centre_screen function, which takes the width and height of our main form (which is provided in the GUI python file that PAGE creates). It then uses the screen width and height to calculate the centre of the screen and form.** | ||
+ | |||
+ | La fonction showinfo prend deux paramètres, | ||
+ | |||
+ | def showinfo(titl, | ||
+ | |||
+ | messagebox.showinfo(titl, | ||
+ | |||
+ | La fonction showerror est presque exactement la même que la fonction showinfo, mais affiche une boîte de message d' | ||
+ | |||
+ | def showerror(titl, | ||
+ | |||
+ | messagebox.showerror(titl, | ||
+ | |||
+ | Enfin, nous avons la fonction centre_écran, | ||
Ligne 220: | Ligne 442: | ||
root.geometry(' | root.geometry(' | ||
- | |||
- | |||
That’s it. Our project is completed. I feel happy about the functionality that Tkinter provides against the PySimpleGUI toolkit. When I did the first version of the program, it took me only about 30 minutes to design the PAGE GUI form and write or borrow the code snippets from some of my previous programs. The length of the actual support module with comments (LOTS of comments), and double spacing between functions, is only about 230 lines of code, which isn’t too bad. | That’s it. Our project is completed. I feel happy about the functionality that Tkinter provides against the PySimpleGUI toolkit. When I did the first version of the program, it took me only about 30 minutes to design the PAGE GUI form and write or borrow the code snippets from some of my previous programs. The length of the actual support module with comments (LOTS of comments), and double spacing between functions, is only about 230 lines of code, which isn’t too bad. | ||
Ligne 228: | Ligne 448: | ||
Until next time, as always; stay safe, healthy, positive and creative!** | Until next time, as always; stay safe, healthy, positive and creative!** | ||
+ | |||
+ | def centre_screen(wid, | ||
+ | |||
+ | ws = root.winfo_screenwidth() | ||
+ | |||
+ | hs = root.winfo_screenheight() | ||
+ | |||
+ | x = (ws / 2) - (wid / 2) | ||
+ | |||
+ | y = (hs / 2) - (hei / 2) | ||
+ | |||
+ | root.geometry(' | ||
+ | |||
+ | Voilà, c'est fait. Notre projet est terminé. Je suis content que Tkinter soit si fonctionel par rapport à la boîte à outils PySimpleGUI. Quand j'ai fait la première version du programme, il ne m'a fallu qu' | ||
+ | |||
+ | Vous pouvez trouver le code déjà créé pour vous et prêt à être exécuté sur mon dépôt github à l' | ||
+ | |||
+ | Jusqu' |
issue175/python.1638291607.txt.gz · Dernière modification : 2021/11/30 18:00 de d52fr