Eigenes Entry Feld mit Vervollständigung

Fragen zu Tkinter.
Antworten
seb-korn
User
Beiträge: 8
Registriert: Mittwoch 25. März 2020, 23:15

Hallo allerseits,

für ein Programm, das Rezepte verwaltet stehe ich aktuell vor einem Problem. Ich habe eine Möglichkeit gesucht, ein entry Feld zu erstellen, dass mir automatische Einträge aus einer Liste vorschlägt, während ich tippe. Ich habe ein solches Feld online gefunden, das funktioniert auch ganz prächtig. Jetzt möchte ich eine Funktion hinzufügen, bei der Rezepte automatisch aus Chefkoch ausgelesen werden. Die Zutaten sollen dann nochmal von einer Person überprüft werden, damit man am Ende nicht "zwiebel" und "zwiebeln" in der DB hat (zum Beispiel). Das heißt, das Entry Feld muss 1. weiterhin die Vorschläge aus der DB holen, so wie bisher und 2. wenn das Entry Feld aufgerufen wird muss der Wert, den ich von Chefkoch bekomme in dem Feld stehen.
Problem ist, dass ich bisher nur eins der beiden schaffe, aber nie beides gleichzeitig (manchmal auch gar keins :roll: )!
Die Zutat von Chefkoch steht in einer tk.StringVar, die ich dem Eintry Feld übergebe:

Code: Alles auswählen

 enterZutat = AE.ZutatEntry(self.fenster, textvariable = varZutat)
Ich weiß leider nicht mehr, wo ich das Entry Feld hergenommen habe, sonst hätte ich es natürlich verlinkt.
Aber so sah es aus als ich es mir rein kopiert habe:

Code: Alles auswählen

class ZutatEntry(Entry):
    def __init__(self, *args, **kwargs):

        Entry.__init__(self, *args, **kwargs)
        self.var = self["textvariable"]
        if self.var == '':
            self.var = self["textvariable"] = StringVar()


        self.var.trace('w', self.changed)
        self.bind("<Return>", self.selection)
        self.bind("<Tab>", self.selection)
        self.bind("<Up>", self.up)
        self.bind("<Down>", self.down)

        self.lb_up = False
Die Funktionen selection, changed, up und down kopiere ich euch nicht rein, die funktionieren auch noch.
Da self.var ja offensichtlich nicht mehr "" ist, habe ich folgendes hinzugefügt (letzte 2 Zeilen):

Code: Alles auswählen

class ZutatEntry(Entry):
    def __init__(self, *args, **kwargs):

        Entry.__init__(self, *args, **kwargs)
        self.var = self["textvariable"]
        if self.var == '':
            self.var = self["textvariable"] = StringVar()
        else:
            self.var = self["textvariable"] = StringVar(value=self.var)
Sieht auch einigermaßen vielversprechend aus, Problem ist nur, dass statt z.B. Zwiebeln in meinem Entry Feld so was wie "PY_VAR2", mit beliebiger Nummer steht. So weit ich mich einlesen konnte, müsste das daran liegen, dass self.var eben leider nicht die von mir angegebenen zwiebeln sind, sondern die interne Bezeichnung.

Also meine Frage: Gibt es einen Weg, wie ich das so ändern kann, dass da eine Zutat steht und keine interne tkinter Bezeichnung? Und wenn ja, wie sieht dieser Weg aus?

Bleibt gesund!
seb-korn
Viel zu lernen, ich noch habe.
Benutzeravatar
peterpy
User
Beiträge: 188
Registriert: Donnerstag 7. März 2013, 11:35

Hallo seb-korn,
schau mal hier: https://anzeljg.github.io/rin2/book2/24 ... index.html
Auf ein Entry greift an mit get() und set() zu
Die Klasse StringVar wird etwas merkwürdig instanziert.
Mein Beispiel:

Code: Alles auswählen

