Les 'state' de mes items ne passent jamais à 'active'

Bonsoir,

Dessous un code totalement en chantier qui devrait vocaliser les labels des items.

Dans une mouture simplifiée j’avais pu observer le passage de ‘normal’ à ‘active’ pour des items.
Note: contrairement à ce que semble indiquer la doc il ne s’agit pas de ‘NORMAL’ et ‘ACTIVE’ (notez les majuscules).

Pour mes premiers essais, je tente la chose avec un seul menu ‘Fichier’ et j’affiche les états (state) de ses items pour en voir passer un à ‘active’.
J’ai essayé différents évènements et ils demeurent tous ‘normal’…

J’ai un gros doute où placer le ‘bind’ et sur quoi le placer (ici je l’applique à chaque menu).

Un “pro” et patient saurait me “corriger” ?

#!/usr/bin/python3

"""
Définition d'une fenêtre comportant une barre de menus dont tous les items sont vocalisés
"""

from tkinter import *

# Dictionnaire pour la définition des menus
items = {
'Fichier':
[
['Nouveau','cmd1'],
['Ouvrir','cmd2'],
['Imprimer','cmd3'],
['Quitter','cmd4']
]
#'Edition':
#  [
#  ['Copier','cmd1'],
#  ['Coller','cmd2'],
# ['Couper','cmd3']
#  ]
}

class MyWindow(Tk):
  def __init__(self,items,font,*args,**kwargs):
    Tk.__init__(self,*args,**kwargs)
    self.items = items
    self.geometry('400x200')
    self.title('ACIAH')
    self.font = font
# Dictionnaire recevant les paires "nom du menu" / instance du menu associé
    self.menus = {}
    self.create_menu_bar(self.font)
    self.config(menu=self.menu_bar)
# Méthode qui vocalise les items (en chantier !)
  def speak(self,key):
    for i in [0,1,2,3]:
      print(self.menus[key].entrycget(i,'state'))
  def create_menu_bar(self,font):
    self.menu_bar = Menu(self,activebackground='Black',activeforeground='White',font=font,takefocus=1)
    for k in self.items:
      self.menus[k] = Menu(self.menu_bar, tearoff=0,activebackground='Black',activeforeground='White',font=font)
      for opt in self.items[k]:
        self.menus[k].add_command(label=opt[0],command=self.menus[k].quit())
      self.menu_bar.add_cascade(label=k,menu=self.menus[k])
      self.menus[k].bind('<Down>',lambda e: self.speak(k))

if __name__ == '__main__':
  window = MyWindow(items,('Arial',24,'bold'))
  window.mainloop()

Merci

pierre estrem

Salut,

Je ne suis pas sûr de comprendre, qu’est-ce qui devrait faire que l’option du menu soit “active” quand tu appuies sur la touche “bas” de ton clavier ?

Bonsoir Antoine,

Le script que j’ai joint fonctionne sans échec.
Mais chaque item apparaît comme bien prendre le focus (les couleurs sont inversées) mais leurs états restent à ‘normal’ (couleurs si “non focussé”) alors que j’attends un ‘state’ à ‘active’.
Et cela a eu marché dans un code plus simple que j’ai du mal à reproduire.

J’ai tenté divers évènements (FocusIn, FocusOut…) mais rien ne change.

Ai-je répondu ou je demeure confus ? :wink:

Merci
pierre estrem

J’ai l’impression qu’il faut s’abonner à l’évènement <<MenuSelect>>, mais je n’ai pas réussi à le faire fonctionner.

Ahh je commence à y arriver :

self.menu_bar.bind_class('Menu', '<<MenuSelect>>', lambda e: print(e))

affiche quelque chose :slight_smile:

Exemple complet :

#!/usr/bin/python3

"""
Définition d'une fenêtre comportant une barre de menus dont tous les items sont vocalisés
"""

from tkinter import *
from tkinter.ttk import *

