Les deux révisions précédentesRévision précédenteProchaine révision | Révision précédente |
issue136:python [2018/09/13 12:01] – auntiee | issue136:python [2018/09/17 16:06] (Version actuelle) – andre_domenech |
---|
Il y a quelque temps, j'ai écrit un billet de blog au sujet d'un problème avec Python et Tkinter, car le widget des cases à cocher ne répondait pas comme il fallait à l'événement d'un clic, du moins selon moi. Voici l'histoire : | Il y a quelque temps, j'ai écrit un billet de blog au sujet d'un problème avec Python et Tkinter, car le widget des cases à cocher ne répondait pas comme il fallait à l'événement d'un clic, du moins selon moi. Voici l'histoire : |
| |
Je travaillais sur un widget personnalisé qui implémentait une fenêtre d'une liste cochée défilant pour l'utiliser avec une base de données. Je pensais qu'il serait utile de pouvoir afficher une série d'éléments sélectionnés à partir d'une liste de catégories basée sur un seul enregistrement. Comme d'habitude, j'utilisais Page pour créer l'interface graphique. | Je travaillais sur un widget personnalisé qui implémentait une fenêtre avec une liste cochée défilante pour l'utiliser avec une base de données. Je pensais qu'il serait utile de pouvoir afficher une série d'éléments sélectionnés à partir d'une liste de catégories basée sur un seul enregistrement. Comme d'habitude, j'utilisais Page pour créer l'interface graphique. |
| |
Je voulais que le widget présente un événement chaque fois que n'importe laquelle des cases du widget était cochée, puis affiche une liste de tous les éléments sélectionnés. Sur le moment, cela ne semblait pas si difficile que cela. Cependant, quand l'événement fut déclenché et mon code de rappel se lançait, la dernière case à coché qui était sélectionnée ne paraissait pas sur la liste. Pire encore, si je cliquais sur le même bouton à nouveau, décochant le bouton en fait, c'est alors que ce bouton paraissait sur la liste. Cela ne me paraissait pas du tout logique. J'ai commencé des recherches et j'ai trouvé le problème. | Je voulais que le widget présente un événement chaque fois que n'importe laquelle des cases du widget était cochée, puis affiche une liste de tous les éléments sélectionnés. Sur le moment, cela ne semblait pas si difficile que cela. Cependant, quand l'événement était déclenché et mon code de rappel se lançait, la dernière case à cocher qui était sélectionnée n'apparaissait pas sur la liste. Pire encore, si je cliquais sur le même bouton à nouveau, décochant le bouton en fait, c'est alors que ce bouton apparaissait sur la liste. Cela ne me paraissait pas du tout logique. J'ai commencé des recherches et j'ai trouvé le problème. |
| |
**As you probably know, the Checkbutton widget looks just like any GUI checkbutton. It’s an empty box that, when you click it, it shows a check in the box. If you click it again, the check goes away and the box is blank again. Under Tkinter, the Checkbutton widget provides a variable that you can monitor to show the state of the widget by using the .get() method of the widget. If it returns a “1”, then the checkbutton is checked, and if it returns a “0”, it’s unchecked. Pretty simple. I wanted to use the mouse-click event to be the trigger to check the state of the widget. | **As you probably know, the Checkbutton widget looks just like any GUI checkbutton. It’s an empty box that, when you click it, it shows a check in the box. If you click it again, the check goes away and the box is blank again. Under Tkinter, the Checkbutton widget provides a variable that you can monitor to show the state of the widget by using the .get() method of the widget. If it returns a “1”, then the checkbutton is checked, and if it returns a “0”, it’s unchecked. Pretty simple. I wanted to use the mouse-click event to be the trigger to check the state of the widget. |
Nothing spectacular, but it would help with my snooping. If you have used Page before, you know that you usually bind an event (either mouse or keyboard) to a particular widget. In this case, I set the binding to the <Button-1> event (left mouse button click) for the Checkbutton widget. Then, in the callback function for the event, I printed out the state of the Checkbutton. The code (below) was pretty simple…** | Nothing spectacular, but it would help with my snooping. If you have used Page before, you know that you usually bind an event (either mouse or keyboard) to a particular widget. In this case, I set the binding to the <Button-1> event (left mouse button click) for the Checkbutton widget. Then, in the callback function for the event, I printed out the state of the Checkbutton. The code (below) was pretty simple…** |
| |
Comme vous le savez sans doute, le widget Checkbutton (case à cocher) ressemble à n'importe quelle case à cochée d'une interface graphique. C'est un carré vide qui, quand vous cliquez dessus, affiche une coche dans le carré. Si vous cliquez dessus à nouveau, la coche disparaît et le carré est vide à nouveau. Dans Tkinter, le widget Checkbutton fournit une variable, que vous pouvez surveiller, pour montrer l'état du widget en utilisant la méthode .get() du widget. S'il donne un « 1 », la case est cochée et s'il donne un « 0 », elle est vide. C'est simple. Je voulais me servir de l'événement du clic de la souris pour déclencher la vérification de l'état du widget. | Comme vous le savez sans doute, le widget Checkbutton (case à cocher) ressemble à n'importe quelle case à cocher d'une interface graphique. C'est un carré vide qui, quand vous cliquez dessus, affiche une coche dans le carré. Si vous cliquez dessus à nouveau, la coche disparaît et le carré est vide à nouveau. Dans Tkinter, le widget Checkbutton fournit une variable, que vous pouvez surveiller, pour montrer l'état du widget en utilisant la méthode .get() du widget. S'il donne un « 1 », la case est cochée et s'il donne un « 0 », elle est vide. C'est simple. Je voulais me servir de l'événement du clic de la souris pour déclencher la vérification de l'état du widget. |
| |
Ces jours-ci, je travaille principalement sous Linux ; aussi, c'est le système d'exploitation que j'ai utilisé pour faire le travail de base. J'ai essayé divers contournements, sans pouvoir convaincre le truc de fonctionner comme il devrait. Je suis donc retourné aux bases. J'ai créé une interface graphique simple avec Page et commencé mes explorations. | Ces jours-ci, je travaille principalement sous Linux ; aussi, c'est le système d'exploitation que j'ai utilisé pour faire le travail de base. J'ai essayé divers contournements, sans pouvoir convaincre le truc de fonctionner comme il devrait. Je suis donc retourné aux bases. J'ai créé une interface graphique simple avec Page et commencé mes explorations. |
| |
Cela ressemblait un peu à ceci… | Ça ressemblait un peu à ceci... |
| |
Rien de spectaculaire, mais elle m'aiderait dans mes investigations. Si vous avez déjà utilisé Page, vous savez que, habituellement, vous liez un événement (soit de la souris, soit du clavier) à un widget précis. Ici, pour le widget Checkbutton, j'ai réglé le lien à l'événement <Button-1> (clic sur le bouton gauche de la souris). Puis dans la fonction rappel de l'événement, j'ai imprimé l'état du Checkbutton. Le code (ci-dessous) était assez simple… | Rien de spectaculaire, mais elle m'aiderait dans mes investigations. Si vous avez déjà utilisé Page, vous savez que, habituellement, vous liez un événement (soit de la souris, soit du clavier) à un widget précis. Ici, pour le widget Checkbutton, j'ai réglé le lien à l'événement <Button-1> (clic sur le bouton gauche de la souris). Puis dans la fonction rappel de l'événement, j'ai imprimé l'état du Checkbutton. Le code (ci-dessous) était assez simple... |
| |
**The first two lines are provided by Page as a simple notification that the user has triggered the callback function. I usually leave these in until I’m done with the majority of the early testing. The last line of the function simply shows a ‘1’ or a ‘0’ in the terminal window, to show me what is going on. The thought being that when I click to check the button, I will see a 1 in the terminal. | **The first two lines are provided by Page as a simple notification that the user has triggered the callback function. I usually leave these in until I’m done with the majority of the early testing. The last line of the function simply shows a ‘1’ or a ‘0’ in the terminal window, to show me what is going on. The thought being that when I click to check the button, I will see a 1 in the terminal. |
1** | 1** |
| |
Les deux premières lignes sont fournies par Page comme une notification simple du fait que l'utilisateur a déclenché la fonction de rappel. En général, je les laisse jusqu'à ce que j'ai terminé la plupart des premiers tests. La dernière ligne de la fonction affiche tout simplement un « 1 » ou un « 0 » dans la fenêtre du terminal pour m'indiquer ce qui se passe. L'idée étant que, lors d'un clic pour cocher le bouton, je verrai un 1 dans le terminal. | Les deux premières lignes sont fournies par Page comme une notification simple du fait que l'utilisateur a déclenché la fonction de rappel. En général, je les laisse jusqu'à ce que j'aie terminé la plupart des premiers tests. La dernière ligne de la fonction affiche tout simplement un « 1 » ou un « 0 » dans la fenêtre du terminal pour m'indiquer ce qui se passe. L'idée étant que, lors d'un clic pour cocher le bouton, je verrai un 1 dans le terminal. |
| |
Toutefois, quand j'ai exécuté le programme et cliqué sur le widget, ce qui j'ai vu était… | Toutefois, quand j'ai exécuté le programme et cliqué sur le widget, ce qui j'ai vu était... |
| |
chkbtntest_support.on_ChkBtnClick | chkbtntest_support.on_ChkBtnClick |
I took my code over to a Windows 10 machine and started it up. Knowing that I had gotten it all figured out, I knew it would work just fine, since the code is all very basic (forgive the term) and nothing would go wrong.** | I took my code over to a Windows 10 machine and started it up. Knowing that I had gotten it all figured out, I knew it would work just fine, since the code is all very basic (forgive the term) and nothing would go wrong.** |
| |
Ce n'était pas du tout logique. La valeur n'est pas du tout synchronisée avec l'indicateur visuelle, tout comme dans mon widget personnalisé. Je me suis déjà servi du widget Checkbutton, de très nombreuses fois, et j'ai utilisé la méthode .get() pour demander le status du widget, mais toujours à partir d'un autre événement, notamment d'un bouton « Enregistrer ». | Ce n'était pas du tout logique. La valeur n'est pas du tout synchronisée avec l'indicateur visuel, tout comme dans mon widget personnalisé. Je me suis déjà servi du widget Checkbutton, de très nombreuses fois, et j'ai utilisé la méthode .get() pour demander le status du widget, mais toujours à partir d'un autre événement, notamment d'un bouton « Enregistrer ». |
| |
Ma frustration étant MAXIMALE, j'ai décidé par désespoir d'essayer l'événement <ButtonRelease-1> pour en voir le résultat. Ayant désactivé l'événement clic, j'ai lié l'autre événement au widget. J'ai écrit le même code pour cet événement et, à ma grande surprise, il a fonctionné comme il fallait. Chaque fois que j'ai lâché le bouton de la souris, l'état fut demandé et affiché correctement sur le terminal ! | Ma frustration étant MAXIMALE, j'ai décidé par désespoir d'essayer l'événement <ButtonRelease-1> pour en voir le résultat. Ayant désactivé l'événement clic, j'ai lié l'autre événement au widget. J'ai écrit le même code pour cet événement et, à ma grande surprise, il a fonctionné comme il fallait. Chaque fois que j'ai lâché le bouton de la souris, l'état fut demandé et affiché correctement sur le terminal ! |
| |
Puisque j'avais au moins un contournement, je me sentais bien mieux. J'ai modifié le code de mon widget personnalisé et ça fonctionnait encore ; c'était parfait et j'étais rassuré. J'ai même essayé le code dans Python 2.x et Python 3.x, les deux et il fonctionnait comme je voulais. Cependant, ma joie serait de courte durée. | Puisque j'avais au moins un contournement, je me sentais bien mieux. J'ai modifié le code de mon widget personnalisé et ça fonctionnait encore ; c'était parfait et j'étais rassuré. J'ai même essayé le code dans Python 2.x et dans Python 3.x et il fonctionnait comme je voulais. Cependant, ma joie fut de courte durée. |
| |
J'ai transféré le code sur une machine sous Windows 10 et l'ai démarré. Sachant que j'avais tout compris et rectifié, j'étais certain que cela fonctionnerait bien, puisque le code était très basique (BASIC - désolé) et rien de fâcheux ne pouvait arriver. | J'ai transféré le code sur une machine sous Windows 10 et l'ai démarré. Sachant que j'avais tout compris et rectifié, j'étais certain que cela fonctionnerait bien, puisque le code était très basique (BASIC - désolé) et rien de fâcheux ne pouvait arriver. |
The other day, I get an email from Don Rozenberg, who is the author of Page. Among other things, he said “Under Linux Mint, pressing and holding button-1 on one of the checkboxes causes it to toggle, whereas under Windows the checkbox doesn't toggle until the button-1 is released.”** | The other day, I get an email from Don Rozenberg, who is the author of Page. Among other things, he said “Under Linux Mint, pressing and holding button-1 on one of the checkboxes causes it to toggle, whereas under Windows the checkbox doesn't toggle until the button-1 is released.”** |
| |
À ma très grande surprise, cela n'aurait pas pu être pire. La détection de l'état du widget ne fonctionnait pas sur l'événement de la relâche du bouton de la souris et elle ne fonctionnait pas non plus sur l'événement du clic. Je pouvais demander l'état à partir d'un autre bouton « standard » APRÈS a avoir cliqué sur le Checkbutton, mais je n'arrivais pas à avoir une « mise à jour en temps réel », même en cas de vie ou de mort. Rien que j'ai essayé n'a fonctionné. | À ma très grande surprise, cela n'aurait pas pu être pire. La détection de l'état du widget ne fonctionnait pas sur l'événement de la relâche du bouton de la souris et elle ne fonctionnait pas non plus sur l'événement du clic. Je pouvais demander l'état à partir d'un autre bouton « standard » APRÈS a avoir cliqué sur le Checkbutton, mais je n'arrivais pas à avoir une « mise à jour en temps réel », même en cas de vie ou de mort. Rien de ce que j'ai essayé n'a fonctionné. |
| |
Ayant trouvé après maints efforts un très « sale » contournement sous Windows, j'ai décidé de m'arrêter là. Je voulais toujours savoir ce qui se passait, mais d'autres projets de sont présentés et j'ai donc laissé celui-là de côté pendant un certain temps. Un jour, ayant quelques loisirs, j'ai décidé d'améliorer le widget personnalisé pour qu'il prenne en charge la molette de défilement. La barre de défilement fonctionnait avec la molette, mais, si j'essayais de faire défiler la fenêtre à partir du centre du widget, rien ne se passait. C'était tellement contrintuitif que je ne pouvais pas l'abandonner. Après beaucoup de recherches, j'ai trouvé une façon de le faire. Malheureusement, Linux gère la molette de défilement de la souris d'une façon différente que Windows et je retrouvais en train de créer du code basé sur le système d'exploitation. J'ai à nouveau essayé de récupérer l'état du Checkbutton en « temps réel », mais je n'ai pas du tout progressé. J'ai encore une fois mis le code de côté en pensant y retourner « un jour ». | Ayant trouvé après maints efforts un très « sale » contournement sous Windows, j'ai décidé de m'arrêter là. Je voulais toujours savoir ce qui se passait, mais d'autres projets se sont présentés et j'ai donc laissé celui-là de côté pendant un certain temps. Un jour, ayant un peu de temps, j'ai décidé d'améliorer le widget personnalisé pour qu'il prenne en charge la molette de défilement. La barre de défilement fonctionnait avec la molette, mais, si j'essayais de faire défiler la fenêtre à partir du centre du widget, rien ne se passait. C'était tellement contre-intuitif que je ne pouvais pas l'abandonner. Après beaucoup de recherches, j'ai trouvé une façon de le faire. Malheureusement, Linux gère la molette de défilement de la souris d'une façon différente de Windows et je me retrouvais en train de créer du code basé sur le système d'exploitation. J'ai à nouveau essayé de récupérer l'état du Checkbutton en « temps réel », mais je n'ai pas du tout progressé. J'ai encore une fois mis le code de côté en pensant y retourner « un jour ». |
| |
L'autre jour j'ai reçu un mail de Don Rozenberg, l'auteur de Page. Il a notamment dit : - « Sous Linux Mint, appuyer sur, et maintenir, le button-1 sur l'une des cases à cocher, le fait basculer, alors que, sous Windows, la case ne bascule pas avant que le button-1 ne soit relâché. » | L'autre jour j'ai reçu un mail de Don Rozenberg, l'auteur de Page. Il a notamment dit : « Sous Linux Mint, appuyer sur, et maintenir, le button-1 sur l'une des cases à cocher, le fait basculer, alors que, sous Windows, la case ne bascule pas avant que le button-1 ne soit relâché. » |
| |
**I decided to go back to basics and try to figure out, once and for all, a way to monitor the check state correctly for both operating systems. | **I decided to go back to basics and try to figure out, once and for all, a way to monitor the check state correctly for both operating systems. |
J'ai décidé de revenir aux bases pour essayer de trouver une fois pour toute une façon de surveiller correctement l'état de la coche pour les deux systèmes d'exploitation. | J'ai décidé de revenir aux bases pour essayer de trouver une fois pour toute une façon de surveiller correctement l'état de la coche pour les deux systèmes d'exploitation. |
| |
J'ai recréé mon appli de test de Checkbutton en interface graphique avec Page, et j'étais en train de faire les lens quand je me suis rendu compte que j'avais oublié que le widget Checkbutton a également une commande attribut que vous pouvez utiliser pour répondre aux événements de la souris. La raison principale pour laquelle je ne l'utilise plus est que vous ne pouvez pas passer l'objet événement dans la fonction de rappel, qui pourtant peut être extrêmement utile puisqu'il comprend la position de la souris. Cette fois-ci, cependant, il n'y en avait pas besoin et j'ai décidé donc de l'ajouter au mélange. | J'ai recréé mon appli de test de Checkbutton en interface graphique avec Page, et j'étais en train de coder les liens quand je me suis rendu compte que j'avais oublié que le widget Checkbutton a également un attribut de commande que vous pouvez utiliser pour répondre aux événements de la souris. La raison principale pour laquelle je ne l'utilise plus est que vous ne pouvez pas passer l'objet événement dans la fonction de rappel, qui pourtant peut être extrêmement utile puisqu'il comprend la position de la souris. Cette fois-ci, cependant, il n'y en avait pas besoin et j'ai décidé donc de l'ajouter à l'ensemble. Comme ça, j'avais les fonctions de rappel pour souris enfoncée, souris lâchée et l'événement Commande. Le code se trouve à la page suivante (en haut à droite). |
| |
That was what I wanted to see. The mouse-down event came first, then the command callback was fired, and finally the mouse-up fired. Good. So, I bundled up my app and booted up the Windows machine. I copied the code to a folder and ran it under Python. The form came up correctly and I clicked the Checkbutton to set it to Checked. | Cela m'a permis de voir ce qui se passait à chaque clic sur la souris, quelle que soit la façon dont c'était cliqué. Puisque je pensais savoir ce qui devrait arriver, j'ai commencé sous Linux. Quand j'ai lancé le programme, la fenêtre du terminal affichait ce qui suit (à la page suivante, en bas à droite)... |
| |
| **That was what I wanted to see. The mouse-down event came first, then the command callback was fired, and finally the mouse-up fired. Good. So, I bundled up my app and booted up the Windows machine. I copied the code to a folder and ran it under Python. The form came up correctly and I clicked the Checkbutton to set it to Checked. |
| |
I fully expected to see pretty much the same thing, except that I wouldn’t see the correct state for the button just like I did before. Much to my surprise, this is what I got: | I fully expected to see pretty much the same thing, except that I wouldn’t see the correct state for the button just like I did before. Much to my surprise, this is what I got: |
| |
| |
| chkbtntest_support.on_ChkBtnClick |
| Unchecked |
| |
| chkbtntest_support.on_ChkBtnRelease |
| Unchecked |
| |
| chkbtntest_support.on_ChkBtnCommand |
| Checked** |
| |
| C'était ce que je voulais voir. L'événement appui-sur-la-souris venait en premier, puis la commande de rappel fut déclenché et, enfin, la souris fut relâchée. Bon. J'ai donc pris mon appli et démarré la machine sous Windows. J'ai copié le code vers un dossier et l'ai exécuté dans Python. Le formulaire s'est affiché correctement et j'ai cliqué sur Checkbutton pour le régler à Checked (coché). |
| |
| Je m'attendais vraiment à voir à peu près la même chose, sauf que je ne verrais pas l'état correct du bouton, tout comme ce qui arrivait auparavant. À ma surprise, voici ce qui s'est affiché : |
| |
chkbtntest_support.on_ChkBtnClick | chkbtntest_support.on_ChkBtnClick |
| |
| |
Under Windows, the mouse up event fires before the command event. AND the command event can query the state of the Checkbutton. Just to make sure, I clicked it again to uncheck it and sure enough I got this: | **Under Windows, the mouse up event fires before the command event. AND the command event can query the state of the Checkbutton. Just to make sure, I clicked it again to uncheck it and sure enough I got this: |
| |
| |
Just goes to show, don’t give up if, the first hundred times, things don’t go the way you expect them to. | Just goes to show, don’t give up if, the first hundred times, things don’t go the way you expect them to. |
| |
Until next time, have a GREAT month. | Until next time, have a GREAT month.** |
| |
| Sous Windows, l'événement souris relâchée se déclenche avant l'événement commande. Et l'événement commande peut demander l'état du Checkbutton. Pour m'en assurer, j'ai cliqué dessus à nouveau pour le décocher et effectivement, ceci s'est affiché : |
| |
| chkbtntest_support.on_ChkBtnClick |
| Checked |
| |
| chkbtntest_support.on_ChkBtnRelease |
| Checked |
| |
| chkbtntest_support.on_ChkBtnCommand |
| Unchecked |
| |
| Ainsi, je sais maintenant que, sous Windows, l'événement commande arrive APRÈS l'événement souris relâchée et, en fait, suit correctement l'état du widget. |
| |
| Je n'ai aucune idée de ce que cela signifie pour quelqu'un qui utilise Python et Tkinter sous le système d'exploitation de Mac, mais je suppose que cela donne à peu près les mêmes résultats que sous Linux. |
| |
| J'ai l'intention de faire des recherches plus approfondies pour cerner l'origine du « problème », puis essayer de trouver la personne à laquelle il faudrait que j'envoie mon rapport de bogue. |
| |
| Cela montre bien qu'il faut pas abandonner si, les cent premières fois, les choses ne se passent pas comme vous y attendiez. |
| |
| À la prochaine fois. Que votre mois soit GÉNIAL ! |