Entry einer Funktion mit einer Funktion ändern?

Fragen zu Tkinter.
Antworten
Dopefish
User
Beiträge: 6
Registriert: Mittwoch 5. Februar 2014, 11:31

Hallo Forengemeinde,

ich stolper gerade meine ersten Schritte mit Python und stehe vor meinem ersten kleinen Problem.
Ich habe ein root Fenster aus dem ich über einen Button ein neues mittels einer Funktion erzeuge.
Soweit kein Problem, nun sind in diesem Child Fenster ein paar tk.Entry Felder deren Werte ich mittels
weiteren Buttons ändern möchte. Nun habe ich das bislang über eine weitere Funktion versucht
aber das scheitert daran, daß meine Entry Felder dort nicht bekannt sind.

also in etwa so:

Code: Alles auswählen

def incdec (fenster,mode,field):
     fenster.field.insert(0,"test")

def eingabe():
      eingabe=tk.Tk()
      anzahl=tk.Entry(eingabe)
      inc=tk.Button(eingabe,text="inc", command=incdec("eingabe","inc","anzahl"))
Eigentlich soll der Button inc den Wert in anzahl inkrementieren aber soweit bin ich noch nicht
also habe ich das erst einmal plump mit einem String probiert.

Auch wenn ich fenster und field direkt anspreche, also:

Code: Alles auswählen

eingabe.anzahl.insert(0,"test")
klappt das nicht

Geht sowas vielleicht auch ohne eine zusätzliche Funktion?
In der Art:
command=anzahl.value++
BlackJack

@Dopefish: `Tk` ist der Datentyp für *das* Hauptfenster, davon darf es immer nur ein Exemplar zur gleichen Zeit geben. Zusätzliche Fenster muss man mit `Toplevel` erstellen.

Was Du hier möchtest ist objektorientierte Programmierung (OOP), also eine Klasse und Methoden die auf den gemeinsamen Zähler als Attribut zugreifen können. Bei GUIs kommt man ohne OOP in der Regel nicht all zu weit. Schau mal hier, das war gerade erst Thema: http://www.python-forum.de/viewtopic.ph ... 53#p251853
Dopefish
User
Beiträge: 6
Registriert: Mittwoch 5. Februar 2014, 11:31

Danke schaue ich mir an, okay als Klasse ergibt das auch eher Sinn.
Ich wurde schon stutzig als die Funktion ein neues Fenster generierte.
Hätte ich akzeptiert aber schön ist das eigentlich nicht.
Hab mich immer um OOP gedrückt... wird wohl mal Zeit.
Dopefish
User
Beiträge: 6
Registriert: Mittwoch 5. Februar 2014, 11:31

@BlackJack

Das hat mir schonmal sehr weitergeholfen.
Trotzdem eier ich natürlich noch reichlich rum.

Momentan versuche ich innerhalb einer Klasse Buttons generieren. Nun bleibt aber immer nur der letzte übrig.
Ich dachte eigentlich, daß ich eindeutige Instanzen erzeuge indem ich den Namen des Buttons übermittel
aber das ändert leider nichts.

Code: Alles auswählen


class Tastenfeld(tk.Toplevel):
    def __init__(self):
        tk.Toplevel.__init__(self)
        self.geometry("%dx%d+0+0" % (320,240))    

        self.buttonmaker(122,120,120,'button.gif',lambda:self.incdecs(1),'+','incb')
        self.buttonmaker(0,150,120,'button.gif',lambda:self.incdecs(-1),'-','decb') 

    def buttonmaker(self,posx,posy,width,image,cmd,text,bname):
            self.photo1 = tk.PhotoImage(file=image)        
            self.bname = tk.Button(self, text=text, image=self.photo1, command=cmd)
            self.bname.place(x=posx,y=posy,width=width)
            self.bname.config(bd=0)

BlackJack

@Dopefish: Das Argument `bname` wird nicht verwendet. `self.bname` erzeugt ein Attribut mit dem Namen `bname` und nicht mit einem Namen der in einer Zeichenkette steht, die an den lokalen Namen `bname` gebunden ist. Und wenn die Methode mehrfach aufgerufen wird, dann ist das Attribut `bname` natürlich nur an den letzten Wert gebunden weil eine Zuweisung an einen neuen Wert die Bindung an den alten aufhebt, oder anders gesagt ein Namen oder ein Attribut kann zu jedem gegebenen Zeitpunkt immer nur an *einen* Wert gebunden sein.

Fenstergrösse und Position sowie absolute Platzierung von Widgets in Containerwidgets sollte man nicht machen. Das sieht auf anderen Systemen mit anderen Einstellungen und anderer Anzeigehardware sonst eventuell komisch oder unschön aus, oder ist sogar unbedienbar wenn Widgets anfangen andere Widgets zu verdecken. Die Methoden `geometrie()` und insbesondere `place()` sollte man deshalb nach möglichkeit meiden.

