event.widget in callbacks von menüeinträgen

Fragen zu Tkinter.
Antworten
Prophet
User
Beiträge: 6
Registriert: Dienstag 6. Oktober 2009, 14:08

Hi allerseits ^^

Ich bin jetzt schon zwei Tage lang auf der Suche nach einer Lösung für ein Problem mit meinem Tkinter-Programm, konnte sie aber bisher nicht finden.
Vorneweg möchte ich mich entschuldigen, dass ich keine Code-Ausschnitte posten kann, aber das Programm ist für eine Bachelorarbeit und dementsprechend so umfangreich, dass ich nicht denke dass ich da aus dem Code das Problem richtig herausarbeiten kann.
Stattdessen will ich einfach versuchen so genau wie möglich zu beschreiben was ich vor habe, warum es nicht anders geht bzw ich es nicht anders haben möchte, wenn es irgendwie so geht, wie ich es mir vorgestellt habe und was das Problem dabei ist.

So, lange Rede, kurzer Sinn, kommen wir ans Eingemachte.

Was ich tun möchte ist zu jedem widget in meinem programm mit Rechtsklick ein Kontextmenü zu anzuzeigen, dessen inhalt von dem angeklickten widget abhängig ist (u.a. um eigenschaften dieses widgets anzuzeigen und zu ändern).
So weit kein Problem, aber ich möchte, dass das auch für Menüs und Menüeinträge funktioniert, wobei man letztere in Tkinter ja strenggenommen nie als "widget"-objekte geliefert bekommt. Dementsprechend kann ich auch nicht individuell für einen Menüeintrag die bind-methode verwenden, sondern muss mit bind_all arbeiten. (Falls ich mich da irren sollte und es gibt doch einen weg einem menü_eintrag_ ein callback zuzuweisen für <Button-3>, sagt es mir ^^)

So, auch das wäre eigentlich kein Problem bisher. Man bekommt ja, wenn man ein callback über bind-methoden registiert ein Event-Objekt mit dem man zugriff auf all die netten Attribute hat, die man braucht - theoretisch.
Das Attribut für das ich mich jetzt im speziellen interessiere ist natürlich das "widget"-attribut, weil ich die Eigenschaften der widgets ja brauche um zu entscheiden welches Kontextmenü angezeigt wird bzw wie dieses aussieht,

Greife ich aber auf widget.event zu, wenn das Event von einem Menu-Objekt oder einem Menüeintrag kommt, bekomme ich kein Widget-Objekt mit Attributen und Methoden, sondern nur einen str-Objekt, dessen Inhalt in etwa zum Beispiel so aussieht ".#164828300" bei einem Menu-Objekt oder so ".#164828300.#164828300#164828396" bei einem Eintrag.
Das sieht ja sehr nach einem Benennungssystem aus wie es in Tkinter auch bei "normalen" widgets existiert, wenn man das Widget zu einem String umwandelt oder widget.winfo_name() aufruft. Augenscheinlich hilft hier aber nametowidget auch nicht weiter, denn das wirft einen Fehler wenn ich es auf dem Tk-Objekt aufrufe und mit diesen seltsamen Menu-Strings füttere.

Ich bräuchte also irgendeinen Weg, wie ich von diesen Bezeichnern aus dem event.widget Feld zu einem "benutzbaren" Hinweis auf den angeklickten Menü-Eintrag komme, sei das nun das label des Eintrags, der Index im Menu oder was auch immer.

Ich bin sogar schon so weit gegangen, dass ich versucht habe, einfach als command für die Menüeinträge eine Funktion übergeben habe, die jeweils dann in einer Variable speichert welcher Eintrag zuletzt geklickt wurde, aber da das übergebene command erst _nach_ dem callback für das <Button-3>-Event ausgeführt wird bringt das auch wieder nichts, da ich dann die Information ja erst bekomme nachdem ich sie schon lange gebraucht hätte.

Eine Tkinter-Erweiterung mit anderen Menu-Widgets kommt für mich erst als letzte Option in Frage, weil es für das Programm ziemlich wichtig ist, dass es ohne viele zusätzliche software auskommen und bestenfalls auf jedem Linux-Rechner mit python laufen soll. Wenn da jemand eine gute Alternative kennt, wäre ich über den Hinweis aber natürlich auch dankbar.

