Automatische erstellung von mehreren Buttons?

Fragen zu Tkinter.
Antworten
Arp
User
Beiträge: 65
Registriert: Dienstag 15. März 2011, 13:21

Hallo,

Ich möchte gern auf meinem TKinter Bildschirm mehrere Buttons erstellen. Deren Anzahl hängt davon ab, wieviele Elemente eine Liste hat, und die Buttons kriegen auch den Namen dieser Liste. Allerdings sollten das alles StringVar() sein, da ich die dynamisch ändern möchte.

Meine erste Idee war, das mit einer Liste zu machen die ich mit Buttons fülle.

z.b

Code: Alles auswählen

import Tkinter as Tk
liste1 = ['a','b','c']

root = Tk.Tk()
buttons = []
btext = []

for i in liste1:
   btext.append(Tk.StringVar())
   btext[-1].set(i)
   buttons.append(Tk.Button(root,text=btext[-1].get()))
   buttons[-1].pack()
Tk.mainloop()
An sich funktioniert das, es erstellt automatisch 3 buttons.
Wenn ich dann aber noch sowas wie btext[0].set('test') mache, ist die Idee das sich der Text auf dem ersten Button ändert. Das passiert jedoch nicht.

Könnt ihr mir sagen warum, oder einen besseren Ansatz liefern?
Thx.
BlackJack

@Arp: Warum sollte sich der Text auf dem Button denn auch ändern. Der Button bekommt von Dir eine ganz normale Zeichenkette und hat überhaupt keine Ahnung von dem `StringVar`-Exemplar. Du musst dem Button schon das `StringVar`-Exemplar über das dazu gehörende Schlüsselwort-Argument geben.
deets

Dein Ansatz ist an sich ganz gut. Du bist etwas umstaendlich mit dem ganzen negativen indizieren - warum bindest du die StringVar und den Button nicht erstmal an lokale Namen, und fuegst die dann am Ende hinzu?

Zu deinem Update-Problem: AFAIK kann ein Button-text nicht mit einem StringVar verbunden werden. Generell, aber erst recht nicht so wie du das tust, denn du holst ja den *normalen* string aus der Variablen um in an das text-attribut zu binden.

Ich vermute, du musst ein callback fuer die Variable anlegen, in welchem dann wiederum der Button re-konfiguriert wird.

http://effbot.org/tkinterbook/variable.htm