`incdecs()` ist ja ein schäusslicher Name. Funktionen und Methodennamen sollten Tätigkeiten beschreiben, und zwar möglichst treffend die Tätigkeit die sie ausführen. Abkürzungen die nicht allgemein bekannt sind, sollte man vermeiden. Die Methode müsste also eher `add_value()` oder `add_to_counter()` oder so ähnlich heissen. Und `buttonmaker()` eher `make_button()` oder besser `create_button()`. Namen sollten auch nicht sinnlos durchnummeriert werden. Oder wie in diesem Fall „an”nummeriert. Einfach `photo` würde reichen.

Das `PhotoImage` muss man aufheben auf Python-Seite, denn sonst werden die Grafikdaten freigegeben, auch wenn die Tk-Seite die eigentlich noch braucht. Wenn man mehr als eines davon haben möchte, dann könnte man es zum Beispiel an das `Button`-Exemplar als Attribut binden auf dem es auch angezeigt wird. Und dann muss man natürlich auch alle `Button`\s aufheben. Zum Beispiel in einer Liste oder einem Wörterbuch. In letzterem könnte man den Namen `bname` dann als Schlüssel verwenden.

Dem Style Guide for Python Code nach gehst Du ein wenig sehr sparsam mit Leerzeichen um. Das kann man besser lesen wenn nicht alles so zusammenklebt.

Edit: Es bleibt übrigens nicht nur die letzte Schaltfläche übrig, es sind alle da — nur wird das Bild nur beim letzten angezeigt weil die anderen wie oben schon erwähnt zerstört werden solange man auf der Python-Seite keine Referenz auf das `PhotoImage`-Exemplar behält.
Dopefish
User
Beiträge: 6
Registriert: Mittwoch 5. Februar 2014, 11:31

Was die absolute Positionierung angeht ist das beabsichtigt, aus dem einfachen Grund weil dieses kleine Ding für einen 320x240 Mini Bildschirm und nur dafür geplant ist.
Andere Hardware spielt hier also überhaupt keine Rolle. In anderen Fällen würde ich auch nie absolut positionieren.
Ist jetzt aber auch nicht Teil meines Problems.

Ich habe gerade mal das Image entfernt dann funktioniert es auch.
Irgendwie auch klar, da ich das Bild immer gleich nenne.

Code: Alles auswählen

           
            self.img = tk.PhotoImage(file = image)
            self.images[bname] = self.img      
            self.bname = tk.Button(self, text = text, image = self.images[bname], command = cmd)
das wiederum gibt mir ein AttributeError: 'Tastenfeld' object has no attribute 'images' zurück
BlackJack

@Dopefish: Hast Du denn `self.images` auch vorher an ein `dict`-Exemplar gebunden? Attribute entstehen ja nicht einfach so auf magische Weise nur weil man darauf zugreift (es sei denn man programmiert das in der Klasse so :-)).
Dopefish
User
Beiträge: 6
Registriert: Mittwoch 5. Februar 2014, 11:31

Ach Mist, das kommt davon wenn man in PHP denkt.

Code: Alles auswählen

         
            self.img = tk.PhotoImage(file=image)
            self.images = {}
            self.images[bname] = self.img        
            self.bname = tk.Button(self, text=text, image=self.images[bname], command=cmd)
            self.bname.place(x = posx,y = posy,width = width)
            self.bname.config(bd=1)
Macht zwar keinen Fehler mehr aber das Bild ist trotzdem nur im letzten Button.

print(self.images)

{'incb': <tkinter.PhotoImage object at 0x0000000002AC7B70>}
{'decb': <tkinter.PhotoImage object at 0x0000000002AC7F28>}

Für die Buttons auch ein dict? Das kann ich irgendwie nicht nachvollziehen, das müsste dann wohl auch in der Klasse passieren aber wozu?

Code: Alles auswählen

        self.buttons = {}
        
        self.buttons['incb']=self.buttonmaker(122,120,120,'button.gif',lambda:self.incdecs(1),'+','incb')
        self.buttons['decb']=self.buttonmaker(0,150,120,'button.gif',lambda:self.incdecs(-1),'-','decb')
Ergibt für mich irgendwie keinen Sinn da die Buttons zu dem Zeitpunkt ihren Zweck schon voll und ganz erfüllen.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Jetzt überschreibst Du ja auch »images« jedes mal. Das »self.images = {}« muss in die »__init__«.
Dopefish
User
Beiträge: 6
Registriert: Mittwoch 5. Februar 2014, 11:31

Autsch, logisch.. das war natürlich das Problem.
Antworten