Widgets dynamisch erzeugen und auslesen/manipulieren

Fragen zu Tkinter.
Antworten
Terahnee
User
Beiträge: 13
Registriert: Dienstag 8. August 2017, 13:48

Hallo zusammen,

ich beschäftige mich derzeit mit der Frage wie man verschiedene Widgets, hauptsächlich Entry und Label, dynamisch erzeugen und darauf zugreifen kann. Bislang fand ich immer wieder den Hinweis dass man die Widgets dafür in Listen oder Dictionaries speichert. Das will mir nicht so richtig gelingen.

Mein Ziel:

Eine Einkaufsliste für verschiedene Standorte (Märkte) in folgender tabellarischer Anordnung:

Code: Alles auswählen

		Markt1	Markt2	Markt3	...
Artikel1	Entry	Entry	Entry	Entry
Artikel2	Entry	Entry	Entry	...
Artikel3	...		...		...		...
...
Mein Ansatz:

Code: Alles auswählen

def baue_artikel_dictionary():
            
# Hier erfolgt eine Datenbankabfrage die mir alle vorhandenen Artikel auflistet. Ergebnis steht in artikel.
artikel_eingabefelder = {}
i = 2
for row in artikel.fetchall():
    artikel_eingabefelder[row[0]] = tk.Entry(self).grid(row=i, column=1)
    i = i + 1
return artikel_eingabefelder
Im oberen Teil erzeuge ich ein Dictionary mit folgender Zuordnung:
Artikel1, Entry
Artikel2, Entry
usw.

Das fertige Dictionary gebe ich wieder zurück.

Code: Alles auswählen

MAERKTE = ["Laar", "Beeck", "Buchholz"]

artikel_dictionary = {}
                for markt in MAERKTE:
                    artikel_dictionary[markt] = baue_artikel_dictionary()
print("Finales Dictionary: " + str(artikel_dictionary))
Im unteren Teil wird ein weiteres Dictionary gebaut in dem die Zuordnung folgendermaßen ist:
Markt1, DictionaryMitObigerZuordnung
Markt2, DictionaryMitObigerZuordnung
usw.

Die Print-Anweisung in der letzten Zeile gibt übrigens das hier aus:

Code: Alles auswählen

Finales Dictionary: {'Laar': {'Melone': None, 'Banane': None, 'Chicoree': None}, 'Beeck': {'Melone': None, 'Banane': None, 'Chicoree': None}, 'Buchholz': {'Melone': None, 'Banane': None, 'Chicoree': None}}
Genau so habe ich es mir vorgestellt. Es verwundert mich allerdings, dass dort wo ich die Entry-Objekte erwarte, immer nur "None" steht.
Zugreifen auf die Entry-Widgets wollte ich nun über so etwas in der Art:

Code: Alles auswählen

print("Einkaufsmenge = " + str(artikel_dictionary["Laar"]["Melone"].get()))
Bekomme aber folgende Fehlermeldung:

Code: Alles auswählen

print("Einkaufsmenge = " + str(artikel_dictionary["Laar"]["Melone"].get()))
AttributeError: 'NoneType' object has no attribute 'get'
Meine Frage:

Ist meine Vorgehensweise grob richtig, oder macht man so etwas komplett anders?

Vielen Dank im Voraus!
Sirius3
User
Beiträge: 17703
Registriert: Sonntag 21. Oktober 2012, 17:20

@Terahnee: das hat nichts mit Deiner gewählten Struktur zu tun.

In der Zeile 7 erzeugst Du erst ein tk.Entry-Objekt und rufst darauf die grid-Methode auf. grid positioniert ein Entry und liefert nichts (also None) zurück. Diesen Rückgabewert weist Du dann einem Schlüssel in Deinem Wörterbuch zu.

Statt den Index i selbst zu zählen, benutze »enumerate«:

Code: Alles auswählen

artikel_eingabefelder = {}
for i, row in enumerate(artikel.fetchall(), start=2):
    entry = tk.Entry(self)
    entry.grid(row=i, column=1)
    artikel_eingabefelder[row[0]] = entry