Ich bedanke mich schon einmal im vorraus und hoffe wirklich, dass ihr mir hier weiterhelfen könnt. Ihr seid sozusagen meine letzte Hoffnung. ^^

freundliche Grüße
Prophet

P.S. :
Sollte etwas bei meiner Beschreibung noch unklar sein, fragt mich bitte. Ich denke wir kennen ja alle die Probleme von Fragestellungen ohne den zugehörigen Programmcode. ^^
BlackJack

Ein kleines Minimalbeispiel wäre schon irgendwie hilfreich. Ist das Problem nicht allgemein genug, dass man das Beispiel schnell neu schreiben kann, also nicht eine vorhandene Anwendung auf's Wesentliche "eindampfen" muss?
Prophet
User
Beiträge: 6
Registriert: Dienstag 6. Oktober 2009, 14:08

Stimmt. Dazu war ich ganz ehrlich gesagt gestern nach all dem Frust einfach zu faul. ^^"

Ich habe da jetzt mal was zusammengezimmert :

Code: Alles auswählen

from Tkinter import *

t = Tk()

#soll bei rechtsklick auf eintrag 'label1' im menue 'File' angezeigt werden
context1 = Menu()
context1.add_command(label='cm for menuentry 1')
#soll bei rechtsklick auf eintrag 'label2' im menue 'File' angezeigt werden
context2 = Menu()
context2.add_command(label='cm for menuentry 2')
#soll bei rechtsklick auf 'ok'-button angezeigt werden
context3 = Menu()
context3.add_command(label='cm for button 1')
#soll bei rechtsklick auf menue 'File' angezeigt werden
context4 = Menu()
context4.add_command(label='cm for filemenu')

normalwidget = Button(t,text='ok')
normalwidget.test_contextmenu = context3
normalwidget.pack()

menubar = Menu(t)
t.config(menu=menubar)
menuwidget = Menu(menubar,tearoff=False)
menubar.add_cascade(menu=menuwidget,label='File')
menuwidget.add_command(label='label1')
menuwidget.add_command(label='label2')
menuwidget.test_contextmenu = context4
#contextmenus by index of menuitem
menuwidget.test_contextmenu_list = [context1,context2]

def callback(event):
    cls = event.widget.__class__.__name__
    print 'right click on widget of class %s' % cls
    if 'test_contextmenu' in dir(event.widget) :
        #hier ist alles prima
        #wir haben ein kontextmenue zum widget gefunden und koennen es darstellen
        event.widget.test_contextmenu.post(event.x_root,event.y_root)
    elif not isinstance(event.widget,Widget) :
        #hier ist das problem
        #selbst wenn wir dem widget ein kontextmenue in einer variable zugewiesen haben
        #kommen wir an diese variable nicht heran, weil wir nicht die widget-instanz haben
        #es gibt auch kein weiteres feld von 'event' das uns hinweise auf das widget liefert
        print 'event.widget is not instance of Widget, (class %s , content %s)' % (cls,event.widget)
    else :
        print 'clicked on %s without assigned context-menu' % cls 

t.bind_all('<Button-3>',callback)
t.mainloop()
Ausgabe bei Rechtsklick auf den Button (wobei das richtige Kontextmenü angezeigt wird) ist diese :

Code: Alles auswählen

right click on widget of class Button
Und Ausgabe bei Rechtsklick erst auf das 'File'-Menü selbst und dann auf den ersten Eintrag ist diese :

Code: Alles auswählen

