mousebutton-event & aktuelles widget

Fragen zu Tkinter.
Antworten
tuner
User
Beiträge: 32
Registriert: Sonntag 23. April 2006, 22:17

hy,

ich habe eine listbox definiert, und will bei einem mausklick den inhalt der aktuellen box auslesen.

Code: Alles auswählen

x=event.widget.get(ACTIVE)
gibt jedoch immer den text des fensters aus, das vorher aktiv war.
wie kann man den das beheben?

thx, toni.
Benutzeravatar
Michael Schneider
User
Beiträge: 569
Registriert: Samstag 8. April 2006, 12:31
Wohnort: Brandenburg

tuner hat geschrieben:

Code: Alles auswählen

x=event.widget.get(ACTIVE)
gibt jedoch immer den text des fensters aus, das vorher aktiv war.
wie kann man den das beheben?
Hi Toni,

dass Du keinen Event bei der Änderung (vermutlich auch Active) der Listbox fangen kannst, könnte daran liegen, dass sie bei Änderung der Auswahl keinen Event generiert :) - jedenfalls keinen, den ich kenne. Wo rufst Du diese Zeile denn im Code auf? Woher kommt "event"?

Wenn Du es unbedingt über eine Listbox machen musst, wirst Du den Zustand regelmäßig abfragen müssen - entweder mit einem thread + sleep oder mit der widget-callback-Methode "after". Die Listbox dient eigentlich nur der Auswahl/Markierung. Für Deine Zwecke empfehle ich Dir einen Frame mit Optionsfeldern (wenn Du nur eine Auswahl zulassen möchtest) oder mit Checkboxen für Mehrfachauswahlen. Alles in eine Klasse verpackt sieht der erste Fall in etwa so aus:

Code: Alles auswählen

import Tkinter as TK

class FrameListbox(TK.Frame):
	def __init__(self, lEintraege, Command = None):
		TK.Frame.__init__(self)
		self.lEintraege, self.Command = lEintraege, Command
		if self.lEintraege: self.add_elements()
		
	def add_elements(self):
		for sEintrag in self.lEintraege:
			def LocalCommand(sEintrag = sEintrag): self.Command(sEintrag)

			TK.Radiobutton(self, 
				text = sEintrag, 
				value = sEintrag, 
				command = LocalCommand,
				indicatoron = False).\
			grid(sticky = TK.NSEW,
				ipadx = 40,
				ipady = 2)

def Ausgabe(sEintrag):
	print "Eintrag gewaehlt:", sEintrag
	
f = FrameListbox(("Hallo", "Welt!", "ein", "einfacher", "Text"), Ausgabe)
f.master.wm_title("Listboxersatz")
f.grid()
Der FrameListbox übergibst Du als erstes eine Sequenz mit den Beschriftungen der Auswahlfelder und als zweites Argument die Handler-Funktion, der der Text des ausgewählten Feldes übergeben wird. Übrigens: die Funktion LocalCommand wird benötigt, damit in jedem Schleifendurchgang eine neue Funktion erzeugt wird, die als Argument den jeweiligen Knopf-Text enthält und so die spätere Unterscheidung ermöglicht.
Reicht das für Deine Zwecke oder brauchst Du unbedingt eine Listbox?

Grüße,
der Michel
Diese Nachricht zersört sich in 5 Sekunden selbst ...
Benutzeravatar
Mawilo
User
Beiträge: 452
Registriert: Sonntag 22. Februar 2004, 10:58
Wohnort: Sachsen
Kontaktdaten:

Hallo,

eine Möglichkeit wäre auch, die Listbox mit einem Doppelklick abzufragen.
Das sollte gehen:

Code: Alles auswählen

import Tkinter as tk

def ausgabe(event):
    print listbox.get('active')

root = tk.Tk()
listbox = tk.Listbox(root,height=10)
listbox.bind('<Double-Button-1>',ausgabe)
listbox.pack()

for x in range(10):
    listbox.insert(tk.END,x)
    
root.mainloop()
Stephan
Benutzeravatar
Michael Schneider
User
Beiträge: 569
Registriert: Samstag 8. April 2006, 12:31
Wohnort: Brandenburg

Stephan hat geschrieben: eine Möglichkeit wäre auch, die Listbox mit einem Doppelklick abzufragen.
Hi Stephan,
ein guter Einfall. Jetzt habe ich auch erst verstanden, worauf Toni anspielte. Das Problem hatte ich damals schon in Visual Basic: es werden mehrere Reaktionen auf das Ereignis "Änderung der Auswahl" ausgeführt. Leider wird z.B. das click-Ereignis vorher an Python gemeldet, erst dann wird die Selection umgeschaltet. D.h. bei der Abfrage bekommt man immer noch den Stand vor der Selection-Änderung. Aufbauend auf Deinen Code habe ich mal zwei Möglichkeiten der Verzögerung eingebaut:

Code: Alles auswählen

import Tkinter as tk
import thread, time

def ausgabe(event = None):
    if event: time.sleep(0.01)
    print listbox.get('active')

root = tk.Tk()
listbox = tk.Listbox(root,height=10)
def start_after(event): listbox.after(10, ausgabe)
def start_thread(event): thread.start_new_thread(ausgabe, (event,))
listbox.bind('<ButtonRelease-1>', start_after)
#listbox.bind('<ButtonRelease-1>', start_thread)
listbox.pack()

for x in range(10):
    listbox.insert(tk.END,x)
   
root.mainloop()
Weg 1: Methode after
In Zeile 11 wird eine Hilfsprozedur definiert, die über die Widget-Methode "after" den Eventhandler nach 10 Millisekunden aufruft. Auf diesem Wege geht leider das Eventobjekt verloren, daher ist event in der Funktion "ausgabe" optional. Es spielt in diesem Fall auch keine Rolle, aber wenn man es braucht, kann man es in der Funktion start_after in die Listbox verlinken

Weg 2: thread und time.sleep
Die Hilfsprozedur Zeile 12 wird in Zeile 14 (Raute in Z. 13 versetzen) aufgerufen und ruft den Eventhandler Z. 5 in einem neuen Thread. Hier kann das event-Objekt direkt übergeben werden, das auch gleich als Kriterium genutzt wird, ob noch einmal 10 ms gewartet werden soll.

In beiden Fällen sollten die 10 ms genügen, damit sich die Selection der Listbox ändert.
Aber wenn ein Doppelklick ok ist, finde ich Stephans Variante am elegantesten.

Grüße,
der Michel
Diese Nachricht zersört sich in 5 Sekunden selbst ...
tuner
User
Beiträge: 32
Registriert: Sonntag 23. April 2006, 22:17

Thx,
eins davon wird wohl mein Problem lösen.

Nebenbei: gibt es unter Tk keine Möglichkeit, eine 'aktualisiere Frames'-Methode oder ähnliches auszuführen?

Gruss.
Benutzeravatar
Michael Schneider
User
Beiträge: 569
Registriert: Samstag 8. April 2006, 12:31
Wohnort: Brandenburg

Hi Toni!
tuner hat geschrieben:
eins davon wird wohl mein Problem lösen.
Das Problem lösen sie alle. Fragt sich nur, welche Kompromisse Du zwischen Codierung und Ausführung/Intuitive Nutzung schließen möchtest. Da es keinen integrierten Change-Event zu geben scheint, wird zumindest eines von beidem aufwendiger.
tuner hat geschrieben: Nebenbei: gibt es unter Tk keine Möglichkeit, eine 'aktualisiere Frames'-Methode oder ähnliches auszuführen?
Ich habe mich mit TKinter noch nicht soo tiefgreifend beschäftigt, aber vielleicht hilft die update-methode. Du kannst eigene Update-methoden schreiben und diese zeitgesteuert aufrufen, oder gebunden an beliebige events. Dazu würde mir spontan die methode bind_class einfallen:

Code: Alles auswählen

import Tkinter as TK

def ausgabe(event): 
	print "%s - Klickposition: (%i, %i)" % (event.widget.wm_title(), event.x, event.y)

tk = TK.Tk()
TK.Label(tk, text = "klick in die Toplevel Fenster").grid(padx = 30, pady = 5)
tk.bind_class("Toplevel", "<1>", ausgabe)
tk.wm_title("Hauptfenster")
wTL1 = TK.Toplevel(tk)
wTL1.wm_title("Toplevel 1")
wTL2 = TK.Toplevel()
wTL2.wm_title("Toplevel 2")

tk.mainloop()
In Zeile 8 wird der Event allen Elementen der Klasse Toplevel zugewiesen, Du brauchst Dich also nicht mehr einzeln darum kümmern. Das geht natürlich auch mit Frames, war nur zur Veranschaulichung. Die möglichen Namen bekommst Du über die widget-Methode "bindtags". So kannst Du zum einen normale events abfangen, aber auch eigene erzeugen.

Grüße,
der Michel
Diese Nachricht zersört sich in 5 Sekunden selbst ...
Antworten