Ansprechen von widgets, die über eine loop generiert wurden

Fragen zu Tkinter.
Antworten
joe2000
User
Beiträge: 7
Registriert: Montag 24. Oktober 2022, 15:35

Hallo zusammen,

ich habe mir zu Übungszwecken folgende Aufgabe gestellt:
4 Checkbuttons sollen mit einem Eingabefeld gekoppelt werden, oder anders gesagt, die Checkbuttons und das Eingabefeld beeinflussen sich gegenseitig. Was ich hinbekommen habe, ist die eine Richtung: Werden die Checkbuttons in der Gui gesetzt, dann wird in das Eingabefeld das entsprechende Muster übertragen (0 bedeutet: Checkbutton nicht gesetzt / 1 bedeutet: Checkbutton gesetzt). Beispiel: Wird der erste und der letzte Checkbutton gesetzt, wird in das Eingabefeld eine 1001 übertragen.
Die andere Richtung bekomme ich nicht hin, wenn zum Beispiel in das Eingabefeld 1100 eingegeben wird, dann sollen die ersten beiden Checkbuttons gesetzt werden. Das gleiche Problem habe ich mit der Änderung des Textes der Checkbuttons (die Texte sollen nach Betätigung des Buttons geändert werden). Mir ist also nicht klar, wie man loop-generierte widgets ansprechen kann. Ich habe es mit Listen versucht, in die der Name des widgets geschrieben wird, aber auch hier mir fehlt mir wohl noch ein Schritt zum Erfolg.

Für einen einzelner Checkbutton (der aufgrund seines eindeutigen Namens direkt angesprochen werden kann), sind beide Richtungen kein Problem.

Unten stehend der Code dazu, für einen Tipp wäre ich dankbar (Fehleingaben über try/except noch nicht abgefangen). Und wenn Euch sonst noch was auffällt, was an dem Code nicht so dolle ist, über entsprechende Hinweise würde ich freuen. Ich stehe bzgl. Python noch am Anfang.

Schon mal Danke…

Code: Alles auswählen

import tkinter as tk
from functools import partial

NUMBER_CHECKBOX=4

class Gui(tk.Frame):
    def __init__(self, master):
        tk.Frame.__init__(self, master)
        #entry
        self.entry_var= tk.StringVar()
        self.entry_var.trace('w', self.entry_change)
        self.entry = tk.Entry(self, textvariable=self.entry_var)
        self.entry.grid(sticky=tk.W, row=0, column=0)
        #label
        self.label = tk.Label(self)
        self.label.grid(sticky=tk.W, row=1, column=0)
        #checkboxen loopgeneriert
        self.list_checkbox_value=[]
        self.list_checkbox_text=[]
        for idx in range(NUMBER_CHECKBOX):
            self.checkbox_text_var = tk.StringVar()
            self.checkbox_text_var='Check_'+str(idx)
            self.list_checkbox_text.append(self.checkbox_text_var)
            self.checkbox_value_var = tk.IntVar()
            self.list_checkbox_value.append('0')
            self.checkbox = tk.Checkbutton(self,text=self.checkbox_text_var, variable=self.checkbox_value_var)
            self.checkbox.config(command=partial(self.checkbox_change, self.checkbox_text_var, self.checkbox_value_var))
            self.checkbox.grid(sticky=tk.W, row=2+idx, column=0)
        #button
        self.button = tk.Button(self,text='Ändere Text Checkboxen', command=self.checkbox_text_change)
        self.button.grid(sticky=tk.W, row=7, column=0)
        #einzelcheckbox 
        self.checkbox_single_value = tk.IntVar()
        self.checkbox_single_text = tk.StringVar()
        self.checkbox_single_text.set('Eingabefeld befüllt')
        self.checkbox_single = tk.Checkbutton(self,textvariable=self.checkbox_single_text, variable=self.checkbox_single_value, command=self.checkbox_single_change)
        self.checkbox_single.grid(sticky=tk.W, row=8, column=0)

    def entry_change(self,a, *args):
        self.label['text']=self.entry_var.get()
        if self.entry_var.get()=='':
            self.checkbox_single_value.set(0)
        else:
            self.checkbox_single_value.set(1)
        #self.checkbox_value.set(1)# ->So funkiert es nicht

    def checkbox_change(self, checkbox_text, checkbox_value):
        idx=int(self.list_checkbox_text.index(checkbox_text))
        self.list_checkbox_value[idx]=str(checkbox_value.get())
        anzeige_label=("".join(self.list_checkbox_value))
        if anzeige_label=='0000':
            anzeige_label='' 
        self.label['text']=anzeige_label
        self.entry_var.set(anzeige_label)
    
    def checkbox_single_change(self,*args):
        if self.checkbox_single_value.get()==0:
           self.entry_var.set('')

    def checkbox_text_change(self):
        self.checkbox_single_text.set ('neue Bezeichnung')
        #self.checkbox_text_var.set ('Check_loop')->So funkiert es nicht

