Mausrad-Event im Textwidget deaktivieren!?

Fragen zu Tkinter.
Antworten
dahaze
User
Beiträge: 75
Registriert: Freitag 13. März 2009, 10:57
Wohnort: im Schwabenland

Hallo zusammen!
Ich stehe vor einem interessanten Problem:
Im Gegensatz zu anderen will ich kein Mousewheel-Event fangen sondern ich würde ich gerne das Mousewheel-Event bei Textwidgets deaktivieren!
Tkinter scheint dies bei Textfeldern automatisch zu fangen und so das Textfeld, das den Focus besitzt, immer mitscrollen!

Folgendes soll aus Beispielcode dienen:

Code: Alles auswählen

# -*- coding: ISO-8859-15 -*-
import Tkinter

root = Tkinter.Tk()

# Listbox-Scrollbar
lb_sb = Tkinter.Scrollbar(root, orient='vertical')
lb_sb.grid(row=0, column=1, sticky='ns')

# Listbox
lb = Tkinter.Listbox(root, yscrollcommand=lb_sb.set)
lb.grid(row=0, column=0, sticky='nsew')
lb_sb.config(command=lb.yview)

# Textfeld Scrollbar
txt_sb = Tkinter.Scrollbar(root, orient='vertical')
txt_sb.grid(row=1, column=1, sticky='ns')

# Textfeld
txt = Tkinter.Text(root,  yscrollcommand=txt_sb.set)
txt.grid(row=1, column=0, sticky='nsew')
txt_sb.config(command=txt.yview)

def scrollupordown(event):
  """Eventhandler"""
  if event.num == 5 or event.keycode == -120:
    lb.yview_scroll(2, Tkinter.UNITS)
  if event.num == 4 or event.keycode == 120:
    lb.yview_scroll(-2, Tkinter.UNITS)

# Mausrad an die Listbox binden
root.bind('<MouseWheel>',scrollupordown)

# Widgets befüllen
map(lambda i:lb.insert('end', i), range(20))
map(lambda i:txt.insert('end', '%s\n'%i), range(40))

root.mainloop()
Der Effekt:
Solange das Textfeld keinen Focus besitzt wird die entsprechende Listbox wie im Eventhandler beschrieben gescrollt. Erhält das Textfenster dann jedoch den Focus wird dies bei einer Mausradbewegung immer mitgescrollt und verliert den Focus - bezogen auf Mausrad-Event - nie wieder!

Folgendes habe ich bereits probiert um sicherzustellen das nur die Listbox den Focus hat:

Code: Alles auswählen

def scrollupordown(event):
  """Eventhandler"""
  # alter Focus speichern
  last_focus = lb.focus_get()
  # Focus nur auf die Listbox legen
  lb.focus_set()

  # Scrollen
  if event.num == 5 or event.keycode == -120:
    lb.yview_scroll(2, Tkinter.UNITS)
  if event.num == 4 or event.keycode == 120:
    lb.yview_scroll(-2, Tkinter.UNITS)

  # Focus zurücklegen
  last_focus.focus_set()
Leider hat dies überaupt keinen Effekt, es sei denn man legt den Focus am Ende des Handlers nicht mehr zurück auf das Textwidget!
Wenn am Schluss der Focus wieder ins Textwidget gelegt wird, wird dies anschließend noch gescrollt! Als würde das Event erneut intern für das Textwidget aufgerufen?!

Kann mir das vielleicht jemand erklären bzw. weiss jemand ne Lösung wie man dies unterdrückt? ;-)

mfg,
Simon

PS: Python v2.2, TK v8.3
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Wie wäre es damit, das du dein eigenes Scroll-Event nicht an "root", sondern an die Listbox bindest ?

Im übrigen wird "event.num" bei dir nie 4 oder 5, weil du die entsprechenden Events aufrufst. Was bedeutet, das du zB unter X11 nicht scrollen kannst. Hierfür dienen die Event "<Button-4>" und "<Button-5>".

Noch ein paar Anmerkungen:
- Wieso eigentlich Python 2.2 hindert irgendwas den wechsel auf zumindest 2.5 oder 2.6 ?
- wenn du Python 2.x nutzt nimm "xrange" statt "range"
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
dahaze
User
Beiträge: 75
Registriert: Freitag 13. März 2009, 10:57
Wohnort: im Schwabenland