right click on widget of class str
event.widget is not instance of Widget, (class str , content .#3084604524L)
right click on widget of class str
event.widget is not instance of Widget, (class str , content .#3084604524L.#3084604524L#3084604556L)
Edit :
Was interessant ist : Wenn ich auf das Kontextmenü vom Button wieder einen Rechtsklick mache kommt das hier :

Code: Alles auswählen

right click on widget of class Menu
clicked on Menu without assigned context-menu
Bei Menüs, die nicht direkt eingebunden sind, sondern gepostet werden scheint der Zugriff über event.widget also zu funktionieren. oO
Hat das vielleicht dann irgendwas mit dieser Klongeschichte von Menu-Widgets zu tun?
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hallo Prophet

Hier ein Skript zum herumexperimentieren:

Code: Alles auswählen

# wuf_ref: menu_event_callback_01_02.py

from Tkinter import *

def button3_press(event):
    cls = event.widget.__class__.__name__

    t.button_activated = True
    t.app_xroot = event.x_root
    t.app_yroot = event.y_root
    if t.delay_obj:
        t.after_cancel(t.delay_obj)
        t.delay_obj = None

    print 'right click on widget of class %s' % cls
    if 'test_contextmenu' in dir(event.widget) :
        #hier ist alles prima
        #wir haben ein kontextmenue zum widget gefunden und koennen es darstellen
        event.widget.test_contextmenu.post(event.x_root,event.y_root)
    elif not isinstance(event.widget,Widget) :
        #hier ist das problem
        #selbst wenn wir dem widget ein kontextmenue in einer variable zugewiesen haben
        #kommen wir an diese variable nicht heran, weil wir nicht die widget-instanz haben
        #es gibt auch kein weiteres feld von 'event' das uns hinweise auf das widget liefert
        if t.menu_data:
            #~~ Zeigt Context-Menu bei Rechtsklick auf ein Hauptmenu
            show_context_menu()
        print 'event.widget is not instance of Widget, (class %s , content %s)' % (cls,event.widget)
    else :
        print 'clicked on %s without assigned context-menu' % cls
    t.menu_data = ()

def button3_release(event=None):
    t.menu_data = ()
    t.button_activated = False

def delay_timeout():
    #~~ Wird aufgerufen, wenn mit der linken Maustaste auf das Hauptmenu
    #   gedrueckt wird!
    t.menu_data = ()
    t.delay_obj = None

def sub_menu_callback(label, context):
    #~~ BEMERKUNG: Auf die rechte Maustaste bezogen.
    #   Wird mit dem loslassen der Maustaste aufgerufen
    if t.button_activated:
        #~~ Zeigt Context-Menu bei Rechtsklick auf ein Submenu
        t.menu_data = ('Submenu', label, context)
        show_context_menu()

def main_menu_callback(label, context):
    #~~ BEMERKUNG: Auf die rechte Maustaste bezogen.
    #   Wird mit dem druecken der Maustaste aufgerufen
    t.menu_data = ('Hauptmenu', label, context)
    t.delay_obj = t.after(100, delay_timeout)

def show_context_menu():
    men_type, men_label, context_menu = t.menu_data
    print '%s-%s' % (men_type, men_label)
    context_menu.post(t.app_xroot, t.app_yroot)

t = Tk()

t.geometry('200x50')

#soll bei rechtsklick auf eintrag 'label1' im menue 'File' angezeigt werden
context1 = Menu()
context1.add_command(label='cm for menuentry 1')
#soll bei rechtsklick auf eintrag 'label2' im menue 'File' angezeigt werden
context2 = Menu()
context2.add_command(label='cm for menuentry 2')
#soll bei rechtsklick auf 'ok'-button angezeigt werden
context3 = Menu()
context3.add_command(label='cm for button 1')
#soll bei rechtsklick auf menue 'File' angezeigt werden
context4 = Menu()
context4.add_command(label='cm for filemenu')

normalwidget = Button(t,text='ok')
normalwidget.test_contextmenu = context3
normalwidget.pack()

#~~ Erzeugt Menuleiste-Objekt
menubar = Menu(t)
#~~ Fuegt Menuleiste-Objekt dem Hauptfenster zu
t.config(menu=menubar)

#~~ Erzeugt das erste Hauptmenu-Objekt 'File'
menu_file = Menu(menubar,tearoff=False,
    postcommand=lambda lb='File': main_menu_callback(lb, context4))

menubar.add_cascade(menu=menu_file,label='File')

menu_file.add_command(label='label1',
    command=lambda lb='label1': sub_menu_callback(lb, context1))

menu_file.add_command(label='label2',
    command=lambda lb='label2': sub_menu_callback(lb, context2))

t.bind_all('<Button-3>',button3_press)
t.bind_all('<ButtonRelease-3>',button3_release)

#~~ Zusaetzliche Variablen fuer die Menuauswertung
#   (An Hauptfenster-Objekt gebunden)
t.menu_data = ()
t.button3_activated = False
t.app_xroot = 0
t.app_yroot = 0
t.delay_obj = None

t.mainloop()
Es braucht manchmal ein wenig Zauberei um bestimmte TK-Nebeneffekte zu umschiffen. Sicher gibt es noch einige andere neuzeitliche Lösungsansätze.. (Getestet unter Linux SuSE11.0 und Python2.5)

Feststellung beim Verhalten von TK-Menues. Ein Klick mit der linken oder rechten Maustaste auf ein Hauptmenu zeigt seine Untermenues.

Viel Spass beim weiteren Experimentieren. Gruss wuf :wink:
Take it easy Mates!
Prophet
User
Beiträge: 6
Registriert: Dienstag 6. Oktober 2009, 14:08

Ok, das hatte ich auch noch nie.
Ich verstehe nicht warum deine Lösung funktioniert, aber ich denke ich kann mein Problem trotzdem damit auf eine Art lösen die ich verstehe. ^^

Also was mich irritiert ist, dass wenn ich in jede der Funktionen button3_press,button3_release,sub_menu_callback und main_menu_callback zum Testen an erser Stelle ein kleines print mit dem funktionsnamen reinsetze, bekomme ich folgende Ausgabe bei einem Klick auf ein Submenü :

Code: Alles auswählen

main_menu_callback
button3_press
right click on widget of class str
event.widget is not instance of Widget, (class str , content .#3083708844L.#3083708844L#3083708876L)
sub_menu_callback
Submenu-label1
button3_release
Aber wie kann aus diesen Codezeilen hier

Code: Alles auswählen

if t.menu_data: 
     #~~ Zeigt Context-Menu bei Rechtsklick auf ein Hauptmenu 
     show_context_menu() 
print 'event.widget is not instance of Widget, (class %s , content %s)' % (cls,event.widget)
eine solche Ausgabe entstehen? show_context_menu() wird aus der button3_press - Funktion aufgerufen, aber trotzdem erscheint 1. die Ausgabe die hinter dem Funktionsaufruf steht vor der Ausgabe aus diesem Funktionsaufruf ("Submenu-label1") und 2. schiebt sich dazwischen noch der aufruf des sub_menu_callback.
Es passiert genau in der Reihenfolge in der es passieren muss um zu funktionieren, aber ich scheine da noch auf dem Schlauch zu stehen, denn eigentlich müsste es meiner Meinung nach in einer ganz anderen Reihenfolge passieren. Vor allem müssten doch Events von Tkinter in einem Thread und sequentiell abgearbeitet werden, oder nicht? Wie kann dann ein command-callback sich in die Ausführung eines anderen hineinmogeln?
Ich bin grade total verwirrrt. XD ^^

Der Grund warum ich aber trotzdem jetzt denke das Problem lösen zu können ist, dass ich vorher schlichtweg nicht an "<ButtonRelease-3>" gedacht habe. Solange das nach command und postcommand - Aufrufen abgearbeitet wird, kann ich das Kontextmenü ja auch erst mit dem ButtonRelease event anzeigen lassen, was vermutlich sogar eher den Guidelines entspricht, und eben so wie du es jetzt auch gemacht hast die command- und postcommand- funktionen die Menüdaten in einer variable eintragen lassen.
Von diesem Weg würde ich dann wenigstens verstehen, warum er funktioniert. XD

Also auf jeden Fall schon einmal ein dickes Danke an dich, Wuf, und wenn du mir noch erklären kannst was ich übersehe bei der Funktionsweise dieser bestehenden Lösung, würde mich das natürlich auch noch interessieren. ^^
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hallo Prophet

Habe mein Skript auf dein Testskript angepasst:
http://paste.pocoo.org/show/143764/

Dein Testfall ist ein Klick mit der rechten Maustatste auf das Submenu 'label1' im geöffneten Hauptmenu 'File'?

Kannst du die Richtigkeit meiner Skriptanpassung und meine Annahme deines Testfalls bitte bestätigen.

Gruss wuf :wink:
Take it easy Mates!
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hallo Prophet

Hier noch eine längere Beschreibung was in meinem Testskript abläuft:

Test-1:
-------
Aktion:
Mit der linken Maustaste wird auf das Hauptmenu 'File' geklickt.

1)
Öffnet das Hauptmenu 'File', Gibt die Meldung 'main_menu_callback' auf die Konsole aus.

2)
t.menu_data (Speicherung der Menudaten als Tuple)

3)
t.delay_obj (Erstellt ein Verzögerungs-Objekt, welches nach 100ms die Funktion 'delay_timeout' aufruft und das Tuple mit den Menudaten wieder löscht. Die Menudaten braucht bei dieser Aktion nicht mehr.

4)
Auf der Konsole wurde bei dieser Aktion folgendes ausgegeben:

