Mausereignisse Parameter

Fragen zu Tkinter.
Antworten
greedy
User
Beiträge: 7
Registriert: Sonntag 20. November 2011, 12:36

Hallo Pythonfreunde,

ich bräuchte einmal eure Hilfe und zwar möchte ich 10 Labels erstellen (Anzahl soll schnell veränderbar sein), welche mit verschiedenen mausereignissen angeklickt werden können.
Hierzu erstmal meine Idee. (bin python anfänger)

Code: Alles auswählen

x = []
for j in range(0,11):
     x.append(j)

for i in range(0,11):
    x[i] = tkinter.Label(main, text = "test")
    x[i]["height"] = 5
    x[i]["width"] = 5
    x[i]["anchor"] = "w"
    x[i]["bg"]= "#000000"
    x[i]["fg"]= "#FFFFFF"
    x[i].pack()
hiermit erzeuge ich erstmal meine labels, welche ich in x abspeicher.

jetzt möchte ich aber noch ein mausereignis an die x binden

Code: Alles auswählen

   x[i].bind("<Button 3>", fgRechtsklick)
und die Funktion sieht so aus:

Code: Alles auswählen

def fgRechtsklick(e):
    x[]["fg"] = "#333333" #hier hapert es bei mir
ab hier habe ich keine Idee, wie ich für jedes einzelne x diesen befehl ausführen soll. Kann man irgendwie das bestimmte Label per Parameter übergeben?
also quasi fgRechtsklick(i)

Gruß Greedy
BlackJack

@greedy: Du könntest Dir zum einen mal das Argument `e` anschauen was da übergeben wird. Ansonsten kannst Du auch eine Funktion schreiben, die das `Label`-Exemplar und ein Ereignisobjekt entgegen nimmt und beim `bind()` mit `functools.partial()` das erste Argument fest an das entsprechende `Label` binden.

Dein Quelltext zum erstellen und füllen von `x` ist übrigens ziemlich umständlich und „unpythonisch“. In Python sind solche Indexzugriffe in den allermeisten Fällen eine unnötige indirektion. Wenn man ``for i in range(len(obj)):`` schreibt, nur um `i` dann für ``obj``-Zugriffe zu verwenden, macht man in 99,9% der Fälle etwas umständlicher als es sein muss. Ungetestet:

Code: Alles auswählen

labels = list()
for _ in range(0, 11):
    label = tkinter.Label(
        main,
        text='test',
        height=5,
        width=5,
        anchor=tkinter.W,
        bg='black',
        fg='white'
    )
    label.pack()
    labels.append(label)
greedy
User
Beiträge: 7
Registriert: Sonntag 20. November 2011, 12:36

habe mich jetzt nochmal über den event parameter informiert und das dabei gefunden:
x, y Koordinaten des Mauszeigers bezogen auf die linke obere Ecke des Widgets
char Zeichen, wenn das Event durch eine Taste ausgelöst wurde
num Maustaste, wenn es sich um einen Mausklick handelt
widget Referenz auf das Widget, durch das das Event ausgelöst wurde
time Zeitwert in Millisekunden zur Bestimmung der Zeitspanne zwischen 2 Events
widget müsste dabei doch das richtige für mich sein oder? weil es ja die Referenz auf das jeweilige Label ist, aber bei print(e.event) bekomme ich nur irgendeine nummer(referenz) wie kann ich mit der arbeiten und der funktion fgchange sagen, dass die funktion von dem jeweiligen label aufgerufen wurde?
Ansonsten kannst Du auch eine Funktion schreiben, die das `Label`-Exemplar und ein Ereignisobjekt entgegen nimmt und beim `bind()` mit `functools.partial()` das erste Argument fest an das entsprechende `Label` binden.
den abschnitt habe ich noch nicht ganz verstanden :(

Danke für die Antwort und die Verbesserung der Schleife :)
BlackJack

@greedy: Du meinst wahrscheinlich `e.widget` und nicht `e.event`. Was würdest Du denn erwarten wenn Du ein `Label`-Exemplar mit `print()` ausgibst? Oder anders gesagt: Gib doch einfach mal die `Label`\s, die Du erstellst mit `print()` aus.
greedy
User
Beiträge: 7
Registriert: Sonntag 20. November 2011, 12:36

oh stimmt, ich meinte natürlich e.widget

hab ich ja schon

Code: Alles auswählen

.3068071372
.3068070956
.3068070796
.3068070636
.3068289996
.3068070636
.3068070636
.3068070636
.3068070796
.3068289996
.3068289996
.3068289996
.3068513164
.3068289996
.3068513164
.3068070636
.3068070700
.3068070700
.3068070796
.3068070796
.3068070796
.3068070860
kann ich damit denn in irgendeiner weise in der labels liste auf das entsprechende label zugreifen und den fg verändern?


