Focus - Events

Fragen zu Tkinter.
Antworten
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Hab mal wieder selbst ein kleines Problem mit Tkinter.

Wenn ich das richtig verstanden habe, dann werden Events nur von dem Widget ausgeführt über welchem die Maus steht, aber nur wenn kein anderes Widget fokusiert ist.
Wenn ein Widget fokusiert sein sollte, dann sollten die Events dort ausgeführt werden, oder ?

So weit die Theorie, nun habe ich aber das bekannte Problem, dass wenn ich ein paar dutzend Widget auf einen Frame packe, den Frame ja nicht mehr mit der Maus drüber fahren und ihn indirekt fokusieren kann.
Also bleibt der mir nur bekannte weg ihn direkt fokusieren zu lassen mit takefocus=True und dann der ".focus_set()"-Methode.

Dies funktioniert aber nur im Windows, unter allen anderen System werden die Events dennoch nicht ausgelöst, obwohl die entsprechenden Widgets manuell fokusiert wurden und über die ".focus_get()" Methode auch als selektiert ausgegeben werden.

Wo liegt also das Problem, habe ich in der Theorie da was falsch verstanden ?

Ziel des ganzen ist es, auf einer Oberfläche mehrere Scrollbars, Spinboxen und Scales per Mausscrollradbedienen zu können. Spinboxen und Scales kein Problem, aber halt die Scrollbars, da ich den Scroll Event auf das gesamte Widget legen möchte und allen unteren Widgets.

Folgendes hatte ich mir ausgedacht und ich bin mir ziemlich sicher, das dies auch schöner geht:

Code: Alles auswählen

#Widgets bei denen auf keinen Fall der Focus verloren gehen darf
_dont_select = [ttk.Entry]

#Widgets auf den der Focus manuell gelegt werden soll
_do_select = [ScrolledLabelFrame, ttk.Scale, tkinter.Spinbox]

#Ein Event, welches mit bind_all("<Motion>", ...) auf das gesamte Widget gelegt wird
def select_scrolls(event): 

    #Das Widget auf dem die Maus steht ermitteln
    widget = event.widget
    while widget is not None and type(widget) is not str:

        #Prüfen ob eine Fokusierung erfolgen soll
        if any(isinstance(widget, w) for w in _do_select):

            #Prüfen ob das aktuell fokusierte Widget abgewählt werden darf
            if not any(isinstance(widget.focus_get(), w) for w in _dont_select):
                widget.focus_set()
            break
        
        #Übergeordnetes Widget setzen und darauf prüfen
        widget = widget.master
So bekomm ich immer das aktuelle Frame worauf gescrollt werden soll, nur das ScrollEvent wird ebend nur im Windows ausgeführt.

Die Scrollevents sehen dann so aus:

Code: Alles auswählen

    ...
        self.bind("<MouseWheel>", self.scroll)        
        self.bind("<Button-4>", self.scroll)
        self.bind("<Button-5>", self.scroll)
    
    def scroll(self, event):
        if event.num == 5 or event.delta < 0:
            self.canvas.yview_scroll(1, "unit")
        elif event.num == 4 or event.delta > 0:
            self.canvas.yview_scroll(-1, "unit")
Jemand eine Idee ?
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hallo Xynon1

Wäre es dir möglich uns ein lauffähiges Skript zur Verfügung zu stellen?

Gruß wuf :wink:
Take it easy Mates!
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Klar, kein Problem.
Darf ich deiner Aussage schon mal entnehmen das ich in der Theorie erstmal nichts falsch gemacht habe ?

und Danke schonmal für das bekundete Interesse, ich habe das Programm so gut wie mir möglich war gekürzt ohne das es den Kontext verliert.

Edit: Achso "from base import ttk", steht im Beispiel nur, weil ich Python 2.6 nutze und dort ttk noch nicht dazu geliefert wird und ich es als extra Modul mitliefere. Also am besten durch "import ttk" ersetzen und mit Python 2.7 testen.
ttk, wird benötigt, weil die Widgets dort scrollcommands haben, welches beim normalen tkinter.Widgets ja nicht der Fall ist.

ok, hier der gekürzte Quellcode, mit kleinem Beispiel:
Habe jetzt aber nur die Scrollbars drin, der Rest geht ja :wink:

Code: Alles auswählen

import Tkinter as tkinter
from base import ttk