Code: Alles auswählen

    main_menu_callback
Test-2:
-------
Aktion:
Mit der rechten Maustaste wird auf das Hauptmenu 'File' geklickt.

1)
Öffnet das Hauptmenu 'File' (Dieses bleibt offen bis die Maustaste wieder lösgelassen wird. (BEMERKUNG: Das Hauptmenu öffnet sich also mit der linken und rechten Maustaste. Der Unterschied ist: Mit der linken bleibt es offen und mit der rechten bleibt es solange offen bis die Taste wieder losgelassen wird. Beide rufen aber die in Option-'postcommand' angegebene Funktion 'main_menu_callback' auf. (Scheinbar Eigenart von TK/TCL)

2)
Gibt die Meldung 'main_menu_callback' auf die Konsole aus.

3)
Sichert die Menudaten in t.menu_data als Tuple.

4)
Startet das Verzögerungs-Objekt t.delay_obj

5)
Da diesmal mit der rechten Maustaste auf das Hauptmenu geklickt wurde löst es unmittelbar nach dem 'postcommand' des Hauptmenus auch den Event ('<Button-3>',button3_press) aus, welcher die Funktion-'button3_press' aufruft.

6)
In der Funktion-'button3_press' wird als erstes mit print die Meldung-'button3_press' auf die Konsole ausgegeben.