ah ich bin zu einer lösung gekommen und zwar mit e.widget["fg"] = "#CCCCCC"
aber warum wird das widget als lange zahl geprintet?
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hi greedy

Hier etwas zum herumspielen:

Code: Alles auswählen

import tkinter

def label_callback(event):
    """Links-Klick"""
    event.widget.config(fg='red')

main = tkinter.Tk()

labels = list()

for _ in range(0, 11):
    label = tkinter.Label(
        main,
        text='test',
        height=5,
        width=5,
        anchor=tkinter.W,
        bg='black',
        fg='white'
    )
    label.pack()
    label.bind('<Button-1>', label_callback)
    labels.append(label)

#~~ Setzt die Schriftfarbe des letzten Labels auf grün
labels[-1].config(fg='green')

labels[8].config(bg='yellow', fg='blue')

main.mainloop()
Gruss wuf :wink:
Take it easy Mates!
BlackJack

@greedy: Du musst Dir den Unterschied zwischen einem Objekt und dessen Darstellung als Zeichenkette klar machen. Du kannst mit `print()` keine Objekte ausgeben, sondern immer nur eine Zeichenkette. Objekte müssen also in eine Zeichenkette umgewandelt werden. Bei Python-Objekten die ein Tk-Objekt repräsentieren ist das in der Regel halt *so* eine Zeichenkette.
greedy
User
Beiträge: 7
Registriert: Sonntag 20. November 2011, 12:36

danke für das beispiel wuf, und danke an blackjack für die erklärung.

jetzt habe ich noch eine kurze frage zu dem Bereich OOP.
Wenn ich das ganze in oop programmieren will,
dass sagt er mir:
AttributeError: 'Feld' object has no attribute 'event'
es hängt da doch irgendwie mit dem self zusammen oder?

Code: Alles auswählen

 ##Funktion für Linksklick
    def bgChangeLeft(self,event):
        
        event.widget["bg"] = "#000000"
was muss ich verändern? danke schonmal für die antworten
BlackJack

@greedy: Von *dem* Quelltext kann *die* Meldung nicht kommen.
greedy
User
Beiträge: 7
Registriert: Sonntag 20. November 2011, 12:36

hier der ganze quellcode

Code: Alles auswählen

import tkinter

###

class Feld(object):

    def __init__(self):
        
        #Fenster
        self.main = tkinter.Tk()
        
        #Liste erstellen
        self.labels = list()

        #Labels für Liste erstellen
        for _ in range(0,16):
            self.label = tkinter.self.Label(
                self.main,
                height=2,
                width=5,
                bg="white")
            self.label.pack()

            #Mausereignisse binden
            self.label.bind('<Button-1>', self.bgChangeLeft)
            self.label.bind('<Button-3>', self.bgChangeRight)
            self.label.bind('<Double-Button>', self.bgChangeDouble)

            #Label in LabelsListe packen
            self.labels.append(self.label)
            

    ##Funktion für Linksklick
    def bgChangeLeft(self,e):
        
        e.widget["bg"] = "#000000"
        

    ##Funktion für Linksklick
    def bgChangeRight(self,e):
        
        e.widget["bg"] = "#333333"
        

    ##Funktion für Doppelklick
    def bgChangeDouble(self,e):
        
        e.widget["bg"] = "#FFFFFF"
        

    def run(self):

        self.main.mainloop()

###
Feld().run()
gruß greedy
BlackJack

@greedy: Da kommt aber ein anderer Fehler als Du weiter oben behauptet hast. Und der sollte eigentlich auch klar sein: Das `tkinter`-Modul hat kein Attribut mit dem Namen `self`.

Edit: Davon abgesehen scheint das auch zu funktionieren. Wobei ich nicht ganz glaube dass das dritte Event das ist was Du wolltest.
greedy
User
Beiträge: 7
Registriert: Sonntag 20. November 2011, 12:36

ups hab die vorgängerversion gepostet.

hier ist mein derzeitiger code

Code: Alles auswählen

import tkinter

###