from tkinter import Tk, Entry, Label, StringVar
class Gui(object):    
    def __init__(self):        
        self.root = Tk()
        self.zahl = StringVar()
        self.text = StringVar()
        self.eingabe = StringVar()
        self.produkt = StringVar()        

    def felderstellen(self): 
        zaehlerfeld = Label(relief = "groove",
                            textvariable = self.zahl, width = 25,
                            bg = "cyan", justify = "center")
        zaehlerfeld.pack(padx = 3, pady = 3)

        beschrieb = "Bitte im grünen Feld\n eine Zahl eingeben\nz.B. -1.23"
        textfeld = Label(relief = "raised",
                        width = 25, height = 3, bg = "cyan", justify = "center",
                        text = beschrieb)
        textfeld.pack(padx = 3, pady = 3)

        eingabefeld = Entry(relief = "raised",
                            textvariable = self.eingabe, width = 25,
                            bg = "lightgreen", justify = "center")
        eingabefeld.pack(padx = 3, pady = 3)

        ausgabefeld = Entry(relief = "sunken",
                            textvariable = self.produkt, width = 25,
                            bg = "white", justify = "center")
        ausgabefeld.pack(padx = 3, pady = 3)
        
        takt = Taktgeber(self.root, self.zahl, self.eingabe, self.produkt)
        takt.zaehle()
        self.root.mainloop()

class Taktgeber(object):
    def __init__(self, root, zahl, eingabe, produkt):
        self.root = root
        self.zahl = zahl        
        self.eingabe = eingabe
        self.produkt = produkt
        self.zaehler = 0

    def zaehle(self):
        self.zaehler += 1
        if self.zaehler > 50:                     
            self.zaehler = -50
        self.root.after(500, self.zaehle)
        anzeige = "%s %s" %("Zählerstand", str(self.zaehler))
        self.zahl.set(anzeige)
        self.berechne()

    def berechne(self):
        eingabe = self.eingabe.get()               
        try:
            faktor1 = float(eingabe)
        except:
            faktor1 = 0
        faktor2 = self.zaehler
        produkt = faktor1 * faktor2
        self.produkt.set(produkt)

if __name__ == "__main__":
    start = Gui()
    start.felderstellen()
Gruss
Peter
seb-korn
User
Beiträge: 8
Registriert: Mittwoch 25. März 2020, 23:15

Hallo Peter,
danke für deine Antwort! Ich bin mir nicht sicher, in wie weit das etwas mit meinem Problem zu tun hat... Vielleicht habe ich es etwas ungünstig beschrieben, deshalb versuche ich es nochmal.

Ich habe tk.Entry angebpasst und nutze nun ein Eingabefeld, dass mir Vorschläge gibt. Gefunden habe ich das hier: http://code.activestate.com/recipes/578 ... inter-gui/. Das habe ich ein bisschen angepasst, aber sonst ist noch gleich (s. oben).

Die von mir verwendeten Eingabefelder müssten demnach ja alle Instanzen der Klasse AutocompleteEntry sein. Die Funktion funktioniert so weit auch ganz gut, so lange ich keinen Eintrag darin stehen haben möchte. Wenn ich die Abfrage von Chefkoch jetzt mache, habe ich ja das Problem, dass da schon stehen soll, welche Zutat im Rezept steht. Es kann aber sein, dass ich das von hand noch ändern möchte und dann brauch ich eben die vorschlagsfunktion (wenn ich mir nicht mehr sicher bin, ob zwiebel oder zwiebeln in der DB steht). Ich bin so weit gekommen, dass ich 2 Möglichkeiten gefunden habe:
1.: Ich habe das folgende unterhalb der if- einweisung im __init__ block kopiert:

Code: Alles auswählen

        else:
            self.var = StringVar()
            self.var.set(self["textvariable"])
Dann steht die richtige Zutat im Eingabefeld. Aber die trace Funktion funktioniert nicht mehr, beziehungsweise sie folgt nicht mehr dem passenden objekt....
2.: Wie oben beschrieben:: ich füge das folgende hinter die if anweisung:

Code: Alles auswählen

  else:
                 self.var = self["textvariable"] = StringVar(value=self.var)
Problem dann ist, dass die trace Funktion zwar Funktioniert, aber in den Entry Feldern leider nur "PY_VAR2" usw steht. Also nicht das richtige Rezept.