6a)
Der Merker t.button_activated wird auf True gesetzt. (Rechte Maustaste wurde aktiviert) Wird erst beim aktivieren eines Submenus mit der rechten Maustaste benötigt!

6b)
Die Mausposition wird in t.app_xroot und t.app_yroot gesichert.

6c)
Da jetzt t.delay_obj nicht None ist sondern das Verzögerungs-Objekt das zuvor in der Funktion-'main_menu_callback' generiert wurde enthält wird dieses sofort wieder mit t.after_cancel gestoppt und auf None gesetzt. Damit wird verhindert, dass die Menudaten in t.menu_data über die Funktion-'delay_timeout' gelöscht werden!
6d)
Auf Zeile 26 wird kontrolliert ob t.menu_data Menudaten enthält. Dies ist jetzt der Fall und ruft somit die Funktion-'show_context_menu'.

7)
In der Funktion-'show_context_menu' werden mit print die Menudaten auf die Konsole ausgegeben und anschliessend das auf Position t.app_xroot und t.app_yroot Context-Menu geöffnet

8:
Beim loslassen der rechten Maustaste wird über den Event ('<ButtonRelease-3>',button3_release) die Funktion-'button3_release' aufgerufen. Mit print wird die Meldung-'button3_release' auf die Konsole ausgegeben. Die Menudaten t.menu_data werden gelöscht. Der Merker t.button_activated wird wieder auf False gesetzt.

9)
Auf der Konsole wurde bei dieser Aktion folgendes ausgegeben:

Code: Alles auswählen

    main_menu_callback
    button3_press
    right click on widget of class str
    Hauptmenu-File
    event.widget is not instance of Widget, (class str , content .#3083071756L)
    button3_release
Test-3:
-------
Aktion-1:
Mit der linken Maustaste wird auf das Hauptmenu 'File' geklickt. Es läuft das gleiche ab wie in Test-1. Das Hauptmenu öffnet sich und zeigt die Untermenus 'label1' und 'label2'.

Aktion-2:
Mit der rechten Maustaste wird auf das Submenu 'label1' geklickt.

1)
Als erstes wird die Funktion-'button3_press' über den Event ('<Button-3>',button3_press) aufgerufen.

