Partage
  • Partager sur Facebook
  • Partager sur Twitter

set() de StringVar() qui ne fonctionne pas?

Sujet résolu
    15 décembre 2023 à 18:32:52

    Bonjour,

    J'apprends à utiliser Tkinter et je bidouille un peu.

    Mon problème survient ligne 16, 17 et 18 du code ci-dessous:

    from tkinter import*
    from tkinter import ttk
    
    class tkinterWidget:
        def __init__(self, root):
    
            root.geometry("800x600")
            root.columnconfigure(0, weight=1)
            root.rowconfigure(0, weight=1)
            explorerFrame = ttk.Frame(root)
            explorerFrame['padding'] = (5, 10)
            
            explorerFrame['relief'] = 'raise'
            explorerFrame.grid(column= 0, row=0, sticky=(N, W, E, S))
    
            self.text = StringVar()
            self.text.set('Du texte')
            label = ttk.Label(explorerFrame, textvariable=self.text)
            label.grid(column= 0, row= 0, sticky=(N, W, E, S))
    
    root = Tk()
    
    tkinterWidget(root)
    root.mainloop()



    j'assigne un objet de classe StringVar() à self.text, j'utilises le setter de StringVar() sur self.text et j'assigne self.text à un objet de la classe ttk.Label nommé grossièrement label

    Alors j'ai testé text="du texte" à la place de textvariable=self.text et mon label s'affiche, le problème provient donc soit de la classe StringVar(), soit de son setter.

    Comment est-ce que je fais pour assigner une string à StringVar()?

    • Partager sur Facebook
    • Partager sur Twitter
      15 décembre 2023 à 19:05:11

      Si j'écris:

      import tkinter as tk
      from tkinter import ttk
      
      root = tk.Tk()
      variable = tk.StringVar()
      variable.set('1234')
      label = ttk.Label(textvariable=variable)
      label.grid(column= 0, row= 0, sticky='nwse')
      tk.mainloop()
      

      ça fonctionne chez moi et ça devrait aussi fonctionner chez vous.

      Si tel est le cas, le problème est ailleurs.

      ligne 23, ajoutez une référence à l'instance (via xx = tkinterWidget(root)) et tenez compte de la durée de vie des objets utilisés...

      -
      Edité par mps 15 décembre 2023 à 19:07:16

      • Partager sur Facebook
      • Partager sur Twitter
        15 décembre 2023 à 21:22:22

        mps a écrit:

        Si j'écris:

        import tkinter as tk
        from tkinter import ttk
        
        root = tk.Tk()
        variable = tk.StringVar()
        variable.set('1234')
        label = ttk.Label(textvariable=variable)
        label.grid(column= 0, row= 0, sticky='nwse')
        tk.mainloop()
        

        ça fonctionne chez moi et ça devrait aussi fonctionner chez vous.

        Si tel est le cas, le problème est ailleurs.

        ligne 23, ajoutez une référence à l'instance (via xx = tkinterWidget(root)) et tenez compte de la durée de vie des objets utilisés...

        -
        Edité par mps il y a environ 1 heure

        J'ai rajouté une référence à l'instance et ça fonctionne comme il faut, cependant je ne saisi pas du tout la logique derrière. 

        2 questions:

        -Qu'est ce que tu entends par "la durée de vie des objets utilisés" ?

        -Je suis parti d'un tutoriel qui utilise StringVar() (ligne 12 et 13 ci-dessous) et qui assigne l'objet à textvariable par le biais d'un entry. Dans ce contexte là, pas besoin d'ajouter une réference à l'instance tkinter(root), pourquoi?

        from tkinter import *
        from tkinter import ttk 
        
        class FeetToMeters:
        
            def __init__(self, root):
                root.title("Feet to Meters")
        
                mainframe = ttk.Frame(root, padding="3 3 12 12")
                mainframe.grid(column= 0, row=0, sticky=(N, W, E, S))
                
                self.feet = StringVar()
                feet_entry = ttk.Entry(mainframe, width=7, textvariable=self.feet)
                feet_entry.grid(column=2, row=1, sticky=(W, E))
                self.meters = StringVar()
        
                ttk.Label(mainframe, textvariable=self.meters).grid(column=2, row=2, sticky=(W, E))
                ttk.Button(mainframe, text="Calculate", command=self.calculate).grid(column=3, row=3, sticky=(W, E))
        
                ttk.Label(mainframe, text="Feet").grid(column=3, row=1, sticky=W)
                ttk.Label(mainframe, text="is equivalent to").grid(column=1, row=2, sticky=E)
                ttk.Label(mainframe, text="Meters").grid(column=3, row=2, sticky=W)
        
                for child in mainframe.winfo_children():
                    child.grid_configure(padx=5, pady=5)
                feet_entry.focus()
                root.bind("<Return>", self.calculate)
        
                
        
            def calculate(self, *args):
                try:
                    self.value = float(self.feet.get())
                    self.meters.set(int(0.3048 * self.value * 10000.0 + 0.5)/ 10000.0)
                except ValueError:
                    pass
        
        root = Tk()
        FeetToMeters(root)
        root.mainloop()



        -
        Edité par AlexRz 15 décembre 2023 à 21:29:30

        • Partager sur Facebook
        • Partager sur Twitter
          15 décembre 2023 à 23:33:14

          AlexRz a écrit:

          Je suis parti d'un tutoriel qui utilise StringVar() (ligne 12 et 13 ci-dessous) et qui assigne l'objet à textvariable par le biais d'un entry. Dans ce contexte là, pas besoin d'ajouter une réference à l'instance tkinter(root), pourquoi?

          Mettez en commentaire les lignes qui font référence à la méthode calculate

          Normalement, on devrait reproduire le même problème car l'instance de FeetToMeters n'est plus référencée (indirectement)... et les StringVar seront détruits avec l'instance.

          C'est une programmation "naïve" qui fonctionne par hasard: changez de tuto!

          Une solution simple serait de faire hériter FeetToMeters de tk.Frame. Devenant widget, les références sont gérées par tkinter et l'objet ne sera détruit que via un destroy explicite.

          -
          Edité par mps 16 décembre 2023 à 7:34:24

          • Partager sur Facebook
          • Partager sur Twitter
            16 décembre 2023 à 14:20:38

            Si je déplace juste root.mainloop() dans la classe, je vois l'écriture dans le Label.
            from tkinter import *
            from tkinter import ttk
            
            class tkinterWidget:
                def __init__(self, root):
                    root.geometry("800x600")
                    root.columnconfigure(0, weight=1)
                    root.rowconfigure(0, weight=1)
            
                    explorerFrame = ttk.Frame(root)
                    explorerFrame['padding'] = (5, 10)
                    explorerFrame['relief'] = 'raise'
                    explorerFrame.grid(column=0, row=0, sticky=(N, W, E, S))
            
                    self.text = StringVar()
                    self.text.set('Du texte')
            
                    label = ttk.Label(master=explorerFrame, textvariable=self.text)
                    label.grid(column=0, row=0, sticky=(N, W, E, S))
                    
                    root.mainloop()
            
            root = Tk()
            tkinterWidget(root)
            

            C'est la seule erreur qu'on peut voir selon le premier code proposé et à modifier.

            Mais il serait préférable comme l'indique @mps d'hériter de Frame

            from tkinter import *
            from tkinter import ttk
            
            class WidgetFrame(ttk.Frame):
                def __init__(self, master=None):
                    super().__init__(master=master)
            
                    self['padding'] = (5, 10)
                    self['relief'] = 'raise'
                    self.grid(column=0, row=0, sticky=(N, W, E, S))
            
                    self.text = StringVar(master=self)
                    self.text.set('Du texte')
            
                    label = ttk.Label(master=self, textvariable=self.text)
                    label.grid(column=0, row=0, sticky=(N, W, E, S))
            
            root = Tk()
            root.geometry("800x600")
            root.columnconfigure(0, weight=1)
            root.rowconfigure(0, weight=1)
            WidgetFrame(master=root)
            root.mainloop()

            On explicite bien le nom des arguments, ça aide à connaître ceux à quoi les objets font références.

            Pas d'espace dans les options, merci !

            Dans le dernier code, attention sur la fonction calculate, car si on rentre dans le except, on retourne la valeur None, valeur peut-être pas attendue...

            À mon sens, ton deuxième code ne prend pas en compte les remarques relevées par @mps et moi-même, ce que je te conseillerai d'appliquer pour éviter une construction difficile par la suite.

            -
            Edité par fred1599 16 décembre 2023 à 14:26:10

            • Partager sur Facebook
            • Partager sur Twitter

            Celui qui trouve sans chercher est celui qui a longtemps cherché sans trouver.(Bachelard)
            La connaissance s'acquiert par l'expérience, tout le reste n'est que de l'information.(Einstein)

              16 décembre 2023 à 14:44:39

              fred1599 a écrit:

              Si je déplace juste root.mainloop() dans la classe, je vois l'écriture dans le Label.

              C'est une astuce qui empêche __init__ de se terminer  (bloqué dans mainloop) et... la destruction de l'instance (et des StringVar).

              Ça fonctionne comme les références à la méthode calculate dans le code du tuto: un heureux hasard.

              Il n'y a rien d'erroné à programmer ainsi. Juste que si ça se casse la gueule quand on bouge une ligne sans que le soucis saute aux yeux, je préfèrerai une construction plus robuste (mais certains aiment ces codes malicieux).

              • Partager sur Facebook
              • Partager sur Twitter
                16 décembre 2023 à 15:03:13

                mais certains aiment ces codes malicieux

                Tout à fait, je conseille... et un conseil n'est pas imposer quoi que se soit, mais écouter l'expérience peut être chose intéressante ;)

                -
                Edité par fred1599 16 décembre 2023 à 15:03:41

                • Partager sur Facebook
                • Partager sur Twitter

                Celui qui trouve sans chercher est celui qui a longtemps cherché sans trouver.(Bachelard)
                La connaissance s'acquiert par l'expérience, tout le reste n'est que de l'information.(Einstein)

                  25 décembre 2023 à 0:48:09

                  mps a écrit:

                  AlexRz a écrit:

                  Je suis parti d'un tutoriel qui utilise StringVar() (ligne 12 et 13 ci-dessous) et qui assigne l'objet à textvariable par le biais d'un entry. Dans ce contexte là, pas besoin d'ajouter une réference à l'instance tkinter(root), pourquoi?

                  Mettez en commentaire les lignes qui font référence à la méthode calculate

                  Normalement, on devrait reproduire le même problème car l'instance de FeetToMeters n'est plus référencée (indirectement)... et les StringVar seront détruits avec l'instance.

                  C'est une programmation "naïve" qui fonctionne par hasard: changez de tuto!

                  Une solution simple serait de faire hériter FeetToMeters de tk.Frame. Devenant widget, les références sont gérées par tkinter et l'objet ne sera détruit que via un destroy explicite.

                  -
                  Edité par mps 16 décembre 2023 à 7:34:24


                  Désolé, j'ai du changer de pc entre temps, je viens tout juste de voir ces messages !

                  Si je comprends bien, en python, la création d'une méthode de classe crée une référence à l'instance ?

                  -
                  Edité par AlexRz 25 décembre 2023 à 0:48:47

                  • Partager sur Facebook
                  • Partager sur Twitter
                    25 décembre 2023 à 11:06:52

                    L'instance d'une classe est self, les méthodes de classes prennent en argument self qui est une référence de l'objet créé (instance).
                    • Partager sur Facebook
                    • Partager sur Twitter

                    Celui qui trouve sans chercher est celui qui a longtemps cherché sans trouver.(Bachelard)
                    La connaissance s'acquiert par l'expérience, tout le reste n'est que de l'information.(Einstein)

                      27 décembre 2023 à 15:07:14

                      AlexRz a écrit:

                      Si je comprends bien, en python, la création d'une méthode de classe crée une référence à l'instance ?

                      L'existence de la méthode calculate permet de fabriquer l'objet self.calculate et y faire référence dans des instructions comme root.bind(..., self.calculate). Et comme c'est une méthode, c'est une référence a l'objet self et une référence à la fonction calculate (qui sera référencé aussi dans le dictionnaire de la class).

                      Mettre en commentaire cette instruction supprime ces 2 références (il y en a une autre ligne 18)... et sans référence à self, le StringVar disparait aussi.

                      Pourquoi changer de tuto? Dans votre exemple, si on ne stocke  pas l'instance dans une variable, le garbage collector voit le nombre de références passer à 0 et fait immédiatement le ménage.

                      Dans l'exemple du tuto, on crée des références circulaires qui font que le nombre de références ne passera pas à 0... Le garbage collector devra examiner la collection des objets alloués pour se rendre compte qu'il peut détruire l'instance.

                      Le problème sera visible ou pas mais c'est une bombe qui fait tic tac...


                      -
                      Edité par mps 28 décembre 2023 à 17:52:12

                      • Partager sur Facebook
                      • Partager sur Twitter
                        28 décembre 2023 à 17:31:00

                        mps a écrit:

                        AlexRz a écrit:

                        Si je comprends bien, en python, la création d'une méthode de classe crée une référence à l'instance ?

                        L'existence de la méthode calculate permet de fabriquer l'objet self.calculate et y faire référence dans des instructions comme root.bind(..., self.calculate). Et comme c'est une méthode, c'est une référence a l'objet self et une référence à la fonction calculate (qui sera référencé aussi dans le dictionnaire de la class).

                        Mettre en commentaire cette instruction supprime ces 2 références (il y en a une autre ligne 18)... et sans référence à self, le StringVar disparait aussi.

                        fred1599 a écrit:

                        L'instance d'une classe est self, les méthodes de classes prennent en argument self qui est une référence de l'objet créé (instance).


                        Merci pour vos réponses ! 

                        Dans ce cas, histoire de voir si j'ai bien compris: Supposons qu'on supprime def calculate...
                        Ligne 17: on a une référence a self, mais comme aucun objet n'est crée que ce soit par l'instanciation d'un objet de la classe: tk = FeetToMeters(root) qui crée une référence à l'objet self ou par la définition d'une méthode qui crée l'objet self.calculate la référence est caduque et tout ce qui est déclaré de l'objet 'self' disparaît ?



                        -
                        Edité par AlexRz 28 décembre 2023 à 22:24:29

                        • Partager sur Facebook
                        • Partager sur Twitter
                          29 décembre 2023 à 18:55:40

                          Si la création de l'instance est appelée (FeetToMeters(root)), un mécanisme permettra d'utiliser self en ce qui concerne les méthodes appelées à l'intérieur de la classe. Cependant, si vous ne gardez pas en mémoire l'instance créée, il n'y a pas la possibilité d'utiliser/appeler vos méthodes à l'extérieur de votre classe.

                          -
                          Edité par fred1599 29 décembre 2023 à 18:56:15

                          • Partager sur Facebook
                          • Partager sur Twitter

                          Celui qui trouve sans chercher est celui qui a longtemps cherché sans trouver.(Bachelard)
                          La connaissance s'acquiert par l'expérience, tout le reste n'est que de l'information.(Einstein)

                            29 décembre 2023 à 19:28:22

                            fred1599 a écrit:

                            Si la création de l'instance est appelée (FeetToMeters(root)), un mécanisme permettra d'utiliser self en ce qui concerne les méthodes appelées à l'intérieur de la classe. Cependant, si vous ne gardez pas en mémoire l'instance créée, il n'y a pas la possibilité d'utiliser/appeler vos méthodes à l'extérieur de votre classe.

                            -
                            Edité par fred1599 il y a 31 minutes


                            Très bien expliqué, j'ai compris, merci!
                            • Partager sur Facebook
                            • Partager sur Twitter
                              30 décembre 2023 à 17:27:10

                              AlexRz a écrit:

                              la référence est caduque et tout ce qui est déclaré de l'objet 'self' disparaît ?

                              Si c'était si simple...

                              On peut créer la classe suivante:

                              import gc
                              
                              class A:
                                  def __init__(self):
                                      self.a = self
                                  def __del__(self):
                                      print('del')
                              
                              def f():
                                  A()
                              

                              le self.a = self crée la référence à l'instance qui fera que le nombre de référence ne passe pas à zéro "tout  seul".... Et afficher "del" a ce moment là.

                              La fonction f est là pour éviter que l'interpréteur "tienne" la référence à l'instance (dans _).

                              Essayons:

                              >>> f()
                              >>> a = A()
                              >>> del a
                              >>>

                              rien... Par contre, si on force le garbage collector pour faire le ménage:

                              >>> gc.collect()
                              del
                              del
                              2

                              on voit les 2 "del" apparaître.

                              Changeons le code pour avoir:

                              class A:
                                  def __init__(self):
                                      self.a = self
                                  def __del__(self):
                                      print('del')

                              maintenant on obtient:

                              >>> f()
                              del
                              >>> a = A()
                              >>> del a
                              del
                              >>>

                              pas besoin de forcer gc.collect pour que l'instance soit détruite.








                              -
                              Edité par mps 30 décembre 2023 à 17:30:33

                              • Partager sur Facebook
                              • Partager sur Twitter
                                31 décembre 2023 à 17:10:33

                                mps a écrit:

                                AlexRz a écrit:

                                la référence est caduque et tout ce qui est déclaré de l'objet 'self' disparaît ?

                                Si c'était si simple...

                                On peut créer la classe suivante:

                                import gc
                                
                                class A:
                                    def __init__(self):
                                        self.a = self
                                    def __del__(self):
                                        print('del')
                                
                                def f():
                                    A()
                                

                                le self.a = self crée la référence à l'instance qui fera que le nombre de référence ne passe pas à zéro "tout  seul".... Et afficher "del" a ce moment là.

                                La fonction f est là pour éviter que l'interpréteur "tienne" la référence à l'instance (dans _).

                                Essayons:

                                >>> f()
                                >>> a = A()
                                >>> del a
                                >>>

                                rien... Par contre, si on force le garbage collector pour faire le ménage:

                                >>> gc.collect()
                                del
                                del
                                2

                                on voit les 2 "del" apparaître.

                                Changeons le code pour avoir:

                                class A:
                                    def __init__(self):
                                        self.a = self
                                    def __del__(self):
                                        print('del')

                                maintenant on obtient:

                                >>> f()
                                del
                                >>> a = A()
                                >>> del a
                                del
                                >>>

                                pas besoin de forcer gc.collect pour que l'instance soit détruite.
                                Edité par mps il y a environ 23 heures


                                Dans ce cas est-ce que je me trompe si je dis: Toute réference à une instance dans "_" se verra supprimé à la fin de l'execution d'__init__ et pour résoudre le problème d'une façon explicitée et lisible, il suffit d'ajouter une référence à la classe lors de son appel?
                                Edit: Jreprends mon code tkinter, je rajoute ta fonction __del__ et je vois ce qu'il se passe

                                -
                                Edité par AlexRz 31 décembre 2023 à 17:15:59

                                • Partager sur Facebook
                                • Partager sur Twitter
                                  31 décembre 2023 à 18:11:40

                                  AlexRz a écrit:

                                  Dans ce cas est-ce que je me trompe si je dis: Toute réference à une instance dans "_" se verra supprimé à...

                                  _ n'a de sens que dans l'interpréteur interactif (lorsqu'on exécute normalement un script, cette variable n'existe pas).

                                  Pour ce qui est de résoudre le problème initial, faire hériter FeetToMeters de Frame est le plus "naturel" (ça  devient widget). Dans ce cas, inutile de garder une référence à l'instance: tkinter le fait déjà.

                                  -
                                  Edité par mps 31 décembre 2023 à 18:12:23

                                  • Partager sur Facebook
                                  • Partager sur Twitter

                                  set() de StringVar() qui ne fonctionne pas?

                                  × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                                  • Editeur
                                  • Markdown