Werte aus parametrisch erzeugten Entrys bestimmen

Fragen zu Tkinter.
Antworten
DanielFackler
User
Beiträge: 5
Registriert: Montag 13. November 2017, 10:41

Hallo Python-Gemeinde,

Folgendes Programm erzeugt ein Fenster, indem je nach Auswahl des Dropdowns mehr oder weniger Entrys und Labels erscheinen. Meine Frage nun: Wie kann ich gezielt auf die Einzelnen Entrys zugreifen? Ich möchte die Werte der Eingaben per Buttonclick in ein .txt-File schreiben. Eigentlich hapert es nur an der GET_ALL Funktion. Ich will den Inhalt der einzelnen Zellen, Variablen zuordnen. Mit .get() scheint es nicht zu funktionieren.

Ich hoffe auf Antworten von euch und bedanke mich schon einmal.

Gruß

Code: Alles auswählen

import tkinter as tk
from tkinter import *

def open_window():
    X = fenster()                      
    Eingabemaske(X)
    schreiben(X)
    X.mainloop()
        
# Fenster kreieren
def fenster():
 
    X = tk.Tk()                                                                          
    X.wm_title('Test')   
    X.wm_geometry('600x600')
    return X 
    
def Eingabemaske(X):
    
    def onChange(val):
        if val == "Spiralverzahnt":
            
            numb_lab = 4

            l_text = [
                      'Achswinkel A',  
                      'Achsversatz av',
                      'Zähnezahl Ritzel z1',
                      'Zähnezahl Tellerrad z2',
                      ]
        elif val == "Geradverzahnt":
            
            numb_lab = 3

            l_text = ['Schrägungswinkel', 'Eingriffswinkel alphan', 'Drehzahl n']

        # Destroys all existing widgets in the container frame
        for wid in container.winfo_children():
            wid.destroy()
            
        for u in range(numb_lab):
            l = tk.Label(container, text=l_text[u])
            l.grid(row=1 + u, column = 0, sticky=W)
         
            e = tk.Entry(container)
            e.grid(row=1 + u, column = 1, sticky=W)
            
    options = ["Spiralverzahnt", "Geradverzahnt"]
    var = tk.StringVar()
    var.set("Spiralverzahnt")

    op_menu = tk.OptionMenu(X, var, *options, command = onChange)
    op_menu.place(x=450, y=0)

    # Container frame for the Entry widgets
    
    container = tk.Frame(X)
    container.grid()
    return container
        
#Button Werte in File schreiben
def schreiben(X):

    b = tk.Button(X, text = 'Werte schreiben', command = write_input_geo)
    #print(Eingabe)
    b.place(x = 450, y = 50)


def GET_ALL():
   
    '''Werte an Variablen übergeben'''
    A = e.get(1)
    av = e.get(2)
    z1 = e.get(3)
    z2 = e.get(4)
    

def write_input_geo():

    Input = GET_ALL()
    var = ['A','av','z1','z2']
    for i in range(len(Input)):
        Input[i] = [var[i],Input[i]]
    with open(read.cfg()['input'],'w') as out:
        for i in range(len(Input)):
                out.write(str(Input[i][0])+ ' ' + str(Input[i][1]) + '\n')


open_window()
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Bitte PEP8 zur Formatierung und Schreibweise von Python-Code beachten - so ist der Code fuer andere hier schwer zugaenglich, und darum postest du ihn ja.

Auch ist ungarische Notation seit Jahrzehnten nicht mehr wirklich ein Ding - ausser bei MS, in alten APIs. So etwas wie l_text macht man nicht. Das ist dann "texte", auch im Deutschen hat man ja einen Plural verfuegbar.

Und wo wir von Deutsch reden: die Mischung aus Englisch und Deutsch mutet seltsam an.

Das "schreiben" und "fenster" eine frei stehende Funktion ist, statt eine Methode auf Eingabemaske ist auch ungewoehnlich. Und das darin dann auch noch ein Button angelegt wird, statt den gleich der Erzeugung der Eingabemaske anzulegen. Generell solltest du all das einfach in den Konstruktor (__init__) von Eingabemaske verlagern.

Oh, und jetzt sehe ich gerade: Eingabemaske ist *benannt* wie eine Klasse. Ist aber keine. Darum ist PEP8 wichtig, und es sollte auch eine Klasse werden. Denn dann kannst du einfach bei Auswahl eine Liste von StringVars in der Eingabemaske-Instanz anzulegen.
DanielFackler
User
Beiträge: 5
Registriert: Montag 13. November 2017, 10:41

Hi deets,