Wenn ich die beiden Lösungen kombiniere, z.B. so:

Code: Alles auswählen

        else:
            self.var = StringVar(value = self["textvariable"])
dann kommt das gleiche raus wie bei 1, die Zutaten stehen da, aber die trace Funktion will nicht.
Viel zu lernen, ich noch habe.
Sirius3
User
Beiträge: 18289
Registriert: Sonntag 21. Oktober 2012, 17:20

Der Fehler ist ja schon beim erzeugen Deines ZutatEntry, denn textvariable muß vom Typ StringVar sein.
`self.var = self["textvariable"] = StringVar(value=self.var)` ist die einzige Variante, die textvariable setzt. Aber der Inhalt von self.var vor dem Aufruf ist halt quatsch.
Wie sieht also der Aufruf von ZutatEntry aus?
seb-korn
User
Beiträge: 8
Registriert: Mittwoch 25. März 2020, 23:15

So?:

Code: Alles auswählen

        for i in self.z:
            varZutat = tk.StringVar(self.fenster)
            varZutat.set(i.name)
            enterZutat = AE.ZutatEntry(self.fenster, textvariable = varZutat)
            enterZutat.grid(row = j, column = 0)
Ich merke gerade, dass es nicht sinnvoll ist, den Code abgekürzt zu haben. So sieht der Aufruf aktuell aus und daraus entstehen die oben genannten Probleme. Aber m.E. müsste das doch stimmen, oder?

in self.z stehen Klassenobjekte, die u.a. das Attribut "name" haben.
Viel zu lernen, ich noch habe.
Benutzeravatar
__blackjack__
User
Beiträge: 14087
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@seb-korn: In `self.z` stehen ganze Zahlen. Denn sonst würde die Laufvariable nicht `i` heissen.

Du übergibst da ein `StringVar`-Objekt. Und Dein Code ersetzt das dann durch ein neues `StringVar`-Objekt und setzt als Inhalt die Zeichenkettenrepresentation des übergebenen `StringVar`-Objekts. Was "PY_VAR42" oder so ähnlich sein wird. Nicht sinnvoll.

Ich würde ja das `StringVar`-Objekt aus den Argumenten fischen, beziehungsweise da reinsetzen *bevor* die `__init__()` der Basisklasse aufgerufen wird. Dann muss man nicht versuchen das *danach* zu fixen und sich damit herumschlagen das Tcl „stringly typed“ ist und man da keine Python-Objekte mehr raus bekommt.

Edit: Der `z` ist auch besch…eiden, und `j` sollte wahrscheinlich mit `enumerate()` erzeugt werden, und `i` heissen.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
seb-korn
User
Beiträge: 8
Registriert: Mittwoch 25. März 2020, 23:15

@blackjack mir gingen irgendwann die abkürzungen für zutat aus, deswegen habe ich dann auch z zurück gegriffen. Aber ja, daran könnte man nochmal arbeiten. Die enumerate Funktion habe ich nur überflogen, wenn das Programm läuft mache ich mich dann nachträglich ans aufräumen :seufz:

Ich habe mal ein bisschen weiter rum probiert und scheinbar ist die Quelle des Problems doch eine andere. Wenn ich den folgenden Code ausführe:

Code: Alles auswählen

        for i in self.z:
            varZutat = tk.StringVar(self.fenster)
            print(i.name)
            varZutat.set(i.name)
            print(varZutat)
bekomme ich folgende Ausgabe:

Code: Alles auswählen

Weizenmehl Type 550
PY_VAR2
Hefe 
PY_VAR7
Wasser, lauwarmes
PY_VAR12
Öl 
PY_VAR17
Und um ehrlich zu sein habe ich keine Ahnung warum das so ist... offensichtlich schafft er es ja nicht den string in die stringvar zu setzen. i.name ist aber vom Typ "str", das habe ich grade auch nochmal geprüft.