Terahnee
User
Beiträge: 13
Registriert: Dienstag 8. August 2017, 13:48

Ah, okay! Das mit .grid() wusste ich nicht - wollte an dieser Stelle nur eine Zeile sparen.
Ja, die Struktur die ich oben skizzierte hatte mit meinen Versuchen noch nichts zu tun. Ich wollte erstmal nur probieren. Das hat mich im Endeffekt mehr Zeit gekostet als es von Anfang an richtig zu machen. :D

Ich habe es jetzt so gelöst:

Code: Alles auswählen

                # Tabelle erzeugen
                artikel_eingabefelder = {}
                tabelle = {}
                for i, row in enumerate(hole_artikel().fetchall(), start=3):
                    label_artikel = tk.Label(self, text=row[0], width=20)
                    label_artikel.grid(row=i, column=0)
                for i, markt in enumerate(heutige_maerkte(), start=3):
                    for k, row in enumerate(hole_artikel().fetchall(), start=1):
                        entry = tk.Entry(self)
                        entry.grid(row=i, column=k)
                        artikel_eingabefelder[row[0]] = entry
                    tabelle[markt] = artikel_eingabefelder
Das ganze erzeugt diese Ausgabe (Bild).
Ich sehe gerade, dass das Bild noch einen horizontalen Separator und die Überschriften beinhaltet. Das habe ich nicht in den Codeblock gesetzt.
Auslesen kann ich jetzt jedes Entry-Feld mit dem "tabelle"-Dictionary wie bereits im ersten Post vermutet.
Danke für den Hinweis mit enumerate(). Habe mir das mal näher angeguckt.

Vielen Dank für deine Hilfe!
Terahnee
User
Beiträge: 13
Registriert: Dienstag 8. August 2017, 13:48

Tja, zu früh gefreut. Ich hatte es leider nur mit einem Entry-Feld getestet. Habe jetzt mal alle gleichzeitig auslesen wollen und dabei festgestellt, dass immer nur die unteren drei ausgelesen werden.

Hm, in meinem "tabelle"-Dictionary sind auch immer nur die selben drei Entry-Felder drin. Zu spät für heute.
Sirius3
User
Beiträge: 17703
Registriert: Sonntag 21. Oktober 2012, 17:20

@Terahnee: ohne die Funktionen "hole_artikel" und "hole_maerkte" zu kennen, kann man dazu nicht viel sagen. Für jede Zeile eine neue Datenbankabfrage zu machen, nur um die Labels zu bekommen, die schon in der ersten Schleife gelesen wurden, ist ein bißchen umständlich. Dann erzeugst Du **einmal** ein artikel_eingabefelder-Wörterbuch, das Du immer wieder mit Entries füllst, und dabei die alten überschreibst. Das ist sicherlich nicht gewollt. Variablen sollte man immer erst dann initialisieren, wenn man sie auch benutzt, dann können solche Fehler nicht auftreten. Bei der Namensgebung hat "deutsch" das Problem, dass die Pluralform manchmal identisch zur Singularform ist, hier weiß man nicht ob es sich um einen Artikel oder mehrere Artikel handelt. ich bevorzuge englische Namen (products), dann beißt es sich auch nicht so mit den englischen Schlüsselwörtern und Standardfunktionen:

Code: Alles auswählen

                # Tabelle erzeugen
                artikel= [row[0] for row in hole_artikel()]
                for i, einzelartikel in enumerate(artikel, start=3):
                    tk.Label(self, text=einzelartikel , width=20).grid(row=i, column=0)

                tabelle = {}
                for i, markt in enumerate(heutige_maerkte(), start=3):
                    artikel_eingabefelder = {}
                    for k, einzelartikel in enumerate(artikel, start=1):
                        entry = tk.Entry(self)
                        entry.grid(row=i, column=k)
                        artikel_eingabefelder[einzelartikel] = entry
                    tabelle[markt] = artikel_eingabefelder
Terahnee
User
Beiträge: 13
Registriert: Dienstag 8. August 2017, 13:48