def main():
    root = tk.Tk()
    root.title("test_2")
    root.geometry('150x200')
    gui = Gui(root)
    gui.pack()
    root.mainloop()


if __name__ == "__main__":
    main()
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

Wenn man innerhalb einer for-Schleife Attribute setzt, dann macht man irgendwas falsch, denn diese Attribute werden ja im nächsten for-Schleifenlauf wieder überschrieben.
`checkbox_text_var` ist erst ein StringVar-Objekt dann ein String. In die Listen willst Du keine Strings stecken, sondern die Var-Objekte.
Var-Objekte sollten als erstes Argument auch den `master` bekommen.
joe2000
User
Beiträge: 7
Registriert: Montag 24. Oktober 2022, 15:35

@Sirius3: danke für die rasche Antwort, das Problem mit dem Überschreiben der Attribute in der Schleife habe ich verstanden. Beim Rest fehlen mir wohl noch ein paar Grundlagen, ich denke ich muss mich der Sache schrittweise nähern:
Ich habe nun die Zeile self.checkbox_text_var='Check_'+str(idx) entfernt und in der Liste steht dann: [<tkinter.StringVar object at 0x0000022D17F8B9D0>, <tkinter.StringVar object at 0x0000022D1840FD00>, <tkinter.StringVar object at 0x0000022D1840FE80>, <tkinter.StringVar object at 0x0000022D18468040>]. Das hast Du gemeint, oder?
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

Das geht in die richtige Richtung.
joe2000
User
Beiträge: 7
Registriert: Montag 24. Oktober 2022, 15:35

Nach ewigem rumprobieren und recherchieren muss ich mir leider eingestehen, dass ich nicht weiterkomme. Geht das überhaupt so wie ich das möchte? Es ist mir klar, dass ich den Mustercode hier nicht geliefert bekomme, das ist ja auch nicht Sinn der Sache. Aber mir ist partout nicht klar, wie mit den Informationen aus der Liste die Checkbuttons ansprechen kann...
__deets__
User
Beiträge: 14536
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ja, gehen tut das. Und die Checkbuttons sprichst du an, indem du deren Variable veraenderst.

Code: Alles auswählen

import tkinter as tk


class App:

    def __init__(self, root):
        self._checkbox_var = tk.IntVar()
        tk.Checkbutton(root, text="Influence me", variable=self._checkbox_var).pack()
        tk.Button(root, text="Set to one", command=lambda: self._set_checkbox(1)).pack()
        tk.Button(root, text="Set to zero", command=lambda: self._set_checkbox(0)).pack()

    def _set_checkbox(self, number):
        self._checkbox_var.set(number)

def main():
    root = tk.Tk()
    app = App(root)
    root.mainloop()

if __name__ == '__main__':
    main()
joe2000
User
Beiträge: 7
Registriert: Montag 24. Oktober 2022, 15:35

@deets: Danke für Deine Antwort. Leider löst das meine Problem nicht, dies ist nämlich die Adressierung eines einzelnen Checkbuttons der über eine Schleife generiert wurde. Wenn ich in Deinen Code eine Schleife implementiere (siehe unten), dann bekomme ich 4 Checkbuttons die nun nicht mehr singulär gesetzt werden können. Und wenn der Button betätigt wird, werden auch alle angesprochen. Mein Klemmer ist, dass ich schleifengenerierte Checkbuttons einzeln Ansprechen möchte. Ich glaube das muss ich irgendwie über Listen machen, aber genau da komme ich nicht weiter...

Code: Alles auswählen

import tkinter as tk


