Probleme mit dem ändern der Labeltexte bei Knopfdruck

Fragen zu Tkinter.
Antworten
raspido
User
Beiträge: 28
Registriert: Montag 10. November 2014, 19:42
Kontaktdaten:

Freitag 18. März 2016, 17:41

Hey Leute,

ich habe mir ein kleines Programm zusammengebastelt um ein bisschen zu "üben".

Hier erstmal der Code:

Code: Alles auswählen

from Tkinter import *
a = 0

text1 = ['ds-12300001', 'ds-12300002', 'ds-12300003', 'ds-12300004', 'ds-12300005']
text2 = [10.2, 12.5, 20.0, 25.0, 18.9]
text3 = ['Balkon', 'Kuehlschrank', 'Wohnzimmer', 'Kueche', 'Schlafzimmer']
text4 = ['OK', 'F', 'F', 'OK', 'F']

def weiter(a):
    a -= 1
    lab1.config(text=text1[a])
    lab2.config(text=text2[a])
    lab3.config(text=text3[a])
    lab4.config(text=text4[a])

def zurueck(a):
    a += 1
    lab1.config(text=text1[a])
    lab2.config(text=text2[a])
    lab3.config(text=text3[a])
    lab4.config(text=text4[a])

root=Tk()
lab1=Label(root,text=text1[0])
lab1.pack()
lab2=Label(root,text=text2[0])
lab2.pack()
lab3=Label(root,text=text3[0])
lab3.pack()
lab4=Label(root,text=text4[0])
lab4.pack()

but2=Button(root,text="Zurueck",command=weiter(a), width=8, height=1)
but2.pack(side="left", padx=15, pady=1)
but1=Button(root,text="Weiter",command=zurueck(a), width=8, height=1)
but1.pack(side="left", padx=15, pady=1)

root.title('Wetterstationsclient')
root.geometry('300x300')
root.mainloop()
Die Namen für Variablen und Co sind nicht sonderlich gewählt, sollte aber für erste Versuche reichen.

Die Funktion weiter(a) soll eigentlich den Text in den Labelfeldern ändern, genauer gesagt, den nächsten Posten aus den Arrays auswählen. Die Funktion zurueck(a) soll fast das gleiche, lediglich soll nicht der nächste sondern der vorherige Posten gewählt werden.

Ich hoffe mir kann man helfen, wo der Gedankenfehler ist. Weil wenn ich den Code so starte, wird in den Labels jeweils alles aus Position 2 (z.B. Text1[1], also Ausgabe = ds-12300002) eigentlich sollte es ja Position 1 sein, da die Funktionen doch erst aufgerufen werden, sobald man auf den Button drückt oder nicht?



Michael
Hobby-Programmierung - Also KEIN Profi-Progger
BlackJack

Freitag 18. März 2016, 19:09

@raspido: Die Funktionen rufst *Du* auf wenn Du die Schaltflächen erstellst. Da steht ``command=weiter(a)`` was bedeutet rufe *jetzt* `weiter()` auf und übergib `Button` den *Rückgabewert* des Aufrufs als `command`. Und der Rückgabewert ist `None` womit die Schaltfläche keine Aktion hat wenn man drauf drückt. Dort muss eine Funktion oder Methode übergeben werden und nicht `None`.

Auch für's üben solltest Du vernünftige Namen wählen. *Das* sollte man nämlich auch üben, denn das ist *wichtig*.

`text1` bis `text4` sind parallele Datenstrukturen wo man nur *eine* Liste verwenden sollte in der die Elemente die zusammengehörigen Werte aus den anderen Listen sind. Zum Beispiel als Tupel. Wenn man das Parallel speichert muss man im Code immer wieder dafür sorgen das die richtigen Daten zusammen gesammelt werden. Das ist fehleranfällig.

