Auswahlmenüs mit verschiedenem Inhalt füllen

Fragen zu Tkinter.
Antworten
sumsum
User
Beiträge: 11
Registriert: Donnerstag 2. Dezember 2010, 13:00

Hallo zusammen,
ich habe mir etwas zusammengebastelt, das 4 verschiedene Auswahlmenüs anbietet. Dabei werden die Inhalte aus den Auswahlmenüs aus 4 verschiedenen Textdateien eingelesen. Falls, der gewünschte Inhalt nicht drin ist, kann man für jedes Menü noch separat Text eingeben und das mit einem Button bestätigen, so dass es auch aufgenommen werden soll. Es klappt aber nicht:-( Bei jedem Start wird die Vorbelegung der Eingabefelder in die Datei neu eingeschrieben, obwohl nicht mit "Add" bestätigt wurde. Und wenn ich etwas in die Eingabefelder schreibe und dies mit "Add" in die Auswahlmenüs aufnehmen will, passiert nichts :-((

Besten Dank schonmal!

Code: Alles auswählen

from __future__ import with_statement
from Tkinter import *
from Tkinter import _setit
from pylab import grid, title

import tkMessageBox

ProgramLabel = " 123"
           
class App (Frame):
    def __init__(self, master=None):
        Frame.__init__(self, master)
        self.grid()

        self.StartFrame()
        self.List =["A", "B", "C", "D"]
        self.s = 0
        self.i = 0
        #for "\n".join(List)
        for self.i in range(4):
            #~ for self.s in self.List:
                #~ print self.s,
            self.setupMenue(self.i)
            


        self.Quit = Button(self.FirstFrame, text="QUIT", bg="red", command=self.FirstFrame.quit)
        self.Quit.grid(row=5, column=6)
        
    def StartFrame (self):
        # First frame for buttons
        self.FirstFrame = Frame(root)
        self.FirstFrame.grid()
        
    def setupMenue(self,Li):
        self.var=StringVar()
        self.var  = self.readFile(Li)

        self.label_add = Label(self.FirstFrame, text="new %s"%self.List[Li])
        self.label_add.grid(row=Li, column=3)
        self.entryString = StringVar()       
        self.entry_add = Entry(self.FirstFrame,textvariable=self.entryString)
        self.entry_add.grid(row=Li, column=4)

        self.entryString.set(self.List[Li])
        self.fetchEntry = self.entryString.get()
        self.entry_add.focus_set()
        self.value = self.entry_add.get()
        print self.value

        self.button_add = Button(self.FirstFrame, text="Add", command=self.writeFile(Li))
        self.button_add.grid(row=Li, column=5)
      
        self.pn= Menubutton (self.FirstFrame, width=40, text=self.List[Li], relief=RAISED)
        self.pn.menu  =  Menu ( self.pn, tearoff = 0 )
        self.pn["menu"]  =  self.pn.menu
        self.pn.grid(row=Li, column=0) 
        
        self.fin = open("Test%s.txt"%(Li+1),"r")
        
        self.iVar  = IntVar()
        self.lineList = self.fin.readlines()
        for self.line in self.lineList:
            print self.line,
            self.pn.menu.add_checkbutton (label=self.line, variable=self.iVar)
        


    def writeFile (self,Li):
        self.input = self.entry_add.get()

        print self.input
        self.pn= Menubutton (self.FirstFrame, width=40, text=self.List[Li], relief=RAISED)
        self.pn.menu  =  Menu ( self.pn, tearoff = 0 )
        self.pn["menu"]  =  self.pn.menu
        self.pn.grid(row=Li, column=0) 
        
        self.menu = self.pn["menu"]
        self.variable = self.var
        self.command = self.entry_add.get() # what you passed as command argument to optionmenu
        #self.menu.add_command(label=self.value, command=_setit(self.variable, self.value, self.command))
        self.pn.menu.add_checkbutton(label=self.value, command=_setit(self.input, self.variable, self.command))
            

        file1 = open("Test%d.txt"%(Li+1),"a")
                  
        print (self.input) # prints out details about the file
        file1.write(self.input)
        file1.write("\n")
        file1.close()
        
    def readFile (self,Li):
        with open("Test%s.txt"%(Li+1)) as self.f:
            #for line in f.readlines().split('\n'):
            for self.line in self.f.read().split("\n"):
                print self.line

if __name__ == '__main__':
    
    # TK requires this for widgets:
    root = Tk()
    
    # Calls parent widget
    app = App()

    root.title(ProgramLabel)
    root.maxsize(width=2500, height=2500)

    app.mainloop()

Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Du führst die Funktion aus und übergibst nur den Rückgabewert an "command" weiter. Die Lösung dafür ist ein einfaches "lambda".

Weiter Anmerkungen:
- _setit, sollte man eigentlich nicht nutzen
- wieso öffnest du einmal die Dateien richtig mit "with" und an anderer Stelle nicht ?
- vorallem schließt du eine Datei nicht
- ein Dateihandle, sollte man nicht als Attribute definieren
- deine split("\n"), kannst du dir sparen, da man einfach Zeilenweise über das handle iterieren kann
- les dir mal die PEP8 durch (vorallem unschön das du einmal CamelCase und dann wieder mixedCase nutzt)
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
sumsum
User
Beiträge: 11
Registriert: Donnerstag 2. Dezember 2010, 13:00

Hallo Xynon1,
danke für die rasche Antwort! Allerdings bin ich ein absoluter Beginner und das mesite, was Du mir gesagt hast, sagt mir nicht wirklich viel.
_setit, sollte man eigentlich nicht nutzen
Ohne _setit hat es gar nicht funktioniert.
ein Dateihandle, sollte man nicht als Attribute definieren
:K
deine split("\n"), kannst du dir sparen, da man einfach Zeilenweise über das handle iterieren kann
Kann ich nicht, da sonst die Einträge einfach hintereinader gepappt werden und mein Auswahlmenü keine Unterpunkte bekommt, sondern einen riesigen Unterpunkt

vorallem schließt du eine Datei nicht
Auch nach Schließen der Datei werden meine Voreinstellungen in den Eingabefeldern ohne den "Add" Befehl einfach in die Textdateiein geschrieben. Nach jedem Starten füllen sich meine Textdateien.


Ich habe nun ein lambda eingesetzt:

Code: Alles auswählen

        
self.button_add = Button(self.FirstFrame, text="Add", command=lambda x=Li: self.writeFile(Li))
        self.button_add.grid(row=Li, column=5)
Allerdings schreibt er nun in alle Textdateien ein "D", wenn ich "Add" anklicke. Bei der Iteration hat "Li" den Wert des letzten Menupunktes bekommen. Wie kann ich ihm Begreiflich machen, dass er immer eine bestimmte Textdatei beschreiben soll?


Danke nochmal!
BlackJack

@sumsum: Ein führender Unterstrich wie bei `_setit()` bedeutet, dass dieser Name nicht für die Öffentlichkeit bestimmt ist. Das ist also nicht bestandteil der öffentlichen API und damit ist weder garantiert, dass es das tut was es jetzt macht, noch das es überhaupt existieren muss. Deshalb sollte man das nicht benutzen.

Mit "Dateihandle" war wahrscheinlich ein Datei-Objekt gemeint. In `readFile()` bindest Du zum Beispiel eins an das `App`-Exemplar was da wohl eher nichts zu suchen hat.

Wenn Du das ``split('\n')`` weg lässt, dann musst Du natürlich auch das `read()` weglassen. Über ein *Dateiobjekt* kann man iterieren und bekommt einzelne Zeilen. `read()` liest das ja aber komplett in eine Zeichenkette und wenn Du dann darüber iterierst, bekommst Du eine Schleife über die einzelnen Zeichen in der Zeichenkette.

Dein ``lambda`` ist so auch nicht ganz richtig. Wie Du ja festgestellt hast, werden freie Variablen erst aufgelöst wenn die Funktion ausgeführt wird. Wenn das `Li` in der ``lambda``-Funktion aufgelöst wird, dann ist das ja an den Wert vom letzten Schleifendurchlauf gebunden. Aber Default-Werte werden gebunden, wenn die Funktion definiert wird. Das `x` hat also den Wert, den `Li` im jeweiligen Schleifendurchlauf hatte.
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

@sumsum
Ok, nochmal Schritt für Schritt:

1. Wieso hängst du vor jede Variable ein "self." ?
Der "self.any" Kennzeichnet ein Attribut oder Methode deiner Instanz. (Deine Instanz ist "app" welche du von "App" instanziert hast.)
Du brauchst also das "self." nur bei Variablen die du für diese gesamte Instanz brauchst.

z.B völliger Blödsinn:

Code: Alles auswählen

        self.s = 0
        self.i = 0
        #for "\n".join(List)
        for self.i in range(4):
            #~ for self.s in self.List:
                #~ print self.s,
            self.setupMenue(self.i)
1.1. "self." brauchst du hier nur bei "setupMenue", da die anderen Variablen nur lokal genutzt werden
1.2. Wenn ich das richtig interpretiere Versuchst/est du hier für jedes Element in der Liste die Funktion auf zurufen, das würde zB. so gehen:

Code: Alles auswählen

for number in xrange(len(self.List)):
    ...
2.

Code: Alles auswählen

self.Quit = Button(self.FirstFrame, text="QUIT", bg="red", command=self.FirstFrame.quit)
Hier solltest du statt "self.FirstFrame.quit", "master.destroy" nutzen.

3.
Dein StartFrame, ist mir völlig Schleierhaft, ist dir bewusst, das deine "App" von "Frame" abgeleitet ist und jede Instanz nun ein Frame ist ?
Das heißt du kannst "app" wie einen normalen Frame handhaben, sprich spar dir das "self.FirstFrame" und nutze einfach "self".

4.
Alle Dateien grundsätzlich mit dem "with" Keyword öffnen, damit sparst du dir das sichere Schließen mit dem try-finally-block. Also aus:

Code: Alles auswählen

        self.fin = open("Test%s.txt"%(Li+1),"r")
        self.iVar  = IntVar()
        self.lineList = self.fin.readlines()
        for self.line in self.lineList:
            print self.line,
            self.pn.menu.add_checkbutton (label=self.line, variable=self.iVar)

4.1 Hier hast du ein Dateihandle, names self.fin - wo brauchst du dieses Datei handle später noch ? (das self ist hier also wie bei vielen anderen Variablen völlig unötig)
4.2 Zudem gibst du hier allen Checkbuttons die selbe Kontrollvariabe "iVar", wenn sich also der Wert in einem Checkbutton ändert, wird der Wert aller Checkbuttons geändert. Folgendens wäre besser:

Code: Alles auswählen

        self.checkbuttons = []
        with open("Test%s.txt"%(Li+1)) as fin:
            for line in fin:
                var = IntVar()
                self.pn.menu.add_checkbutton (label=line, variable=var)
                self.checkbuttons.append(var)


5. Wie in 4. auch hier das "with" Keyword nutzen.
Also aus:

Code: Alles auswählen

        file1 = open("Test%d.txt"%(Li+1),"a")
        print (self.input) # prints out details about the file
        file1.write(self.input)
        file1.write("\n")
        file1.close()

Wird:

Code: Alles auswählen

        with open("Test%d.txt"%(Li+1),"a") as file1:
            file1.write(self.input)
            file1.write("\n")


6.

Code: Alles auswählen

    def setupMenue(self, Li):
        self.var = StringVar()
        self.var = self.readFile(Li)
Was nützt dir "self.var" einer StringVariable zuzuordnen und dann ein "None" Objekt zu übergeben, denn deine "readFile"-Methode hat keinen Rückgabewert.

Ok, ich mach gleich im nächsten Post weiter, dann kannst du schon mal lesen.

Edit: @BlackJack, sollte ich Dateihandle aus meinem Wortschatz für Python streichen ?
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

sumsum hat geschrieben:Auch nach Schließen der Datei werden meine Voreinstellungen in den Eingabefeldern ohne den "Add" Befehl einfach in die Textdateiein geschrieben. Nach jedem Starten füllen sich meine Textdateien.
Das eine hat mit dem anderen ja auch nichst zutun. Die Datei zu schließen ist in nur so fern wichtig, das wenn eine Datei schon geöffnet ist, nicht nochmal geöffnet werden kann. Was zu schweren Folgen führen kann, deswegen Dateien immer nur so kurz wie Möglich öffnen.

7.
Du rufst Instanzierst App ohne ein master, was die Hierarchie durcheinander bringen kann, weil du so ein TopLevel Frame erzeugst. Also mit "root" als Parameter instanzieren.

Code: Alles auswählen

app = App(root)
8.
Ok, jetzt komme ich nicht weiter ohne einige Teile generell umzuschreiben. Um, kurz zu erläutern wo dein Fehler liegt, es ist immer noch "self". Du führst "setupMenue" 4-mal aus und jedes "self."-Attribut welches du dort definierst wird überschrieben, daher gehen deine späteren Funktionen auch nicht mehr, außer mit dem letzten (D), da zB die "entry_add"s, welches du für das command nutzen willst nicht mehr existieren.

Also, räum den Code erstmal nach BlackJacks und meinen Vorschlägen auf und entferne die unötigen "self"s danach wird das ganze für dich und auch für uns wesentlich übersichtlicher.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
sumsum
User
Beiträge: 11
Registriert: Donnerstag 2. Dezember 2010, 13:00

Danke Euch!Ich mache mich mal an die Arbeit...
sumsum
User
Beiträge: 11
Registriert: Donnerstag 2. Dezember 2010, 13:00

Ich habe nun die self.Geschichten geändert. Aber meine Einträge kommen immer noch nicht an. Es ist wie vorher auch: nur der Inhalt des letzten Eingabefeldes wird erkannt. Die anderen ergeben Leere Mengen und einen Zeilenumbruch in Form von einem Rechteck.
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Ja, weil wie ich in 8. geschrieben habe du die Variablen überschreibst, du wirst hier eine Liste oder Dictionary brauchen.
Du musst alle Kontrollvariablen, also die Textvariablen(StringVar, IntVar, ...) alle sauber in einer Sammlung hinterlegen, damit du zu dem späteren Zeitpunkt darauf Zugreifen kannst.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
sumsum
User
Beiträge: 11
Registriert: Donnerstag 2. Dezember 2010, 13:00

ich habe jetzt versucht, die gelesenen Entryfelder in eine Liste zu packen. Allerdings wird nichts gelesen, so dass die Liste im Endeffekt wieder in den ersten drei Stellen leer ist und die 4.Stelle nur belegt.
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Wie speicherst du sie, ohne Quelltext kann ich schwerlich Raten wo dein Fehler liegt.
Hier mal ein Beispiel, wie man es machen könnte:

Code: Alles auswählen

from Tkinter import *

def hello_world(entries):
    for entry in entries:
        entry.set("Hallo Welt")

if __name__ == "__main__":
    root = Tk()
    root.title("eXample")

    entries = []
    for _ in xrange(20):
        control = StringVar()
        Entry(root, textvariable=control).pack()        
        entries.append(control)

    hello_world(entries[3:9])
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
sumsum
User
Beiträge: 11
Registriert: Donnerstag 2. Dezember 2010, 13:00

Leider bleibt immer nur der letzte Eintrag und die ersten 3 Textdateien erhalten nur newlines, aber keine Strings.

Hier meine neuer Code:

Code: Alles auswählen

from __future__ import with_statement
from Tkinter import *
from Tkinter import _setit
from pylab import grid, title

import tkMessageBox

ProgramLabel = " Test your Python :-)"
A =[ ]
           
class App (Frame):
    def __init__(self, master=None):
        Frame.__init__(self, master)
        self.grid()

        self.StartFrame()
        
        self.List =["A", "B", "C", "D"]
        s = 0
        i = 0
        #for number in xrange(len(self.List)):
        for i in range(len(self.List)):
            #~ for self.s in self.List:
                #~ print self.s,
            self.setupMenue(i)
            

        

        self.Quit = Button(self.FirstFrame, text="QUIT", bg="red", command=self.FirstFrame.quit)
        self.Quit.grid(row=5, column=6)
        
    def StartFrame (self):
        # First frame for buttons
        self.FirstFrame = Frame(root)
        self.FirstFrame.grid()

        
        
    def setupMenue(self, Li):
        var = StringVar()
        var = self.readFile(Li)

        #Project
        #~ self.label_show = Label(FirstFrame, text="Recent Project Name")
        #~ self.label_show.grid(row=0, column=1)
        #~ self.entry_show = Entry(FirstFrame, textvariable=self.var)
        #~ self.entry_show.grid(row=0, column=2)

        label_add = Label(self.FirstFrame, text="new %s"%self.List[Li])
        label_add.grid(row=Li, column=3)
        
        entryString = StringVar()       
        self.entry_add = Entry(self.FirstFrame,textvariable=entryString)
        self.entry_add.grid(row=Li, column=4)


        #self.entryString.set(self.List[Li])
        self.value = self.entry_add.get()
        self.entry_add.focus_set()

        A.insert(Li, self.value)
        print "Blaaaaa"
        print self.value

        
        
        #self.button_add = Button(self.FirstFrame, text="Add", command=lambda x=Li: self.writeFile(Li))
        button_add = Button(self.FirstFrame, text="Add", command=lambda x=Li: self.writeFile(Li))
        button_add.grid(row=Li, column=5)


          

        pn= Menubutton (self.FirstFrame, width=40, text=self.List[Li], relief=RAISED)
        pn.menu  =  Menu (pn, tearoff = 0 )
        pn["menu"]  =  pn.menu
        pn.grid(row=Li, column=0) 
        

        checkbuttons = []
        with open("Test%s.txt"%(Li+1)) as fin:
            for line in fin:
                var = IntVar()
                pn.menu.add_checkbutton (label=line, variable=var)
                checkbuttons.append(var)
        




    def writeFile (self,Li):
        input = A[Li]
        #self.input = value
        print input
        pn= Menubutton (self.FirstFrame, width=40, text=self.List[Li], relief=RAISED)
        pn.menu  =  Menu ( pn, tearoff = 0 )
        pn["menu"]  =pn.menu
        pn.grid(row=Li, column=0) 
        
        menu = pn["menu"]
        variable = A[Li]
        command = self.entry_add.get() # what you passed as command argument to optionmenu
        #self.menu.add_command(label=self.value, command=_setit(self.variable, self.value, self.command))
        pn.menu.add_checkbutton(label=self.value, command=_setit(input, variable, command))
            
        #~ with open("Test%s.txt"%(Li+1)) as self.file1:
            #~ #for line in f.readlines().split('\n'):
            #~ for self.line in self.file1.read().split("\n"):
                #~ print self.line
        #~ self.file1.close()   

        with open("Test%s.txt"%(Li+1),"a") as file1:
            file1.write(input)
            file1.write("\n")
        
        
        

    def readFile (self,Li):
        with open("Test%s.txt"%(Li+1)) as f:
            #for line in f.readlines().split('\n'):
            for line in f.read().split("\n"):
                print Li
                print line
        f.close()        
            
        #~ self.fin = open("Test2.txt","r")
        #~ self.iVar  = IntVar()
        #~ ##self.ketchVar = IntVar()
        #~ self.menu = self.optionmenu["menu"]
        #self.lineList = self.fin.readlines()
        #~ for self.line in self.lineList:
            #~ print self.line,
            #~ self.optionmenu.insert(label=self.line, variable=self.iVar)
            



if __name__ == '__main__':
    

    # TK requires this for widgets:
    root = Tk()
    
    # Calls parent widget
    app = App(root)


    root.title(ProgramLabel)
    root.maxsize(width=2500, height=2500)


    app.mainloop()

Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Ich zitiere mich mal selbst:
Xynon1 hat geschrieben:Ja, weil wie ich in 8. geschrieben habe du die Variablen überschreibst, du wirst hier eine Liste oder Dictionary brauchen.
Du musst alle Kontrollvariablen, also die Textvariablen(StringVar, IntVar, ...) alle sauber in einer Sammlung hinterlegen, damit du zu dem späteren Zeitpunkt darauf Zugreifen kannst.
Alles was in "setupMenue" bei dir mit "self" voran steht wird überschrieben, da du diese Funktion in einer Schleife aufrufst.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
sumsum
User
Beiträge: 11
Registriert: Donnerstag 2. Dezember 2010, 13:00

ich muss die Daten doch später auch an andere Funktionen übergeben. Das geht ohne self nicht :-(
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Du sollst ja auch nicht einfach self weglassen, sondern die Referenzen die du brauchst in eine Liste übernehmen, diese wird höchstwarscheinlich self brauchen.

Ich hatte weiter oben schon mal ein Beispiel auf gezeigt, wie man sowas macht.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Antworten