danke für deine Antwort. Ich beschäftige mich erst seit 3 Wochen mit Python im Zuge meiner Bachelorarbeit. Ich bin eigentlich Maschinenbauer und kein gelernter Programmierer. :D Deshalb sind mir Begriffe wie PEP8 oder ungarische Notation leider kein Begriff. :?
Mein eigentliches Programm ist noch sehr viel verschachtelter, weshalb ich auf die schnelle ein Programm mit der Funktion, die mir Sorgen bereitet, "zusammengeschustert" habe. Im Grunde geht es mir nur darum, wie ich an die Werte aus den parametisch erzeugten Entrys komme.
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Wie gesagt, fuer so etwas fuehrt man normalerweise eine Klasse ein. Die erlaubt dir, die Veraenderung der Entry-Felder nachzuhalten, und somit beim speichern immer zu wissen, was gerade angezeigt wird.

PEP8 und ungarische Notation kann man googeln ;) Beides sind Konventionen darueber, wie man Code schreibt. Du bist hochgradig inkonsistent darin, wie du Dinge benennst. Das ist verwirrend fuer uns, und auch fuer dich. Darum ist es besser, sich da einen einheitlichen Standard anzugewoehnen.
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@DanielFackler: Funktionen sollten genau eine Funktion haben und auch nach einer Tätigkeit benannt werden. Bei `Eingabemaske` ist mir das nicht so klar. Wenn ich das richtig verstehe, willst Du den Inhalt mehrerer Eingabefelder in einer Liste speichern, dann solltest Du die Eingabefelder auch in eine Liste schreiben. Den *-Import solltest Du löschen und alle Namen über tk.xxx ansprechen. Variablennamen sollten sprechend sein, `x` oder `e` sind das nicht, `numb_lab` ist eine kryptische Abürzung, von der man auch nur raten kann, was sie bedeuten soll. `for u in range(...)` ist ein sogenanntes Antipattern, weil man in Python direkt über Listen iterieren kann. Braucht man zusätzlich einen Index, nimmt man enumerate.
`schreiben` ist eine seltsame Funktion, weil sie nur einen Button erzeugt.

In `write_input_geo` mehrere dieser Antipattern und zustätzlich noch String-Gestückel zusammengepackt. Mit einer funktionierenden get_all-Funktion könnte das so aussehen:

Code: Alles auswählen

def write_input_geo(filename, entries):
    input_variables = get_all_variables(entries)
    names = ['A','av','z1','z2']
    with open(filename, 'w') as out:
        for name, variable in zip(names, input_variables):
            out.write("{} {}\n".format(name, variable))
DanielFackler
User
Beiträge: 5
Registriert: Montag 13. November 2017, 10:41

Hallo Sirius,

vielen Dank für die Antwort. Ich habe mir deine Tipps vorgenommen, jedoch komme ich noch nicht ans Ziel. Der Fehler sitzt in der
Funktion get_all_variables(). Wie komme ich an eine Liste mit allen Einträgen der Entries? Danke für eure Mühen :P

Code: Alles auswählen

import tkinter as tk


def open_window():
    root = window()                      
    create_entries_labels(root)
    button_write(root)
    root.mainloop()
        
# Fenster kreieren
def window():
 
    root = tk.Tk()                                                                          
    root.wm_title('Test')   
    root.wm_geometry('600x600')
    return root 
    
def read_input_geo():
    '''
    Liest die Eingaben aus dem Inputfile aus und gibt ein Dictionary mit den 
    Eingaben zurück. Dieses Dictionary wird später noch um die Ergebnise der 
    Berechnung erweitert. 
    '''
    def check(string):
        string = string.strip()
        
        if not len(string) == 0:
            true_false = True
        else:
            true_false = False
        return true_false
        

    geo={}
    filepath = ...
    with open(filepath,'r') as in_geo:
        in_geo = in_geo.readlines()
        for i in range(len(in_geo)):
            if check(in_geo[i]):
                key = in_geo[i].split(' ')[0]
                try:
                    var = float(in_geo[i].split(' ')[1].strip())
                except:
                    #print(in_geo[i])
                    var = str(in_geo[i].split(' ')[1].strip())
                                   
                geo.update({key : var})
            else:
                pass

    return geo 
    
def create_entries_labels(root):
    

    def onChange(val):
        if val == "Spiralverzahnt":
            
            number_of_entries_labels = 4

            text_labels = [
                      'Achswinkel A',  
                      'Achsversatz av',
                      'Zähnezahl Ritzel z1',
                      'Zähnezahl Tellerrad z2',
                      ]
        elif val == "Geradverzahnt":
            
            number_of_entries_labels = 3

            text_labels = ['Schrägungswinkel', 'Eingriffswinkel alphan', 'Drehzahl n']

        #Destroys all existing widgets in the container frame
        for wid in container.winfo_children():
            wid.destroy()
            
        for u in range(number_of_entries_labels):
            global label, entry
            label = tk.Label(container, text = text_labels[u])
            label.grid(row=1 + u, column = 0)
         
            entry = tk.Entry(container)
            entry.grid(row=1 + u, column = 1)
            entry.insert(0, '0') #Hier will ich die Entrys mit den alten Werten aus dem Textfile
                                 #vorbelegen.Hierfür habe ich die read_input_geo() geschrieben.
                                 #Dort wird das .txt-File ausgelesen und die Werte in ein 
                                 #Dictionary gepackt
            
            
            
    options = ["Spiralverzahnt", "Geradverzahnt"]
    var = tk.StringVar()
    var.set("Spiralverzahnt")

    op_menu = tk.OptionMenu(root, var, *options, command = onChange)
    op_menu.place(x=450, y=0)

    container = tk.Frame(root)
    container.grid()
    return container
        