# Dictionnaire pour la définition des menus
items = {
    "Fichier": [
        ["Nouveau", "cmd1"],
        ["Ouvrir", "cmd2"],
        ["Imprimer", "cmd3"],
        ["Quitter", "cmd4"],
    ],
    "Edition": [
        ["Copier", "cmd1"],
        ["Coller", "cmd2"],
        ["Couper", "cmd3"],
    ],
}


class MyWindow(Tk):
    def __init__(self, menu_items, font, *args, **kwargs):
        Tk.__init__(self, *args, **kwargs)
        self.menu_items = menu_items
        self.geometry("400x200")
        self.title("ACIAH")
        self.font = font
        # Dictionnaire recevant les paires "nom du menu" / instance du menu associé
        self.menus = {}
        self.create_menu_bar()
        self.config(menu=self.menu_bar)

    def menu_select(self, event):
        label = self.tk.eval(f"{event.widget} entrycget active -label")
        print(label)

    def create_menu_bar(self):
        self.menu_bar = Menu(
            self,
            activebackground="Black",
            activeforeground="White",
            font=self.font,
            takefocus=1,
        )
        self.menu_bar.bind_class("Menu", "<<MenuSelect>>", self.menu_select)
        for label, commands in self.menu_items.items():
            self.menus[label] = menu = Menu(
                self.menu_bar,
                tearoff=0,
                activebackground="Black",
                activeforeground="White",
                font=self.font,
            )
            for command_label, command_cb in commands:
                menu.add_command(label=command_label, command=menu.quit())
            self.menu_bar.add_cascade(label=label, menu=menu)


if __name__ == "__main__":
    window = MyWindow(items, ("Arial", 24, "bold"))
    window.mainloop()

Bonsoir Julien,

Je n’avais pas étudié la méthode bind_class.