Sirius3 hat geschrieben:@Terahnee: ohne die Funktionen "hole_artikel" und "hole_maerkte" zu kennen, kann man dazu nicht viel sagen.
Stimmt, das hatte ich vergessen zu erklären. "hole_artikel" ist nur die Datenbankabfrage der Artikel und "heutige_maerkte" wählt abhängig vom Wochentag einen anderen Satz von Märkten.
Sirius3 hat geschrieben: Für jede Zeile eine neue Datenbankabfrage zu machen, nur um die Labels zu bekommen, die schon in der ersten Schleife gelesen wurden, ist ein bißchen umständlich.
Ja, das macht natürlich Sinn! Darauf achte ich normalerweise, aber habe es doch des Öfteren im restlichen Code gefunden. Habe alle Stellen bereinigt.
Sirius3 hat geschrieben: Dann erzeugst Du **einmal** ein artikel_eingabefelder-Wörterbuch, das Du immer wieder mit Entries füllst, und dabei die alten überschreibst. Das ist sicherlich nicht gewollt. Variablen sollte man immer erst dann initialisieren, wenn man sie auch benutzt, dann können solche Fehler nicht auftreten.
Nein, absolut nicht gewollt. :D Danke für die Korrektur. Das mit den Variablen schreibe ich mir hinter die Ohren. Restcode ist dahingehend auch bereinigt.
Sirius3 hat geschrieben: Bei der Namensgebung hat "deutsch" das Problem, dass die Pluralform manchmal identisch zur Singularform ist, hier weiß man nicht ob es sich um einen Artikel oder mehrere Artikel handelt. ich bevorzuge englische Namen (products), dann beißt es sich auch nicht so mit den englischen Schlüsselwörtern und Standardfunktionen:
Tja, da könnte man fast eine Grundsatzdiskussion starten. Ich habe bislang immer deutsch benutzt gerade damit es sich beißt und man so eine klare Trennung hat, aber ich gebe dir gerade mit Blick auf die Pluralform recht. Ob ich das im aktuellen "Projekt" alles umändere, muss ich mir noch schwer überlegen. Aber wenn ich etwas Neues anfange, werde ich daran denken.

Was mir allerdings völlig neu ist und was ich mit oberflächiger Google-Suche nicht gefunden habe, ist die Schreibweise in diesem Teil:

Code: Alles auswählen

artikel= [row[0] for row in hole_artikel()]
Was da passiert erkenne ich, aber dass man es auch so schreiben kann, wusste ich nicht. Gibt es irgendwo eine Sammlung solcher "Tricks"?

Ich habe schlussendlich noch eine kleine Sache verändert, damit die Logik wieder stimmt. Die Entry-Felder wurden pro Zeile horizontal verteilt. Das passte dann natürlich nicht mit der Zugriffsweise zusammen.

Code: Alles auswählen

# Tabelle erzeugen
                artikel = [row[0] for row in hole_artikel()]
                for i, einzelartikel in enumerate(artikel, start=3):
                    tk.Label(self, text=einzelartikel, width=20).grid(row=i, column=0)

                tabelle = {}
                for i, markt in enumerate(heutige_maerkte(), start=1):
                    artikel_eingabefelder = {}
                    for k, einzelartikel in enumerate(artikel, start=3):
                        entry = tk.Entry(self)
                        entry.grid(row=k, column=i)
                        artikel_eingabefelder[einzelartikel] = entry
                    tabelle[markt] = artikel_eingabefelder
Es wurden nur die Zähler k und i in Zeile 11 und die jeweiligen Startwerte vertauscht.

Vielen Dank noch einmal für deine Mühe, Sirius3.
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

Terahnee hat geschrieben:Was da passiert erkenne ich, aber dass man es auch so schreiben kann, wusste ich nicht. Gibt es irgendwo eine Sammlung solcher "Tricks"?
Das ist weniger ein Trick als ein normales Konstrukt und nennt sich 'List-Comprehension'. Gibt es auch für Dictionaries und Sets.
Antworten