Hallo Xynon1,
danke für deine schnelle Antwort. Daß ich leider noch v2.2 ist firmenbedingt. Würde auch sofort wechseln. ;)
Im Großen und Ganzen sind die Codeschnipsel die ich zu dem Beispiel zusammengeschnitten hab Teile von meinem eigentlichen Eventhandler, deshalb scheint manches zu überzogen.
Das Binding an das Root-Widget hat den Grund, dass ich im eigentlichen Handler nicht das Widget mit dem Focus scrolle sondern das Widget unter dem Mauszeiger, auch egal ob disabled oder nicht.
Ist dieses Widget nicht scrollbar hangel ich mich in der Hirarchie immer tiefer bis zum Root und geb dann erst auf. Es kann ja sein dass in sich verschachtelte Widgets vorliegen und erst das in der 4 Ebene gescrollt werden kann.
Binde ich das Event dann nur an ein "untergeordnetes" Widget funktioniert das Scrollen nur wenn das entsprechende Widget den Focus hat. Und disabled darfs auch nicht sein. Und das Rootwidget hat immer den Focus und ist nie disabled wenn das Fenster aktiv ist.

Das Problem ist halt, dass wenn ich aus dem Eventhandler zurückkomm genau das gleiche Event noch einmal aufgerufen wird - an dem Widget das den Focus hat ?! Und das führt wohl dazu dass sich in meinem Beispiel beide Fenster bewegen. Bzw. das Textwidget wohl irgendwie schon seinen "eigenen" Mousewheel-Handler mitbringt!? Den ich aber nicht haben will... ;)

Gruß,
Simon

PS: range oder xrange sei bei kleinen Listen wohl egal, hab ich irgendwo mal gelesen. Deshalb hab ich einfach range benutzt. :)
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Ok, das ist schwer nachzuvollziehen. Ich habe zwar verstanden was du machen willst, aber dein Beispiel gleicht wohl nicht deinem eigentlich Skript bzw. ist es unglücklich umgesetzt. Denn das Root-Widget kann zwar immer den Focus haben, doch sollten die Scrollevents auf den einzelnen Widgets liegen. Kannst du mir dazu den rellevanten Quelltext zeigen ?
Im moment hängt das fehlerhafte Scrollen daher, dass das Scrollevent nicht richtig gesetzt wurde, denn im Moment tut es genau das was du ihm gesagt hast.

Und ja, bei kleinen Werten ist "range" in Ordnung und frisst sogar weniger speicher, dennoch finde ich es gerade wo du "map"st nicht angebracht, weil man hier so schön generiert. Ist aber letzt endlich irrelevant.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
dahaze
User
Beiträge: 75
Registriert: Freitag 13. März 2009, 10:57
Wohnort: im Schwabenland

Hmmm...
Soweit ichs jetzt kapier funktioniert das Ganze wohl so, dass das ausgelöste Mausrad-Event beginnend vom aktiven Widget nach "unten" bis zum RootWidget durchgereicht wird. Dadurch wird zuerst das - aus irgend einem Grund standardmäßig vorhandene - Scrollevent im Textwidget und zu guter Letzt der von mir gebundene Eventhandler ausgelöst.
Dadurch scrollen bei aktivem Textfenster beide Widgets.

Die Frage ist jetzt nur wie bekomm ich den Eventhandler vom Textwidget deaktiviert?
Das soll ja nicht mitscrollen, auch wenn es aktiv ist.....
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Das wird so nicht gehen, Tkinter ist nur ein Tk-Wrapper, aber die Scrollfunktion ist in Tcl direkt an das Text Template gebunden (in tk8.5 - text.tcl Zeile 466 für Windows)

Code: Alles auswählen

    bind Text <MouseWheel> {
	if {%D >= 0} {
	    %W yview scroll [expr {-%D/3}] pixels
	} else {
	    %W yview scroll [expr {(2-%D)/3}] pixels
	}
    }
Ich hätte jetzt angenommen das man ein Tkinter-"unbind" nutzen könnte oder direkt einen "tk.call", aber das hat auf alle Events die in Tcl geschrieben sind keine Auswirkung.

Im Grunde ist das, was du machen willst ja auch nicht vorgesehen. Wenn du dennoch das umsetzen willst, müsstest du die Wohl ein eigenes Text-Template in Tcl und den Wrapper in Python schreiben. etwas anderes fällt mir leider nicht ein :|

Ich denke eigentlich immernoch, dass das ein Designfehler deinerseits ist, was hast du davon das du kein Maus-Scrollen mehr auf dem Text-Widget hast?
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Antworten