`weiter()` und `zurueck()` enthalten fast den gleichen Code, so etwas sollte man vermeiden. Code und Daten wiederholt man nicht unnötig. Auch das ist eine Fehlerquelle, denn man muss immer sicherstellen das die redundanten Stellen gleich bleiben wenn man das Programm weiterentwickelt oder Fehler behebt.

Die beiden Funktionen machen ausserdem nicht das was sie sollen, denn das `a` in der Funktion ist ein anderes `a` als das auf Modulebene. Letzteres wird durch die Funktionen nicht geändert. Sollte es auch gar nicht, denn Variablen haben auf Modulebene nichts zu suchen. Da gehören nur Konstanten, Funktionen, und Klassen hin. Womit wir bei objektorientierter Programmierung wären: GUI geht im Grunde nicht ohne. GUI-Programmierung ist bei Tk (und auch den allermeisten anderen GUI-Rahmenwerken) ereignisbasierte Programmierung, das heisst man registriert Rückruffunktionen/-methoden und muss sich Zustand über Aufrufe hinweg merken. Das macht man mit Klassen und Methoden.

Zuletzt noch das was Du zuerst machst: Sternchen-Importe sind Böse™. Damit holst Du Dir alle Namen eines Moduls in den Namensraum des importierenden Moduls. Man weiss dann sehr schnell nicht mehr wo eigentlich welcher Name her kommt, und es besteht die Gefahr von Namenskollisionen. Das `Tkinter`-Modul wird üblicherweise beim importieren mit ``as`` in `tk` umbenannt und über diesen Namen kommt man dann an die Namen in dem Modul heran.
raspido
User
Beiträge: 28
Registriert: Montag 10. November 2014, 19:42
Kontaktdaten:

Freitag 18. März 2016, 19:52

Okay,

das ganze bringt mich zwar noch nich wirklich voran, aber ich werd mal das ändern, was ich zumindest an deinen "Kritikpunkten" verstanden habe und poste den neuen Code.

Nur was mich am ganzen etwas "verwirrt" ist, dass ich durch bisschen Probieren vorab mit folgendem Code, zumindest was "grobes" hinbekommen habe, was ich wollte. Aber noch als Minimalversion.

Code: Alles auswählen

from Tkinter import *

def nachricht():
    lab.config(text="Neuer Text")

root=Tk()
lab=Label(root,text="Viel Spass mit dem Tkinter-Tutorial")
lab.pack()
but=Button(root,text="Button",command=nachricht)
but.pack()
root.mainloop()


Also in dem Fall hat sich der Text im Label sich geändert, wie erwünscht. Das verwirrt mich nämlich etwas. Gut, da wird nichts übergeben, aber es wird ja trotzdem der "Text" des Labels geändert.

Vielleicht kann man mich aber trotzdem mal ein wenig weiter auf das stupsen, wo ich hin muss. Nur Tupel wäre eher schlecht, da kann man Werte ja zur Laufzeit nicht mehr verändern. Und ggf. soll das noch in Zukunft gehen.



Michael
Hobby-Programmierung - Also KEIN Profi-Progger
raspido
User
Beiträge: 28
Registriert: Montag 10. November 2014, 19:42
Kontaktdaten:

Freitag 18. März 2016, 20:03

Hier schon mal der etwas veränderten Code:

Code: Alles auswählen

from Tkinter import *
a = 0

ausgabe = [['ds-12300001', 'ds-12300002', 'ds-12300003', 'ds-12300004', 'ds-12300005'],[10.2, 12.5, 20.0, 25.0, 18.9],['Balkon', 'Kuehlschrank', 'Wohnzimmer', 'Kueche', 'Schlafzimmer'],['OK', 'F', 'F', 'OK', 'F']]

def weiter(a):
    a -= 1
    neuer_text(a)

def zurueck(a):
    a += 1
    neuer_text(a)

def neuer_text(a):
    lab1.config(text=ausgabe[0][a])
    lab2.config(text=ausgabe[1][a])
    lab3.config(text=ausgabe[2][a])
    lab4.config(text=ausgabe[3][a])

