Label als Klasse ableiten und <Button-1> an Objekt binden

Fragen zu Tkinter.
Antworten
mintpc
User
Beiträge: 50
Registriert: Montag 23. Januar 2012, 12:44

Hallo zusammen,

ein kleines Problem, wo ich nicht weiter komme.

Ich möchte vom Label aus tkinter eine Klasse ableiten. Bei den erzeugten Objekten
soll man draufklicken können, so dass etwas passiert.

Konkret sollen mit diesen Objekten einfach mehrere Lampen simuliert werden, die
beim Draufklicken an- bzw. ausgehen, dargestellt durch die Farben weiß und rot.

Ich hab nun schon eine Klasse von Label abgeleitet, mehrere Instanzen erzeugt und
auf dem Fenster platziert. Das funktioniert. Der Code sieht so aus:

Code: Alles auswählen

class Lampe(Label):
    def __init__ (self, master, bg=None, width=None, height = None):          
       Label.__init__(self, master, bg=bg, width=width, height = height)


Nun bleibt da also noch das Problem mit dem binden des Button-Ereignisses.

Ich hab viiiieeeeele Varianten probiert, auch das Folgende, klappt aber alles
nicht wirklich.

Code: Alles auswählen

def LampeClick ():
    print("Hallo")

class Lampe(Label):
    def __init__ (self, master, bg=None, width=None, height = None):          
       Label.__init__(self, master, bg=bg, width=width, height = height)    
       Label.bind(self,sequence="<Button-1>", func = LampeClick)
Wie geht es richtig?

Danke schonmal
mintpc
bfm
User
Beiträge: 88
Registriert: Donnerstag 14. März 2013, 09:42

Hallo,

und wenn du das Label gleich durch einen Button ersetzt und bei einem Klick dynamisch die Hintergrundfarbe wechselst?
Benutzeravatar
Michael Schneider
User
Beiträge: 569
Registriert: Samstag 8. April 2006, 12:31
Wohnort: Brandenburg

Hi mintpc,
mintpc hat geschrieben:klappt aber alles nicht wirklich.
damit meinst Du wahrscheinlich, dass Python sich beschwert, weil Du im Handler das event-Objekt nicht entgegennimmst?
Ich würde den Handler auch in die abgeleitete Klasse integrieren, da sie von ihr ausgelöst wird und sich auf sie bezieht:

Code: Alles auswählen

from Tkinter import *
 
class Lampe(Label):
    BG_COLOR_ON = "yellow"
    BG_COLOR_OFF = "red"

    def __init__ (self, *args, **kwds):          
        Label.__init__(self, *args, **kwds)    
        self.bind(self,sequence="<ButtonPress-1>", func=self.toggle_lamp)

    def toggle_lamp(self, event=None):
        print("Lampe angeklickt")
        if self["bg"] == self.BG_COLOR_ON:
            self.config(bg=self.BG_COLOR_OFF)
        else:
            self.config(bg=self.BG_COLOR_ON)
        
if __name__ == "__main__":
    lamp = Lampe(bg="yellow", text="Lampe")
    lamp.grid()
    lamp.mainloop()
VG,
Michael

EDIT: Blackjacks Kommentaren kann ich nur zustimmen - habe das Beispiel entsprechend angepasst. Dass sich das 'bind' auf die Klasse bezieht, habe ich übersehen und mitkopiert. Es könnte sogar Sinn machen, ein Ereignis einmalig an eine Klassencallbackfunktion zu binden, damit sie für alle Lampen gilt. Dann aber nicht im Konstruktor der Instanzen.
Die Konstanten BG_COLOR_ON und BG_COLOR_OFF sind auf Klassenebene definiert, werden aber von der Instanz abgerufen. So kann man bei Bedarf eigene Farben für einzelne Lampen in der Instanz definieren.
Zuletzt geändert von Michael Schneider am Dienstag 25. November 2014, 11:15, insgesamt 1-mal geändert.
Diese Nachricht zersört sich in 5 Sekunden selbst ...
BlackJack

Aus dem ``Lamp.bind(…)`` würde ich noch ein ``self.bind(…)`` machen und vielleicht den Zustand der Lampe von den Zeichenketten für die Farbe trennen.

Edit: Und die Rückruffunktion passender benennen, zum Beispiel in `toggle()` oder `switch()` oder so, denn die möchte man vielleicht auch von aussen mal aufrufen.
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hier noch ergänzend gestützt auf Anregungen von BlackJack:

Code: Alles auswählen

try:
    import Tkinter as tk
except ImportError:
    import tkinter as tk

NUM_OF_LAMPS = 8
LAMP_NAME = "Lampe"
LAMP_ON_COLOR = "red"
LAMP_OFF_COLOR = 'yellow'
ON_STATE = "ein"
OFF_STATE = "aus"

class Lamp(tk.Label):
    def __init__ (self, parent, callback=None, *args, **kwds):
        self.parent = parent
        self.callback = callback
                 
        tk.Label.__init__(self, parent, *args, **kwds)    
        self.bind("<ButtonPress-1>", self.toggle)
        
        self.state = False
        self.switch()
 
    def toggle(self, event=None):
        if self.state:
            self.state = False
        else:
            self.state = True
        self.switch(self.state)
        
    def switch(self, state=False):
        self.state = state    
        self.lamp_color(state)
        
        if self.callback != None:
            if state:
                state_name = ON_STATE
            else:
                state_name = OFF_STATE
                
            self.callback(self['text'], state_name)
        
    def lamp_color(self, state):
        if state:
            self.config(bg=LAMP_ON_COLOR)
        else:
            self.config(bg=LAMP_OFF_COLOR)

def lamp_callback(lamp_name, state):
    print(lamp_name, state)
    
def main():
    lamps = list()
    
    app_win = tk.Tk()
    
    for lamp_nr in range(NUM_OF_LAMPS):
        lamp = Lamp(app_win, lamp_callback, bg=LAMP_OFF_COLOR,
            text="{}-{}".format(LAMP_NAME, lamp_nr), padx=4)
        lamp.pack(padx=10, pady=5)
        lamps.append(lamp)
    
    lamps[0].switch(True)
    
    app_win.mainloop()
    
if __name__ == "__main__":
    main()
Gruss wuf :wink:
Take it easy Mates!
Benutzeravatar
Michael Schneider
User
Beiträge: 569
Registriert: Samstag 8. April 2006, 12:31
Wohnort: Brandenburg

Puh wuf, da hast Du aber weit ausgeholt. Ich wollte eigentlich nur ein kleines (funktionierendes) Beispiel für die Funktion geben und jetzt hat der Fragesteller ein ganzes Rahmenprogramm inklusive globale Konstanten, Hilfsfunktionen und modulsicherer Startfunktionalität. :shock:
Diese Nachricht zersört sich in 5 Sekunden selbst ...
mintpc
User
Beiträge: 50
Registriert: Montag 23. Januar 2012, 12:44

Super. Vielen Dank für die beiden sehr ausführlichen Beispiele.

Hab mich grad mal ne Zeit lang in beide Beispiele eingearbeitet und
hab's jetzt verstanden.

Nochmals vielen vielen Dank für die Mühe und die Hilfe.

mintpc
Sirius3
User
Beiträge: 18216
Registriert: Sonntag 21. Oktober 2012, 17:20

@wuf: die toggle-Methode sieht etwas seltsam aus, da sie sowohl `self.state` setzt als auch `switch` aufruft, das selbst auch `self.state` setzt. Toggle würde ich so schreiben:

Code: Alles auswählen

    def toggle(self, event=None):
        self.switch(not self.state)
Das Defaultargument von switch ist auch unintuitiv. Warum bevorzugst Du AUS vor AN beim Schalten? Wenn es keine Vorbelegung gibt, die tatsächlich dem Normalfall entspricht, sollte man gar keine Default-Werte setzen, dann ist bei jedem Aufruf klar, wie da geswitcht wird. Dann würde ich die AN-AUS-Konstanten in ein Dictionary zusammenfassen:

Code: Alles auswählen

LAMP_COLOR = {False:'yellow', True:'red'}
STATE_NAME = {False:'aus', True: 'ein'}
[...]
    def switch(self, state):
        self.state = state    
        self.lamp_color(state)
       
        if self.callback is not None:
            self.callback(self['text'], STATE_NAME[state])
       
    def lamp_color(self, state):
        self.config(bg=LAMP_COLOR[state])
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hi Sirius3

Besten Dank für deine lehrreichen Anregungen. Habe diese in mein Skript einfliessen lassen. Werde sie auch in Zukunft bei ähnliche Konstellationen anwenden.

Gruss wuf :wink:
Take it easy Mates!
Antworten