Ce que vous récupérez sont en fait les coordonnées de l’évènement virtuel ‘<>’ qui restent figés à (0,0) (le coin supérieur gauche du titre ‘Fichier’ peut-être.
Et si j’applique la méthode self.speak(e,k) à votre ligne j’obtiens toujours les items à ‘normal’ (celui qui paraît focussé ne passe pas à ‘active’).

Je dois tenter de retrouver le code simplifié qui m’a eu amené à vois passer l’item de ‘normal’ à ‘active’.

Mais sur la base de ce ‘<>’ on trouve des choses intéressantes avec la méthode call() , par exemple du code :

def statusbarUpdate( event=None ):
   print tk.call(event.widget, "index", "active")

Je m’y plonge…

Merci
pierre estrem

Bonsoir,

Je reprends le début de votre code et je remplace la commande par l’appel à call() ainsi :

self.menu_bar.bind_class(‘Menu’, ‘<>’, lambda e: print tk.call(event.widget, ‘index’, ‘active’))

Alors s’affiche bien l’index qui prend le focus (0 - 3).

Alors je fais appel à 'entrycget ’ pour lire le label de l’item dont j’ai obtnu l’index et ça passe…

Mais si j’ajoute le menu Edition (en décommentant) alors le code dessous n’affiche que les labels de ce second menu et jamais ceux du premier menu :

#!/usr/bin/python3

"""
Définition d'une fenêtre comportant une barre de menus dont tous les items sont vocalisés
"""

from tkinter import *

# Dictionnaire pour la définition des menus
items = {
'Fichier':
[
['Nouveau','cmd1'],
['Ouvrir','cmd2'],
['Imprimer','cmd3'],
['Quitter','cmd4']
],
'Edition':
[
['Copier','cmd1'],
['Coller','cmd2'],
['Couper','cmd3']
]
}

class MyWindow(Tk):
  def __init__(self,items,font,*args,**kwargs):
    Tk.__init__(self,*args,**kwargs)
    self.items = items
    self.geometry('400x200')
    self.title('ACIAH')
    self.font = font
# Dictionnaire recevant les paires "nom du menu" / instance du menu associé
    self.menus = {}
    self.create_menu_bar(self.font)
    self.config(menu=self.menu_bar)  
# Méthode qui vocalise les items (en chantier !)
  def speak(self,event,key):
    for i in [0,1,2,3]:
      print(self.menus[key].entrycget(i,'state'))
  def create_menu_bar(self,font):
    self.menu_bar = Menu(self,activebackground='Black',activeforeground='White',font=font,takefocus=1)
    for k in self.items:
      self.menus[k] = Menu(self.menu_bar, tearoff=0,activebackground='Black',activeforeground='White',font=font)
      for opt in self.items[k]:
        self.menus[k].add_command(label=opt[0],command=self.menus[k].quit())
      self.menu_bar.add_cascade(label=k,menu=self.menus[k])
      self.menu_bar.bind_class('Menu', '<<MenuSelect>>', lambda e: print(self.menus[k].entrycget(self.call(e.widget,'index','active'),'label')))

if __name__ == '__main__':
  window = MyWindow(items,('Arial',24,'bold'))
  window.mainloop()

Meis c’est en bonne voie !

Merci ** 2

*pierre estrem

Non, la fonction :

    def menu_select(self, event):
        label = self.tk.eval(f"{event.widget} entrycget active -label")
        print(label)

Récupère et affiche le label, à l’utilisation ça donnait :

$ python /tmp/d.py
Edition
Fichier
Nouveau
Fichier
Nouveau
Ouvrir
Imprimer
Ouvrir
Nouveau
Ouvrir
Imprimer
Quitter

Bonsoir,

J’ai remanié comme vous le proposez et oui c’est mieux.
Cependant j’obtiens l’erreur (non fatale) dessous :

Exception in Tkinter callback
Traceback (most recent call last):
  File "/usr/lib/python3.8/tkinter/__init__.py", line 1892, in __call__
    return self.func(*args)
  File "./mymenu.py", line 49, in <lambda>
    self.menu_bar.bind_class('Menu', '<<MenuSelect>>', lambda e: self.menu_select(e))
  File "./mymenu.py", line 40, in menu_select
    label = self.tk.eval(f"{event.widget} entrycget active -label")
_tkinter.TclError: unknown option "-label"

Pourtant la variable label est bien initialisée puisque j’obtiens les labels à l’écran comme vous.

Mon code :

#!/usr/bin/python3

"""
Définition d'une fenêtre comportant une barre de menus dont tous les items sont vocalisés
"""

from tkinter import *

# Dictionnaire pour la définition des menus
items = {
'Fichier':
[
['Nouveau','cmd1'],
['Ouvrir','cmd2'],
['Imprimer','cmd3'],
['Quitter','cmd4']
],
'Edition':
[
['Copier','cmd1'],
['Coller','cmd2'],
['Couper','cmd3']
]
}

class MyWindow(Tk):
  def __init__(self,items,font,*args,**kwargs):
    Tk.__init__(self,*args,**kwargs)
    self.items = items
    self.geometry('400x200')
    self.title('ACIAH')
    self.font = font
# Dictionnaire recevant les paires "nom du menu" / instance du menu associé
    self.menus = {}
    self.create_menu_bar(self.font)
    self.config(menu=self.menu_bar)  
# Méthode qui vocalise les items (en chantier !)
  def menu_select(self, event):
    label = self.tk.eval(f"{event.widget} entrycget active -label")
    print(label)
  def create_menu_bar(self,font):
    self.menu_bar = Menu(self,activebackground='Black',activeforeground='White',font=font,takefocus=1)
    for k in self.items:
      self.menus[k] = Menu(self.menu_bar, tearoff=0,activebackground='Black',activeforeground='White',font=font)
      for opt in self.items[k]:
        self.menus[k].add_command(label=opt[0],command=self.menus[k].quit())
      self.menu_bar.add_cascade(label=k,menu=self.menus[k])
      self.menu_bar.bind_class('Menu', '<<MenuSelect>>', lambda e: self.menu_select(e))

if __name__ == '__main__':
  window = MyWindow(items,('Arial',24,'bold'))
  window.mainloop()

Merci

pierre estrem

Essai ici sans aucune erreur :
python v3.10.8
tkinter v8.6