class AutoScrollbar(ttk.Scrollbar):
            
    def set(self, low, high):
        if float(low) <= 0.0 and float(high) >= 1.0:
            self.tk.call("grid", "remove", self)
        else:
            self.grid()
        ttk.Scrollbar.set(self, low, high)
        
    def pack(self, **kw):
        raise tkinter.TclError, "cannot use pack with this widget"
    
    def place(self, **kw):
        raise tkinter.TclError, "cannot use place with this widget"
    
        
class ScrolledLabelFrame(ttk.LabelFrame):
    
    def __init__(self, master, text="", cnf=dict()):
        ttk.LabelFrame.__init__(self, master, text=text, takefocus=True)
        
        self.vbar = AutoScrollbar(self)
        self.vbar.grid(row=0, column=1, sticky="ns")
        self.hbar = AutoScrollbar(self, orient="horizontal")
        self.hbar.grid(row=1, column=0, sticky="ew")
        
        self.width = 0
        self.height = 0
        if "width" in cnf:
            self.width = cnf["width"]     
        if "height" in cnf:
            self.height = cnf["height"]        
        
        self.canvas = tkinter.Canvas(self, cnf)
        self.canvas.config(yscrollcommand=self.vbar.set)
        self.canvas.config(xscrollcommand=self.hbar.set)
        self.canvas.grid(row=0, column=0)
        self.frame = ttk.Frame(self.canvas)

        self.vbar.config(command=self.canvas.yview)
        self.hbar.config(command=self.canvas.xview)
        
        self.bind("<MouseWheel>", self.scroll)        
        self.bind("<Button-4>", self.scroll)
        self.bind("<Button-5>", self.scroll)
        self.bind("<Configure>", self.update)
        self.update()
    
    def scroll(self, event):
        if event.num == 5 or event.delta < 0:
            self.canvas.yview_scroll(1, "unit")
        elif event.num == 4 or event.delta > 0:
            self.canvas.yview_scroll(-1, "unit")
           
    def update(self, event=None):
        self.canvas.delete("all")
        self.canvas.create_window(0, 0, anchor="nw", window=self.frame)
        
        self.update_idletasks()
        if self.width == 0:    
            self.canvas.config(width=self.frame.winfo_width())
            
        if self.height == 0:
            self.canvas.config(height=self.frame.winfo_height())
       
        self.canvas.config(scrollregion=self.canvas.bbox("all"))


_dont_select = [ttk.Entry]
_do_select = [ScrolledLabelFrame, ttk.Scale, tkinter.Spinbox]

def select_scrolls(event): 
    widget = event.widget
    while widget is not None and type(widget) is not str:
        if any(isinstance(widget, w) for w in _do_select):
            selected_widget = widget.focus_get()
            if not any(isinstance(selected_widget, w) for w in _dont_select):
                widget.focus_set()
            break
        widget = widget.master
        
        
if __name__ == "__main__":
    root = tkinter.Tk()
    root.bind_all("<Motion>", select_scrolls)

    for _ in xrange(5):
        slf = ScrolledLabelFrame(root, "Test", dict(height=500))
        slf.pack(side="left")
        
        for i in xrange(60):
            tkinter.Label(slf.frame, text="Hello World"[:i%12]).pack()

        slf.update()

    root.mainloop()
Zuletzt geändert von Xynon1 am Montag 13. Dezember 2010, 10:10, insgesamt 1-mal geändert.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hallo Xynon1

Erstmals Danke für dein Skript. Meine Programmierkenntnisse hinken leider ein wenig hinter deinen nach da ich nicht ein Alltagsprogrammierer bin. Die ttk Widgets habe ich bis jetzt noch nie eingesetzt. Für mich noch Neuland. Vielleicht nicht mehr lange :wink:
Xynon1 hat geschrieben:Darf ich deiner Aussage schon mal entnehmen das ich in der Theorie erstmal nichts falsch gemacht habe ?
Finde ich auch.

Bei mir ist das ttk Modul nur unter Python3.1 verfügbar. Ich passte dein Skript entsprechend an um es starten zu können.

Habe etwas vielleicht verückes ausprobiert:

Code: Alles auswählen

        self.bind("<MouseWheel>", self.scroll)
        self.vbar.bind("<MouseWheel>", self.scroll)
        self.frame.bind("<MouseWheel>", self.scroll)

        self.bind("<Button-4>", self.scroll)
        self.vbar.bind("<Button-4>", self.scroll)
        self.frame.bind("<Button-4>", self.scroll)

        self.bind("<Button-5>", self.scroll)
        self.vbar.bind("<Button-5>", self.scroll)
        self.frame.bind("<Button-5>", self.scroll)