#Button Werte in File schreiben
def button_write(root):

    b = tk.Button(root, text = 'Werte schreiben', command = write_input_geo)
    b.place(x = 450, y = 50)


def get_all_variables():
   
    '''Werte an Variablen übergeben'''
    input_entries = [entry.get()] #Hier sitzt der fehler: Aktuell wird nur der Inhalt des letzten 
    print(input_entries)          #Entries in die Liste geschrieben
    return input_entries                              
    
    

def write_input_geo():
    filepath = ...
    input_variables = get_all_variables()
    names = ['A','av','z1','z2']
    with open(filepath,'w') as out:
        for name, variable in zip(names, input_variables):
                out.write('{} {} \n'.format(name, variable))

open_window()
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Aller guten Dinge sind ja bekanntlich drei: dazu benutzt man eine Klasse:

Code: Alles auswählen

class Ding(object):

    def __init__(self):
          self._entries = []

    def setup(self, selection):
          if selection == 1:
             self._entries = ["a", "b"]
          elif selection == 2:
             self._entries = ["c", "d"]

    def save(self):
          print(self._entries)
DanielFackler
User
Beiträge: 5
Registriert: Montag 13. November 2017, 10:41

Hey,

vielen Dank für das Feedback. Leider bekomme ich es trotzdem nicht gebacken. Vielen Dank für die Mühen.

Gruß
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@DanielFackler: Dein `check` in `read_input_geo` ist eine sehr umständliche Art und Weise `if lines.strip():` zu schreiben. Fileobjekte sind iterierbar, also kein Grund, immer noch das Anti-Pattern `for i in range(...):` zu benutzen. Nackte Excepts niemals verwenden. Da wird wirklich jeder Fehler (auch viele Tippfehler) abgefangen und eine Fehlersuche verunmöglicht; hier willst Du eigentlich nur einen ValueError abfangen. Wörterbücher haben Indexzugriff! Um ein Element hinzuzufügen ein extra Wörterbuch zu erzeugen ist extrem umständlich. Dateinamen sollten nicht irgendwo mittem im Code stehen. Entweder definiert man eine Konstante und/oder übergibt den Dateiname als Parameter. Ein `else: pass` kann weg.
Die Funktion könnte also so aussehen:

Code: Alles auswählen

def read_input_geo(filepath):
    '''
   Liest die Eingaben aus dem Inputfile aus und gibt ein Dictionary mit den
   Eingaben zurück. Dieses Dictionary wird später noch um die Ergebnise der
   Berechnung erweitert.
   '''
    geo = {}
    with open(filepath, 'r') as lines:
        for line in lines:
            if line.strip():
                key, value = line.split(None, 1)
                try:
                    value = float(value)
                except ValueError:
                    value = value.strip()
                geo[key] = value
    return geo
`open_window`, `window` und `button_write` sind immer noch 3 Minifunktionen, die ohne Probleme eine Funktion sein könnten, um ein Fenster aufzubauen.

Die Funktion `create_entries_labels` erzeugt Entries, die Du noch woanders im Programm verwenden willst. Um diesen Zustand also zu speichern, ist es sinnvoll, eine Klasse zu schreiben, üblicherweise eine pro Fenster. Des `global` in Zeile 78 macht keinen Sinn, weil die Variablen in jedem Schleifendurchgang überschrieben werden und so nur der letzte Wert erhalten bleibt. Generell, vergiss das es `global` gibt, das löst keine Probleme, sondern schafft nur welche. Was Du machen mußt, ist die Entries, die Du in dieser Schleife erzeugst, in einer Liste zu speichern. `number_of_entries_labels` ist eine unnötige Variable, da sie nur die Länge der Liste `text_labels` hart codiert enthält. Eine potentielle Fehlerquelle, falls sich die Liste mal ändert. Sie ist auch unnötig, weil Du wieder die `for ... in range` Schleife durch eine Schleife über die Elemente von `text_labels` ersetzen sollst, hier noch mit `enumerate`, weil Du zusätzlich einen Index brauchst.

Was Klassen sind, `enumerate` oder sonstige Unbekannte hier im Text, schlägst Du am besten entweder direkt in der Python-Dokumentation nach, oder in dem Tutorial, das Du zum Lernen benutzt. Wer Klassen nicht kennt, kann eigentlich keine GUIs schreiben. Wie das für Tk aussieht, gibt es auch Beispiele zu Hauf in diesem Forum.
Antworten