class App:

    def __init__(self, root):
        self._checkbox_var = tk.IntVar()
        for idx in range(4):
            tk.Checkbutton(root, text="Influence me", variable=self._checkbox_var).pack()
        
        tk.Button(root, text="Set to one", command=lambda: self._set_checkbox(1)).pack()
        tk.Button(root, text="Set to zero", command=lambda: self._set_checkbox(0)).pack()

    def _set_checkbox(self, number):
        self._checkbox_var.set(number)

def main():
    root = tk.Tk()
    app = App(root)
    root.mainloop()

if __name__ == '__main__':
    main()
__deets__
User
Beiträge: 14536
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du musst schon eine Variable pro Checkbox machen. Darum wurde hier ja auch von einer Liste gesprochen.
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

Du brauchst für jede Checkbox ein eigenes IntVar, und das muß in eine Liste.

Code: Alles auswählen

import tkinter as tk

NUMBER_CHECKBOX=4

class Gui(tk.Frame):
    def __init__(self, master):
        tk.Frame.__init__(self, master)
        self.label = tk.Label(self)
        self.label.grid(sticky=tk.W, row=1, column=0)
        self.checkbox_vars = []
        for idx in range(NUMBER_CHECKBOX):
            var = tk.IntVar(self, 0)
            var.trace('w', self.var_changed)
            checkbox = tk.Checkbutton(self, text=f"C{idx}", variable=var)
            checkbox.grid(sticky=tk.W, row=2+idx, column=0)
            self.checkbox_vars.append(var)

    def var_changed(self, *args):
        text = [f"{var.get()}"
            for var in self.checkbox_vars
        ]
        self.label['text'] = text

def main():
    root = tk.Tk()
    gui = Gui(root)
    gui.pack()
    root.mainloop()

if __name__ == "__main__":
    main()
joe2000
User
Beiträge: 7
Registriert: Montag 24. Oktober 2022, 15:35

@Sirius3: Weltklasse, genau der Kniff hat mir gefehlt, vielen Dank! Das war Deine gute Tat des Tages, somit bin ich nun auch in der Lage, jeden Checkbutton einzeln anzusprechen (wie im Beispiel unten über einen Button)

Was allerdings noch funktioniert, ist das ändern des Beschriftung ausgewählter Checkbuttons, da fehlt mir der Befehl an der Stelle an der im code gerade 'pass' steht.
Das ist zwar nicht ganz so wichtig, es würde mich allerdings schon interessieren wie das geht.

Code: Alles auswählen

import tkinter as tk

NUMBER_CHECKBOX=4

class Gui(tk.Frame):
    def __init__(self, master):
        tk.Frame.__init__(self, master)
        self.label = tk.Label(self)
        self.label.grid(sticky=tk.W, row=1, column=0)
        self.checkbox_vars = []
        self.checkbox_text=[]
        for idx in range(NUMBER_CHECKBOX):
            var = tk.IntVar(self, 0)
            var.trace('w', self.var_changed)
            txt_var = tk.StringVar
            txt_var=f"C{idx}"
            checkbox = tk.Checkbutton(self, text=txt_var, variable=var)
            checkbox.grid(sticky=tk.W, row=2+idx, column=0)
            self.checkbox_vars.append(var)
            self.checkbox_text.append(txt_var)

        #button
        self.button = tk.Button(self,text='Ändere Wert Checkbox 2', command=self.checkbox_val_change)
        self.button.grid(sticky=tk.W, row=7, column=0)

    def var_changed(self, *args):
        text = [f"{var.get()}"
            for var in self.checkbox_vars
        ]
        self.label['text'] = text

    def checkbox_val_change(self, *args):
        idx=0
        for var in self.checkbox_vars:
            if idx==2:
                f"{var.set('1')}"
            idx+=1

        idx=0
        for txt in self.checkbox_text:
            if idx==2:
                pass
            idx+=1

def main():
    root = tk.Tk()
    gui = Gui(root)
    gui.pack()
    root.mainloop()

if __name__ == "__main__":
    main()
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

Jetzt hast Du ja wieder den selben Fehler mit StringVar drin, wie ganz am Anfang, aber das Prinzip ist das selbe wie mit IntVar. Das solltest Du übernehmen können.
Antworten