@MADI'S Tech: Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.
Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (MixedCase). Einbuchstabige Namen wie `a`, `b`, `c`, … sind schlecht weil sie nicht beschreiben was denn der Wert bedeutet. Genau dafür sind Namen aber da. Die sollten deshalb auch keine kryptischen Abkürzungen enthalten.
Man muss auch nicht alles an einen Namen binden wenn man das danach nie wieder für irgend etwas braucht.
Kommentare sollen dem Leser einen Mehrwert über den Code geben. Faustregel: Kommentare beschreiben nicht *was* der Code macht, denn das steht da bereits als Code, sondern warum er das macht. Sofern das nicht offensichtlich ist.
`thumbnail()` ist die falsche Methode wenn man kein kleines Vorschaubild erzeugen will. Zum Grösse ändern ist die `resize()`-Methode da.
An den Namen `Ausgabefeld` wird ein Frame gebunden und ein Label danach an den Namen `Ausgabeframe`. Verwirrend ohne Ende.
Das "Eingabefeld 970 x 360 px.png"-Bild ist einfach nur ein einfarbiges Rechteck. Wozu soll das gut sein? Für eine einfarbige Fläche braucht man keine Bilddatei. Und am Ende werden in diese Zellen auch noch andere Widgets gesetzt‽
Der Dateiname sollte nur *einmal* im Quelltext stehen, als Konstante, damit man den leicht ändern kann ohne den im ganzen Programm suchen und ersetzen zu müssen.
Funktionen (und Methoden) bekommen alles was sie ausser Konstanten benötigen als Argument(e) übergeben.
"end" gibt es als Konstante im `tkinter`-Modul.
Der `isfile()`-Test ist überflüssig, denn der Dateimodus "a" legt eine nichtvorhandene Datei an.
`Ereignis` ist kein guter Name für eine Datei.
Dateien sollte man wo möglich mit der ``with``-Anweisung öffnen.
Bei Textdateien sollte man immer explizit die Kodierung angeben. Wenn man die selbst in der Hand hat, bietet sich UTF-8 an.
CSV-Dateien schreibt und liest man nicht selbst, die sind komplizierter als sie auf den ersten Blick aussehen. Das Programm fällt beispielsweise auf die Nase wenn man irgendwo in der Eingabe ein Semikolon verwendet, oder in den Notizen einen Zeilenumbruch. Beides erlaubt CSV, man muss das nur der Spezifikation entsprechend umsetzen. Das gibt es schon fertig als `csv`-Modul in der Standardbibliothek. Dann muss man beim öffnen der Datei noch ``newline=""`` als Argument angeben.
``global`` hat in einem sauberen Programm nichts zu suchen. Das ist dann auch der Punkt wo man um objektorientierte Programmierung nicht herum kommt. Das braucht man für jede nicht-triviale GUI.
`Arbeitsliste` ist kein guter Name — er sagt nicht was der Inhalt bedeutet und Grunddatentypen haben nichts in Namen verloren. Zudem sollte diese Liste von Anfang an existieren, halt leer am Anfang, sonst gibt es einen `NameError` wenn man versucht etwas zu löschen bevor man die Datei geladen hat.
Komisch bis Falsch ist ausserdem das neu erzeuge Einträge zwar gespeichert werden, aber nur in der Datei und nicht in dieser Liste. Auf diese weise können Einträge verloren gehen (wenn das Löschen funktionieren würde und dabei nicht sowieso Einträge verloren gehen würden.)
``for i in range(len(Arbeitsliste)):`` ist in Python ein „anti pattern“. Man kann in Python direkt über die Elemente von Sequenztypen wie Listen iterieren. Ohne den Umweg über einen Index. Der wird in `abrufen()` auch noch dazu verwendet um die Listenelemente (Zeichenketten) durch Listen mit Zeichenketten zu ersetzen. Da würde man eine neue Liste erstellen statt eine alte so zu verändern. Letztlich wird einem aber auch das vom `csv`-Modul an der Stelle abgenommen.
`abrufen()` berücksichtig nicht, dass die Datei eventuell leer sein könnte, und das Anzeigen des ersten Eintrags dann nicht funktioniert. Auch der Fall, dass die Datei nicht gelesen werden kann, beispielsweise weil sie noch gar nicht existiert, wird nicht behandelt.
`eintrag_loeschen()` ist dann ziemlich wirr und undurchsichtig. Das die Schleife abbricht nach dem *ein* Datensatz gelöscht wurde ist beispielsweise ziemlich versteckt.
Das ``else`` mit dem ``continue`` macht keinen Sinn, denn das ändert nichts am Programmfluss.
`vergleich1` und `vergleich2` ist ein „code smell“. Man nummeriert keine Namen. Da will man entweder bessere Namen oder eine Datenstruktur. Oft eine Liste. `c` (schlechter Name) wird definiert, aber nirgends verwendet.
Hier ist wieder das ``for i in range(len(…)):`` „anti pattern“.
Zwischenstand (ungetestet):
Code: Alles auswählen
#!/usr/bin/env python3
"""
Dieses Pogramm erzeugt eine GUI, in dem Daten zu Ereignissen eingegeben, auf
eine .csv Datei gespeichert, aus der selben .csv Datei gelesen und gelöscht
werden.
Copyright (C) 2020 MADI'S Tech
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see https://www.gnu.org/licenses/.
"""
import csv
import tkinter as tk
from tkinter import messagebox
from PIL import Image, ImageTk
EREIGNISSE_DATEINAME = "Ereignisse.csv"
ENCODING = "UTF-8"
DELIMITER = ";"
class Reisetagebuch:
def __init__(self, master):
self.eintraege = list()
self.datum_var = tk.StringVar()
self.ort_var = tk.StringVar()
self.bild_pfad_var = tk.StringVar()
self.ausgabedatum_var = tk.StringVar()
self.ausgabe_ort_var = tk.StringVar()
self.ausgabe_bild_var = tk.StringVar()
frame = tk.Frame(master)
self.logo_image = ImageTk.PhotoImage(
Image.open("GUI Header 970x250 px.png").resize((698.4, 180))
)
tk.Label(
frame, borderwidth=0, highlightthickness=0, image=self.logo_image
).pack()
frame.grid(row=0, column=0, rowspan=2, columnspan=6)
tk.Label(
master,
text="Ereigniseingabe:",
bg="#242254",
fg="white",
font=("Poppins", "18", "bold"),
).grid(row=2, column=0, columnspan=2)
tk.Button(
master,
text="Eintrag speichern",
bd=0,
bg="white",
command=self.eintrag_speichern,
).grid(row=2, column=2, columnspan=4)
tk.Label(
master,
text="Datum: ",
bg="#242254",
fg="white",
font=("Poppins", "12", "bold"),
).grid(row=3, column=0)
tk.Entry(master, textvariable=self.datum_var, width=40).grid(
row=3, column=1, columnspan=2, sticky=tk.W
)
tk.Label(
master,
text="Datenpfad zum Foto/Fotos: ",
bg="#242254",
fg="white",
font=("Poppins", "12", "bold"),
).grid(row=3, column=3, columnspan=3, sticky=tk.S)
tk.Label(
master,
text="Ort: ",
bg="#242254",
fg="white",
font=("Poppins", "12", "bold"),
).grid(row=4, column=0)
tk.Entry(master, textvariable=self.ort_var, width=40).grid(
row=4, column=1, columnspan=2, sticky=tk.W
)
tk.Entry(master, textvariable=self.bild_pfad_var, width=45).grid(
row=4, column=3, columnspan=3
)
tk.Label(
master,
text="Notizen: ",
bg="#242254",
fg="white",
font=("Poppins", "12", "bold"),
).grid(row=5, column=0, rowspan=2)
self.notizen_text_editor = tk.Text(master, height=4, width=61)
self.notizen_text_editor.grid(
row=5, column=1, rowspan=2, columnspan=5, sticky=tk.W
)
tk.Label(
master,
text="Ereignisausgabe:",
bg="#242254",
fg="white",
font=("Poppins", "18", "bold"),
).grid(row=8, column=0, columnspan=2)
tk.Button(
master,
text="Ausgabe starten",
bd=0,
bg="white",
command=self.abrufen,
).grid(row=8, column=2, columnspan=4)
tk.Entry(master, textvariable=self.ausgabedatum_var, width=33).grid(
row=9, column=0, columnspan=2
)
tk.Entry(master, width=67, textvariable=self.ausgabe_bild_var).grid(
row=9, column=2, columnspan=4, sticky=tk.W
)
tk.Entry(master, textvariable=self.ausgabe_ort_var, width=33).grid(
row=10, column=0, columnspan=2
)
self.ausgabe_notiz_text_editor = tk.Text(master, height=4, width=50)
self.ausgabe_notiz_text_editor.grid(
row=10, column=2, columnspan=4, sticky=tk.W
)
tk.Label(
master,
text="Diesen Eintrag fertig bearbeitet?: ",
bg="#242254",
fg="white",
font=("Poppins", "13", "bold"),
).grid(row=11, column=2, columnspan=3)
tk.Button(
master, text="Eintrag löschen", bd=0, command=self.eintrag_loeschen
).grid(row=11, column=5)
def show_first_entry(self):
if self.eintraege:
datum, ort, bild_pfad, notizen = self.eintraege[0]
else:
datum, ort, bild_pfad, notizen = [""] * 4
self.ausgabedatum_var.set(datum)
self.ausgabe_ort_var.set(ort)
self.ausgabe_bild_var.set(bild_pfad)
self.ausgabe_notiz_text_editor.delete(1.0, tk.END)
self.ausgabe_notiz_text_editor.insert(1.0, notizen)
def eintrag_speichern(self):
datum = self.datum_var.get().strip()
if not datum:
messagebox.showinfo("Fehler", "Gebe bitte ein Datum ein")
else:
eintrag = [
datum,
self.ort_var.get().strip(),
self.bild_pfad_var.get().strip(),
self.notizen_text_editor.get(1.0, tk.END).rstrip(),
]
with open(
EREIGNISSE_DATEINAME, "a", encoding=ENCODING, newline=""
) as csv_file:
csv.writer(csv_file, delimiter=DELIMITER).writerow(eintrag)
self.eintraege.append(eintrag)
messagebox.showinfo("Eintrag", "Eintrag wurde gespeichert")
def abrufen(self):
try:
with open(
EREIGNISSE_DATEINAME, "r", encoding=ENCODING, newline=""
) as csv_file:
self.eintraege = list(
csv.reader(csv_file, delimiter=DELIMITER)
)
except FileNotFoundError:
self.eintraege = list()
self.show_first_entry()
def eintrag_loeschen(self):
key = [
self.ausgabedatum_var.get().strip(),
self.ausgabe_ort_var.get().strip(),
self.ausgabe_bild_var.get().strip(),
]
index = None
for i, eintrag in enumerate(self.eintraege):
if key == eintrag[:3]:
index = i
break
if index is not None:
self.eintraege.pop(index)
with open(
EREIGNISSE_DATEINAME, "w", encoding=ENCODING, newline=""
) as csv_file:
csv.writer(csv_file, delimiter=DELIMITER).writerows(
self.eintraege
)
messagebox.showinfo("Löschen", "Eintrag wurde gelöscht")
self.show_first_entry()
else:
messagebox.showinfo("Löschen", "Eintrag nicht gefunden")
def main():
root = tk.Tk()
root.configure(bg="#242254")
root.title("Reisetagebuch")
root.iconbitmap("Icon 40x40 px.ico")
_ = Reisetagebuch(root)
root.mainloop()
if __name__ == "__main__":
main()
Die Bedienung ist aber komisch. Man sieht immer nur den ersten Eintrag in der Datei im Ausgabebereich. Warum gibt es die gleichen Felder zweimal? Benutzer sind es ja eigentlich gewohnt das man eine Datei laden, bearbeiten, und dann speichern kann. Und das man in den Datensätzen vor- und zurück blättern kann. Und das man nur eine Maske für Ein- und Ausgabe hat. Dafür noch eine Übersicht über alle Datensätze in Form einer Liste/Tabelle.