class Feld(object):

    def __init__(self):

        #Fenster
        self.main = tkinter.Tk()
        
        #Liste erstellen
        self.labels = list()

        #Labels für Liste erstellen
        for _ in range(0,16):
            self.label = tkinter.Label(
                self.main,
                height=2,
                width=5,
                bg="white")
            self.label.pack()

            #Mausereignisse binden
            self.label.bind('<Button-1>', self.bgChangeLeft)
            self.label.bind('<Button-3>', self.bgChangeRight)
            self.label.bind('<Double-Button>', self.bgChangeDouble)

            #Label in LabelsListe packen
            self.labels.append(self.label)
            

    ##Funktion für Linksklick
    def bgChangeLeft(self,event):
        
        self.event.widget["bg"] = "#000000"
        

    ##Funktion für Linksklick
    def bgChangeRight(self,event):
        
        self.event.widget["bg"] = "#333333"
        

    ##Funktion für Doppelklick
    def bgChangeDouble(self,event):
        
        self.event.widget["bg"] = "#FFFFFF"
        

    def run(self):

        self.main.mainloop()

###
Feld().run()

        
        

und die funktionen machen das richtige, ich habe die funktionalität ein bisschen erweitert bzw verändert, sodass ich die labels durch das anklicken verändere.
aber wenn ich auf ein label klicke erscheint immernoch: AttributeError: 'Feld' object has no attribute 'event'

und ich verstehe noch nicht genau, wo mein fehler liegt und welches self ich hinzufügen oder weglassen muss
BlackJack

@greedy: `self` ist ein Argument. Aus Sicht beim schreiben von Methoden eines wie jedes andere. Nur beim Aufruf geschiet ein wenig „Magie“, in der Form, dass man dafür keinen Wert übergeben muss, sondern dass als erstes Argument automatisch das Objekt übergeben wird, auf dem man die Methode aufgerufen hat.

Lass einfach alle ``self.`` weg bei denen Du nicht auf ein Attribut vom `Feld`-Objekt zugreifen willst. Das betrifft nicht nur die jetzigen Fehler, sondern zum Beispiel auch `self.label`. Warum bindest Du die einzelnen Label der Reihe nach an das `Feld`-Objekt?
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hi greedy

Kannst du einmal den folgenden Code ausprobieren:

Code: Alles auswählen

import tkinter

###

class Feld(object):

    def __init__(self):

        #Fenster
        self.main = tkinter.Tk()
       
        #Liste erstellen
        self.labels = list()

        #Labels für Liste erstellen
        for nr in range(0,16):
            self.label = tkinter.Label(
                self.main,
                text='Label %d' % nr,
                height=2,
                #width=5,
                fg='red',
                bg="black") #"white")
            self.label.pack(fill='x')

            #Mausereignisse binden
            self.label.bind('<Button-1>', self.bgChangeLeft)
            self.label.bind('<Button-3>', self.bgChangeRight)
            self.label.bind('<Double-Button-1>', self.bgChangeDouble)

            #Label in LabelsListe packen
            self.labels.append(self.label)
           

    ##Funktion für Linksklick
    def bgChangeLeft(self,event):
       
        event.widget["bg"] = "#000000"
       

    ##Funktion für Linksklick
    def bgChangeRight(self,event):
       
        event.widget["bg"] = "#333333"
       

    ##Funktion für Doppelklick
    def bgChangeDouble(self,event):
       
        event.widget["bg"] = "#FFFFFF"
       

    def run(self):

        self.main.mainloop()

###
Feld().run()
Für die Präsentation von Pythoncode kannst du den 'python' Tag benutzen.

Gruss wuf :wink:
Take it easy Mates!
greedy
User
Beiträge: 7
Registriert: Sonntag 20. November 2011, 12:36

ok habe meinen fehler jetzt auch gefunden und verstanden, danke Blackjack. Danke auch für den Code wuf, funktioniert einwandfrei! ;)
Damit ist mein Problem behoben ! ;)

Gruß Greedy
BlackJack

Falls bei den Mausklicks nicht wirklich mehr passieren soll, braucht man keine Methoden dafür, sondern kann das auch mit *einer* lokalen Funktion und `functools.partial()` erledigen:

Code: Alles auswählen

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import tkinter as tk
from functools import partial


class Array(object):
    
    def __init__(self, master):
        
        self.master = master
        
        def change_colour(colour, event):
            event.widget['bg'] = colour
        
        self.labels = list()
        for _ in range(0, 16):
            label = tk.Label(
                self.master,
                height=2,
                width=5,
                bg='white')
            label.pack()

            for event_name, colour in [
                ('<Button-1>', 'black'),
                ('<Button-3>', 'gray20'),
                ('<Double-Button>', 'white'),
            ]:
                label.bind(event_name, partial(change_colour, colour))

            self.labels.append(label)


def main():
    root = tk.Tk()
    array = Array(root)
    root.mainloop()


if __name__ == '__main__':
    main()
Ausserdem habe ich das erzeugen des `Tk`-Objekts aus der Klasse heraus gezogen, um sie flexibler zu machen.
Antworten