root=Tk()
lab1=Label(root,text=ausgabe[0][a])
lab1.pack()
lab2=Label(root,text=ausgabe[1][a])
lab2.pack()
lab3=Label(root,text=ausgabe[2][a])
lab3.pack()
lab4=Label(root,text=ausgabe[3][a])
lab4.pack()

but2=Button(root,text="Zurueck",command=weiter(a), width=8, height=1)
but2.pack(side="left", padx=15, pady=1)
but1=Button(root,text="Weiter",command=zurueck(a), width=8, height=1)
but1.pack(side="right", padx=15, pady=1)

root.title('Wetterstationsclient')
root.geometry('300x300')
root.mainloop()
Das mit dem Import versteh ich nicht so ganz, vielleicht wäre da auch ein Hinweis hin, damit ich das "Böse" beheben kann.

Über weitere Hilfe bin ich immer dankbar.



Michael
Hobby-Programmierung - Also KEIN Profi-Progger
BlackJack

Freitag 18. März 2016, 20:57

@raspido: Die Datenstruktur ist immer noch falsch. Da sind ja immer noch die parallelen Listen wo zusammengehörige Werte immer noch in *verschiedenen* Listen stecken. Wenn Tupel nicht gehen, dann sollte man auch keine Listen verwenden, denn Listen sind für *gleichartige* Informationen. Wenn die Werte an den verschiedenen Indizes unterschiedliche Bedeutung haben und veränderlich sein sollen, dann sollte man mit Objekten mit Attributen arbeiten, statt mit magischen Indexwerten. Um objektorientierte Programmierung kommst Du wegen der GUI ja sowieso nicht herum, also kannst Du die auch dafür einsetzen. Was bedeuten die einzelnen Werte denn? So etwas sollte man beim lesen nicht raten müssen.

`a` ist immer noch ein schlechter Name, weil absolut nichtssagend.

Und auf Modulebene sind immer noch Variablen. Auch das Hauptprogramm gehört in eine Funktion, und damit hat sich dann auch erledigt, dass Du auf ”magische” Weise auf Werte in den anderen Funktionen zugreifen kannst, die nicht als Argumente übergeben wurden.

Was den Sternchenimport angeht: ``import Tkinter as tk``. Und dann kannst Du über den Namen `tk` an die Namen aus dem Modul herankommen.
raspido
User
Beiträge: 28
Registriert: Montag 10. November 2014, 19:42
Kontaktdaten:

Freitag 18. März 2016, 21:17

Okay,

dann werd ich mich wohl oder übel weiter einlesen müssen. Gibt es diesbezüglich ggf. ein paar gute und einfach Tutorials, wo man so etwas drin einsteigen kann? Also um das ganze ein wenig besser zu verstehen.

Und nun zum erklären der Werte, die ersten 5 Werte sind Seriennummern, die zweiten 5 Werte sind Temperaturwerte, danach kommen 5 Räume und 5 Werte, die angeben, ob ein Sollwert in Ordnung ist oder außerhalb. Also die Werte hängen schon zusammen. Also jeweils z.B. die ersten Werte aus jedem Block gehören zum Sensor 1 (also ein Block von Informationen) z.B..

In Zukunft wird evtl. die Liste durch eine Datenbankabfrage ersetzt werden, welche immer wieder aktuallisiert werden soll. Aber ich dachte ich nehme erstmal "statische" Werte zum üben.

Die Daten werde von meinem Server in die Datenbank geschrieben. Es handelt sich um ein Raspberry Pi, aber dass ist für die ganze Geschichte nicht unbedingt von nöten.



Michael
Hobby-Programmierung - Also KEIN Profi-Progger
BlackJack

Freitag 18. März 2016, 22:18