Ungefaehr so (ungetestet, kann sein, dass du im callback was uebergeben bekommst, dann muss der angepasst werden:

Code: Alles auswählen

for i in liste1:
     v = StringVar()
     v.set(i)
     b = Button(root, text=i)
     v.trace("w", lambda b=b, v=v: b.configure(text=v.get())
Sowas in der Art.
deets

BlackJack hat geschrieben:@Arp: Warum sollte sich der Text auf dem Button denn auch ändern. Der Button bekommt von Dir eine ganz normale Zeichenkette und hat überhaupt keine Ahnung von dem `StringVar`-Exemplar. Du musst dem Button schon das `StringVar`-Exemplar über das dazu gehörende Schlüsselwort-Argument geben.
Geht das? Ich meine die Doku so verstanden zu haben, dass das nur bei Labels geht, oder bei Checkboxen zB und ein paar anderen Widgets.
Arp
User
Beiträge: 65
Registriert: Dienstag 15. März 2011, 13:21

Ah, ich sehs. Aber wenn ich das zu text = btext[-1] umänder, lauten die buttonnamen PY_VAR0, PY_VAR1 und PY_VAR2.
Arp
User
Beiträge: 65
Registriert: Dienstag 15. März 2011, 13:21

Kommando zurück.

Das geht bei Buttons offenbar auch ohne Stringvar.

Wenn man irgendwo im code sowas schreibt wie button['text']='bla', dann ändert sich direkt der Text auf dem Button.
BlackJack

@deets: Buttons kennen neben `text` noch `textvariable`, damit geht das.
Arp
User
Beiträge: 65
Registriert: Dienstag 15. März 2011, 13:21

Hm, neues problem.

Die Buttons sollen natürlich auch eine Funktion ausführen. Z.b. den Text des Buttons in die Konsole schreiben:

Code: Alles auswählen


def printbuttonname(name):
   print name

for i in liste1:
   buttons.append(Tk.Button(root, text=i, command=lambda: printbuttonname(i)))
   buttons[-1].pack()

Das erstellt zwar wie oben die Buttons mit dem korrekten Namen, aber der übergibt scheinbar immer nur das letzte i. Egal welchen Button ich drücke, die Funktion schreibt immer nur den Namen des letzten Buttons in die Konsole.
deets

@Arp: schau dir mal mein Code-Beispiel genau an, dann siehst du, was ich anders gemacht habe beim lambda. Alternativ functools.partial benutzen.
Arp
User
Beiträge: 65
Registriert: Dienstag 15. März 2011, 13:21

sorry deets, den post muss ich übersehen haben.

Aber der bringt die buttons gar nicht auf die Fläche. Und, was genau machen die b=b und v=v? So gut ist jetzt meine lambda Kentniss nicht :)

Aber das Stringvar problem hat sich ja eh erledigt. Nun zickt ja command rum.
deets

Die Argumente machen genau das, was du willst: den Wert i zum Zeitpunkt der Erstellung des Callback zu binden, statt aus dem Closure der Funktion/Methode drum rum, die dann den letzten Wert enthaelt. Das loest dein Problem.
Arp
User
Beiträge: 65
Registriert: Dienstag 15. März 2011, 13:21

Hmm, also wenn ich das richtig verstehe, sollte ich das dann so schreiben:

Code: Alles auswählen

button.append(Tk.Button(root,text=i,command=lambda name=i: printbuttonnam(name))
Oder hab ich das falsch verstanden? Denn das zeigt auch immer nur den letzten an.
deets

Bezweifele ich.

Code: Alles auswählen

a, b = [], []

def p(arg):
      print arg

for i in xrange(10):
    a.append(lambda : p(i))
    b.append(lambda i=i: p(i))

for cb in (a + b):
     cb()
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hi Arp

Hier auch noch was zum herumspielen:

Code: Alles auswählen

from functools import partial
import Tkinter as tk

def button_callback(button_text_var):
    print button_text_var.get()
    
    if button_text_var.get() == 'c':
        buttons[1].set('Button-2')

button_names = ['a','b','c']
buttons = list()

root = tk.Tk()

for button_name in button_names:
    button_text_var = tk.StringVar()
    button_text_var.set(button_name)
    tk.Button(root, textvariable=button_text_var, 
    command=partial(button_callback, button_text_var)).pack(fill='x')
    buttons.append(button_text_var)

buttons[0].set('Button-1')
    
root.mainloop()
Gruß wuf :wink:
Take it easy Mates!
deets

@wuf

Die Verwendung von StringVar an dieser Stelle ist voellig sinnfrei. Stattdessen kannst du gleich button_name uebergeben. Der Sinn von den *Var-Objekten liegt in ihrer Eigenschaft als Observable. Den nutzt du nicht aus.
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

OK deets

Wie würdest du mein Code-Snippet abändern, dass es ohne StringVar das genau gleiche macht. Die Bezeichnung Observable ist neu für mich. Verstehe ich nicht. Danke für deine Bemühung.

Gruß wuf :wink:
Take it easy Mates!
deets

Code: Alles auswählen


from functools import partial
import Tkinter as tk

def button_callback(button_text):
    if button_text == 'c':
        buttons[0]["text"] = 'Button-2'

button_names = ['a','b','c']
buttons = list()

root = tk.Tk()

for button_name in button_names:
    button = tk.Button(root, text=button_name,
              command=partial(button_callback, button_name))
    button.pack(fill='x')
    buttons.append(button)

buttons[0]['text'] = 'Button-1'
    
root.mainloop()
Ein Observable ist ein Wert, der Nachrichten verschickt, wenn er veraendert wird. Das ist genau das, was eine StringVar und Co. sind.

Der Punkt den ich machen wollte: fuer deinen illustrierten Zweck kommt man ohne extra Komplexitaet aus. Aber natuerlich sind observables sehr hilfreich: wenn man zb ein Datenmodell hat, das sich durch irgendwas anderes aendert, und dadurch dann *automatisch* die GUI upgedated wird.
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

@deets

Super! Besten Dank für deine hilfreiche Antwort mit Erklärung. Man hat eben nie ausgelernt. Dein modifizierte Code-Snippet verhält sich genau gleich wie mein aufgeblähtes Snippet.

Gruß :wink: Noch eine gute Nacht.
Take it easy Mates!
Arp
User
Beiträge: 65
Registriert: Dienstag 15. März 2011, 13:21

@deets

Ich hab meinen Fehler gefunden. Ich hatte bei mir in der callback funktion blöderweise die laufvariable, und nicht die übergebene ausgegeben, daher hab ich natürlich immer nur den letzten Wert ausgegeben. Funktioniert jetzt. Danke!
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hi deets

Habe doch noch einen kleinen Unterschied zwischen meinem und dem modifizierten Snippet festgestellt. Bei meine Snippet ist auch der Name eines modifizierten Button verfügbar. Aber aufbauend auf dem modifizierten Snippet ist dies auch ohne den Einsatz von StringVar() realisierbar. Hier noch eine Anpassung des modifizierten Snippet um dieses Verhalten zu erzielen:

Code: Alles auswählen

from functools import partial
import Tkinter as tk

def button_callback(button_obj):
    if button_obj['text'] == 'c':
        buttons[0]["text"] = 'Button-2'
    print button_obj['text']
    
button_names = ['a','b','c']
buttons = list()

root = tk.Tk()

for button_name in button_names:
    button = tk.Button(root, text=button_name)
    button.pack(fill='x')
    button.config(command=partial(button_callback, button))
    buttons.append(button)

buttons[0]['text'] = 'Button-1'
   
root.mainloop()
Gruß wuf :wink:
Take it easy Mates!
Antworten