Es sieht fast so aus als ob die Events an alle sich im Scrollfeld befindenden Objekte gebunden werden müssen. Habe hier die Label-Objekte noch nicht mit dem Event verbunden. Wenn du jetzt mit der Maus ins gewünschte Scrollfeld bzw. auf den Scrollbar fährts kannst es mindestends mit dem Mausrad bewegen. Natürlich sobald sich die Maus über einem Label befindet geht es nicht da diese noch nicht an einen Event gebunden sind. Vielleicht gelingt es dir mit deinen gute Programmierkenntissen das ganze noch zu vereinfachen.

Habe es hier nur unter meinem OS SuSE 11.0 ausprobiert bei welchem die Events 'Button-4' & 'Button-5' für das Mausrad zuständig sind

Gruß wuf :wink:
Take it easy Mates!
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

wuf hat geschrieben: Die ttk Widgets habe ich bis jetzt noch nie eingesetzt. Für mich noch Neuland. Vielleicht nicht mehr lange.
Sind nicht schwer zu handhaben, eigenlich ziemlich genauso wie die normalen tkinter.Widgets, nur nicht ganz so konfigurierbar.
Eigentlich habe sie nur 2 Vorteile:
1. Sie bieten einige Elemente die in Tkinter fehlen, zB Notebook oder SizeGrip.
2. Sie bieten ein CSS ähnliches Style System.

Das die normalen Tkinter-Widgets keine Scrollcommands haben, wurde mir auch vorhin erst klar als ich bei dem Minimalbeispiel, da oben, das ttk-Modul weglassen wollte.
wuf hat geschrieben: Bei mir ist das ttk Modul nur unter Python3.1 verfügbar. Ich passte dein Skript entsprechend an um es starten zu können.
Mh, richtig ttk, ist in Python 2.7 nur vorhanden wenn du es dir selber kompilierst.
In den Paketverwaltungen ist es meist nicht dabei.
wuf hat geschrieben: Es sieht fast so aus als ob die Events an alle sich im Scrollfeld befindenden Objekte gebunden werden müssen. Habe hier die Label-Objekte noch nicht mit dem Event verbunden. Wenn du jetzt mit der Maus ins gewünschte Scrollfeld bzw. auf den Scrollbar fährts kannst es mindestends mit dem Mausrad bewegen. Natürlich sobald sich die Maus über einem Label befindet geht es nicht da diese noch nicht an einen Event gebunden sind. Vielleicht gelingt es dir mit deinen gute Programmierkenntissen das ganze noch zu vereinfachen.
Hehe, das weiß ich ja :wink:, deshalb habe ich mit einer "globale" Event Steuerung, die höheren Events selektieren lassen, damit ich genau das nicht machen muss.
Denn wenn ich das Widgetirgendwo hinpacke, habe ich meist keine Ahnung was da noch alles drauf kommt und bei jedem Widget was ich drauf packe immer wieder das binding zu machen, halte ich für nicht sehr schön.
wuf hat geschrieben: Habe es hier nur unter meinem OS SuSE 11.0 ausprobiert bei welchem die Events 'Button-4' & 'Button-5' für das Mausrad zuständig sind
Ja, diese gelten für alle Linux-Ditributionen, Mac konnte ich noch nicht testen, sollte aber auch gehen.
Nur Windows macht wieder ein auf extra mit "MouseWheel" hier bekommt man immer +-120 (warum auch immer, habe es einfach als < 0 > 0 eingestuft, reicht ja um die Richtung zu bestimmen).


Also das hilft mir bei meinem Problem noch nicht wirklich,
aber für es mal wenn du Zeit hast mit wine oder unter Windows aus, dort funktioniert es schon, da kannst du es dir mal ansehen.
Das Problem ist halt wie ich schon im ersten Post schrieb, obwohl ein bestimmtes Widget unter einer Linux-Distribution selektiert ist wird nur das Event von dem Widet ausgelöst über welchem sich die Maus befindet.

btw Wenn du noch Fragen zu meinen Script braucht (auch wenn sie nicht zur Lösung meines Problemes dienen), kannst du sie gerne stellen.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

So, ich habe jetzt kapituliert, habe es einfach nicht hinbekommen.
Ich lass es jetzt einfach so, das das Scrollevent nur auf den Scrollbars liegt.

Wenn dennoch jemand einen Einfall hat, wäre ich immer noch Interessiert.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Antworten