@raspido: Wie die Werte zusammengehören war mir schon klar, aber warum speicherst Du dann die zusammengehörigen Werte nicht zusammen? Die fünf Seriennummern ergeben ja nicht zusammen eine Einheit und auch nicht die fünf Temperaturwerte, und so weiter, sondern jeweils eine Seriennummer, ein Temperaturwert, ein Name, und ein Flag gehören zusammen und sollten auch zusammen gespeichert werden. das vereinfacht den Zugriff und verhindert das die Einzellisten durch mögliche Programmfehler nicht mehr synchron sind.

Wenn die Werte in der letzten Liste nur zwei Werte annehmen können die aussagen ob etwas zutrifft oder nicht, dann würde man da eher `True` und `False` für verwenden.

Du fängst eventuell auch am falschen Ende an, nämlich bei der GUI. Das kann in der Anfangsphase praktisch sein um eine Idee davon zu bekommen wie der Benutzer das später bedienen soll und welche Operation man dafür implementieren muss, aber das Programm selber würde ich von der Geschäftslogik her aufbauen, also erst einmal ohne GUI die tatsächliche Funktionalität. Das kann man dann leichter testen, und man läuft weniger Gefahr Geschäftslogik mit GUI-Code zu vermischen.
raspido
User
Beiträge: 28
Registriert: Montag 10. November 2014, 19:42
Kontaktdaten:

Freitag 18. März 2016, 23:49

Ja okay, aber das ganze soll eher ein überschaubares kleines Tool für den eigenen Gebrauch sein. Also von daher würde ich es und vll meine Frau bedienen.

Und das ganze ist eigentlich auch nur ein Hobby, also ich will kein Geld damit verdienen. Dafür fehlt mir zum Teil die Zeit für.

Aber gibt es ggf. etwas wo man sagen kann, darum komme ich nicht rum.

Weil was das Programm können soll, ist mir ja an sich klar. Es geht zum Teil eher an die Umsetzung.



Michael
Hobby-Programmierung - Also KEIN Profi-Progger
Benutzeravatar
wuf
User
Beiträge: 1497
Registriert: Sonntag 8. Juni 2003, 09:50

Montag 21. März 2016, 08:39

Hi raspido

Hier etwas zum ausprobieren:

Code: Alles auswählen

from functools import partial
try:
    # Python2
    import Tkinter as tk
except ImportError:
    # Python3
    import tkinter as tk


data_collection = [
    ['ds-12300001', 10.2, 'Balkon', 'OK'],
    ['ds-12300002', 12.5, 'Kuehlschrank', 'F'],
    ['ds-12300003', 20.0, 'Wohnzimmer', 'F'],
    ['ds-12300004', 25.0, 'Kueche', 'OK'],
    ['ds-12300005', 18.9, 'Schlafzimmer', 'F']
    ]

def update_output(step=0):
    if 0 <= root.data_index + step < len(data_collection):
        root.data_index += step
        data = data_collection[root.data_index]
        for index, data_item in enumerate(data):
            root.output_vars[index].set(data_item)
            
root = tk.Tk()
root.title('Wetterstationsclient')
root.geometry('300x300')

root.data_index = 0
root.output_vars = list()

for label_index in range(len(data_collection)):
    str_var = tk.StringVar()
    root.output_vars.append(str_var)
    tk.Label(root,textvariable=str_var).pack()

update_output()

tk.Button(root, text="Weiter",command=partial(update_output, 1),
    width=8, height=1).pack(side="right", padx=15, pady=1)
        
tk.Button(root, text="Zurueck",command=partial(update_output, -1),
    width=8, height=1).pack(side="left", padx=15, pady=1)

root.mainloop()
Gruss wuf :wink:
Take it easy Mates!
raspido
User
Beiträge: 28
Registriert: Montag 10. November 2014, 19:42
Kontaktdaten:

Sonntag 27. März 2016, 11:10

Danke,

funktionert wie gedacht. Werde mir mal nach und nach noch gedanken machen, um die ganze Anwendung zu bauen. Wird aber alles noch etwas dauern und nach und nach passieren.



Danke noch mal.


Michael
Hobby-Programmierung - Also KEIN Profi-Progger
Antworten