Edit: Kann sein dass die Antwort von dir hier schon gegeben wurde, aber das sind für mich nur böhmische Dörfer...
__blackjack__ hat geschrieben: Mittwoch 8. April 2020, 12:52 Ich würde ja das `StringVar`-Objekt aus den Argumenten fischen, beziehungsweise da reinsetzen *bevor* die `__init__()` der Basisklasse aufgerufen wird. Dann muss man nicht versuchen das *danach* zu fixen und sich damit herumschlagen das Tcl „stringly typed“ ist und man da keine Python-Objekte mehr raus bekommt.
Viel zu lernen, ich noch habe.
Benutzeravatar
__blackjack__
User
Beiträge: 14087
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@seb-korn: Was heisst Dir gingen die Abkürzungen für zutat aus? Da man keine Abkürzungen verwendet, können einem die gar nicht ausgehen. Die sind von Anfang an aus.

Was hast Du den da erwartet? Überrascht Dich die Ausgabe von ``print(varZutat)``? So werden halt `StringVar`-Objekte ausgegeben. Das `StringVar`-Objekt ist ja nicht der Wert der *da drin* enthalten ist. Das sind unterschiedliche Dinge. An den Inhalt kommt man mit der `get()`-Methode: ``print(varZutat.get())``.

Mit ``entry["textvariable"]`` kommt man leider nicht mehr an das Python-Objekt das da mal übergeben wurde, sondern nur noch an die Zeichenkettenrepräsentation von dem Objekt. Deswegen mein Rat das *vorher*, bevor das von Tcl/Tk verurstet wurde aus den Argumenten zu fischen.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
seb-korn
User
Beiträge: 8
Registriert: Mittwoch 25. März 2020, 23:15

Okay ja, verstehe meinen Denkfehler. Und die get() Methode funktioniert nicht mehr in der Entry Klassen, richtig?

Gut, dann sehe ich jetzt das Problem. Aber die Lösung ist mir noch etwas schleierhaft. Dein Vorschlag heißt ich schreibe in die Entry Klassen eine Funktion(?) die mir i.name "aus dem Programm zieht". Steht die Funktion "einfach so" auch in der __init__ Methode? Oder noch vor der __init__ Methode als eigene Funktion in der Klasse und ich rufe die in meinem Programm auf und übergebe die Werte? Ich bin noch nicht ganz so lange dabei und habe das Gefühl, dass das nicht ganz trivial ist.
Viel zu lernen, ich noch habe.
Benutzeravatar
__blackjack__
User
Beiträge: 14087
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@seb-korn: Keine Funktion. Du bekommst in `ZutatEntry.__init__()` doch alles übergeben. Auch wenn da `textvariable` als Argument übergeben wird. Und da gleich in der ersten Zeile, noch bevor `Entry.__init__()` aufgerufen wird, ist alles noch ”Python-Land” mit Python-Objekten. An der Stelle nimmst Du Dir einfach den Wert aus den Schlüsselwortargumenten und bindest den an dein `ZutatEntry`-Objekt. Falls `textvariable` da nicht übergeben wurde, erzeugst Du ein `StringVar`-Objekt und steckst das auch in Schlüsselwortargumente die dann an `Entry.__init__()` weitergegeben werden.

Das ursprüngliche ``if`` nach dem Aufruf von `Entry.__init__()` kann man sich dann sparen, weil man da ja dann schon sicher ist, dass es auf jeden Fall ein `textvariable` gibt.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Sirius3
User
Beiträge: 18289
Registriert: Sonntag 21. Oktober 2012, 17:20

Einfach so:

Code: Alles auswählen

class ZutatEntry(Entry):
    def __init__(self, master=None, textvariable=None, **kwargs):
        Entry.__init__(self, master, textvariable=textvariable , **kwargs)
        self.var = textvariable 
        self.var.trace('w', self.changed)
        self.bind("<Return>", self.selection)
        self.bind("<Tab>", self.selection)
        self.bind("<Up>", self.up)
        self.bind("<Down>", self.down)
        self.lb_up = False
seb-korn
User
Beiträge: 8
Registriert: Mittwoch 25. März 2020, 23:15

okay, also es funktioniert und was noch schöner ist ich verstehe sogar was passiert und wieso es funktioniert! Danke euch allen für eure Hilfe!
Viel zu lernen, ich noch habe.
Antworten