2)
Mit print wird die Meldung-'button3_press' auf die Konsole ausgegeben.
Der Merker t.button_activated wird auf True gesetzt.
Die Mausposition wird in t.app_xroot und t.app_yroot gesichert.
Das Verzögerungs-Objekt t.delay_obj wurde diesmal nicht generiert und hat bei dieser Aktion kein Einfluss.
Die Menudaten t.menu_data enthält noch ein leeres Tuple und hat bei dieser Aktion kein Einfluss
Das ist alles was beim Durchlauf der Funktion-'button3_press' passiert.

3)
Erst jetzt wird über die Funktion-'sub_menu_callback', welche bei der Submenuerstellung der Option-'command' zugewiesen wurde aufgerufen. Interessanterweise beim loslassen der rechten Maustaste!

4)
Als erstes wird mit print die Meldung-'sub_menu_callback' auf die Konsole ausgegeben.
Nun wird kontrollier ob der Merker t.button_activated gesetzt wurde.
Der Merker muss dieses Mal sicher gesetzt sein (Beim Durchlauf der Funktion-'button3_press)
Mit print werden die Menudaten auf die Konsole ausgegeben. (Submenu-label1)
Das Context-Menu wird auf Position t.app_xroot und t.app_yroot geöffnet.

5)
Beim loslassen der rechten Maustaste wird über den Event('<ButtonRelease-3>',button3_release) die Funktion-'button3_release' aufgerufen.
Es passiert das gleiche wie in Test-2 Abschnitt-8

6)
Auf der Konsole wurde bei dieser Aktion folgendes ausgegeben:

Code: Alles auswählen

    main_menu_callback
    button3_press
    right click on widget of class str
    event.widget is not instance of Widget, (class str , content .#3083301132L.#3083301132L#3083301196L)
    sub_menu_callback
    Submenu-label1
    button3_release
Gruss wuf :wink:
Take it easy Mates!
Prophet
User
Beiträge: 6
Registriert: Dienstag 6. Oktober 2009, 14:08

So, hi, da bin ich nochmal. ^^
Danke für die lange Erklärung, aber ich glaube dass ich es nicht verstaden hab lag einfach daran, dass ich übersehen hab, dass aus dem sub_menu_callback ja auch show_context_menu() aufgerufen wird. Damit ist es dann ja eigentlich wieder logisch. ^^"

Vielleicht noch ein Hinweis für andere, die irgendwann mal auf das gleiche Problem stoßen : Mir gefiel es besser die Lösung ohne das delay_timeout zu bauen, weil das je nach Aufbau des Programms ja auch mal zu früh zuschlagen kann.

So in etwa sieht jetzt die Variante aus, die ich verwendet habe :

Code: Alles auswählen

# wuf_ref: menu_event_callback_01_02.py 
 
from Tkinter import * 
 
def button3_press(event): 
     t.button3_activated = True
     
def button3_release(event=None):
     cls = event.widget.__class__.__name__ 
     t.button3_activated = True 
     t.app_xroot = event.x_root 
     t.app_yroot = event.y_root 
 
     print 'right click on widget of class %s' % cls 
     if 'test_contextmenu' in dir(event.widget) : 
         #hier ist alles prima 
         #wir haben ein kontextmenue zum widget gefunden und koennen es darstellen 
         event.widget.test_contextmenu.post(event.x_root,event.y_root) 
     elif not isinstance(event.widget,Widget) : 
         #hier ist das problem 
         #selbst wenn wir dem widget ein kontextmenue in einer variable zugewiesen haben 
         #kommen wir an diese variable nicht heran, weil wir nicht die widget-instanz haben 
         #es gibt auch kein weiteres feld von 'event' das uns hinweise auf das widget liefert 
         if t.menu_data: 
             #~~ Zeigt Context-Menu bei Rechtsklick auf ein Hauptmenu 
             show_context_menu() 
         print 'event.widget is not instance of Widget, (class %s , content %s)' % (cls,event.widget) 
     else : 
         print 'clicked on %s without assigned context-menu' % cls 
     t.menu_data = ()
     t.button3_activated = False 
 
def sub_menu_callback(label, context): 
     #~~ BEMERKUNG: Auf die rechte Maustaste bezogen. 
     #   Wird mit dem loslassen der Maustaste aufgerufen 
     if t.button3_activated: 
         #~~ Zeigt Context-Menu bei Rechtsklick auf ein Submenu 
         t.menu_data = ('Submenu', label, context)
         return
 
def main_menu_callback(label, context): 
     #~~ BEMERKUNG: Auf die rechte Maustaste bezogen. 
     #   Wird mit dem druecken der Maustaste aufgerufen 
     t.menu_data = ('Hauptmenu', label, context) 
 
def show_context_menu(): 
     men_type, men_label, context_menu = t.menu_data 
     print '%s-%s' % (men_type, men_label) 
     context_menu.post(t.app_xroot, t.app_yroot) 
     #der grab muss manuell gesetzt werden um das Kontextmenue "aktiv" zu machen
     #ohne diese zeile werden mouse-events immer noch an das unterhalb des Kontextemenue liegende Menu-Widget gesendet
     context_menu.grab_set()

def button12_press(event):
    t.button3_activated = False
 
t = Tk() 

t.geometry('200x50') 

#soll bei rechtsklick auf eintrag 'label1' im menue 'File' angezeigt werden 
context1 = Menu() 
context1.add_command(label='cm for menuentry 1') 
#soll bei rechtsklick auf eintrag 'label2' im menue 'File' angezeigt werden 
context2 = Menu() 
context2.add_command(label='cm for menuentry 2') 
#soll bei rechtsklick auf 'ok'-button angezeigt werden 
context3 = Menu() 
context3.add_command(label='cm for button 1') 
#soll bei rechtsklick auf menue 'File' angezeigt werden 
context4 = Menu() 
context4.add_command(label='cm for filemenu') 

normalwidget = Button(t,text='ok') 
normalwidget.test_contextmenu = context3 
normalwidget.pack() 

#~~ Erzeugt Menuleiste-Objekt 
menubar = Menu(t) 
#~~ Fuegt Menuleiste-Objekt dem Hauptfenster zu 
t.config(menu=menubar) 

#~~ Erzeugt das erste Hauptmenu-Objekt 'File' 
menu_file = Menu(menubar,tearoff=False, 
     postcommand=lambda lb='File': main_menu_callback(lb, context4)) 
 
menubar.add_cascade(menu=menu_file,label='File') 
 
menu_file.add_command(label='label1', 
command=lambda lb='label1': sub_menu_callback(lb, context1)) 
 
menu_file.add_command(label='label2', 
command=lambda lb='label2': sub_menu_callback(lb, context2)) 
 
t.bind_all('<ButtonPress-3>',button3_press)
t.bind_all('<ButtonPress-1>',button12_press)
t.bind_all('<ButtonPress-2>',button12_press) 
t.bind_all('<ButtonRelease-3>',button3_release) 
 
#~~ Zusaetzliche Variablen fuer die Menuauswertung 
#   (An Hauptfenster-Objekt gebunden) 
t.menu_data = () 
t.button3_activated = False 
t.app_xroot = 0 
t.app_yroot = 0 
t.delay_obj = None 
 
t.mainloop()
(Ich hab das Beispiel nur fix auf meine Variante umgeschrieben. Da könnten durchaus noch Fehler drin sein. Die Grundidee ist aber einfach, mit command und postcommand die Menüdaten einzutragen und dann erst mit dem "<ButtonRelease-3>"-Event das Kontextmenü aufzurufen. Die Unterscheidung welcher Button geklickt wurde habe ich dann einfach ohne Timeout so gelöst, dass ich bei einem Klick auf den rechten Mouse-Button t.button3_activated auf True setze und bei links- und mittel-klick eben auf False.
Sollte mMn genauso gut funtkionieren wie die Timeout-Variante nur eben ohne die Probleme eines Timeouts. ^^
Antworten