@Kleonx: Noch ein paar Anmerkungen zum Quelltext:
Sternchen-Importe machen Programme unübersichtlicher und bergen zudem die Gefahr von Namenskollisionen. Aus dem `tkinter`-Modul holst Du Dir ca. 190 Namen in das Modul, von denen Du nur einen ganz kleinen Bruchteil tatsächlich verwendest. Üblicherweise wird das `tkinter`-Modul mit ``import tkinter as tk`` an den Namen `tk` gebunden und über diesen Namen dann auf die Objekte aus diesem Modul zugegriffen. Das ist nur wenig mehr Tipparbeit, aber man sieht beim Lesen sofort wo der jeweilige Name her kommt.
Auf Modulebene gehören nur Definitionen von Konstanten, Funktionen, und Klassen. Das Hauptprogramm gehört auch in eine Funktion und auf keinen Fall auf Modulebene zwischen Funktionsdefinitionen verteilt, wo man sich das als Leser mühsam zusammen suchen muss.
Es gibt Konventionen bei Namensschreibweisen, zum Beispiel das man Konstanten komplett in Grossbuchstaben schreibt, die meisten anderen Namen in kleinbuchstaben_mit_unterstrichen, ausser Klassennamen, die MixedCase geschrieben werden. Du mischst kleinbuchstaben_mit_unterstrichen und camelCase scheinbar willkürlich. Genau so wie Deutsch und Englisch bei den Namen. Das sollte man einheitlicher lösen. Bei der Schreibweise hält man sich am besten an den
Style Guide for Python Code.
Ich hatte in einem früheren Beitrag ja schon mal einige schlechte Namen aufgezählt, weil die dem Leser nicht verraten was der Wert im Programm bedeutet. Du hast auch Namen die das fast tun, bei denen man aber im Zweifelsfall doch raten muss, weil sie abgekürzt sind. Abkürzungen sollte man meiden solange sie nicht allgemein bekannt sind. Man macht damit sonst nur das Programm unverständlicher ohne einen wirklichen Mehrwert. Weder ist Speicherplatz knapp, noch kann man sich mit Tipparbeit herausreden, weil heute jeder vernünftige Editor eine Autovervollständigung bietet. Selbst wenn die einfach nur ihre Vorschläge aus den geöffneten Textdateien bezieht, reicht das schon aus.
Die ``global``-Anweisung hat auf Modulebene keinen Effekt!
Funktionen sind üblicherweise nach Tätigkeiten benannt. Und zwar am besten nach den Tätigkeiten die sie vollziehen. Bei `action_get_info_dialog()` und `action_get_help_dialog()` hat das `action_`-Präfix jeweils nichts zu suchen. Und das `get_` ist falsch weil so ziemlich jeder Programmierer aus Erfahrung erwartet das eine so benannte Funktion einen Wert von irgendwo holt und als Ergebnis liefert. Was diese Funktionen tun ist Dialoge anzeigen. `show_info()` oder `show_info_dialog()` währen passende Namen für so eine Funktion. Wobei die Funktionen fast das gleiche machen und sich eigentlich nur durch den Text unterscheiden. Da könnte man die Texte als Konstanten definieren und eine gemeinsame Funktion definieren die dann mit dem entsprechenden Text als Argument aufgerufen wird.
Was soll das Willkommen in `welcome_label` und `welcome2_label` bedeuten?
Als Programmierer versucht man Code- und Datenwiederholungen zu vermeiden. Das macht Programme schwerer wart- und änderbar. Überleg zum Beispiel mal wieviel Arbeit das machen würde Hellblau als Hintergrundfarbe in etwas anderes zu ändern. Du musst überall nach der Zeichenkette suchen und an jeder Stelle schauen ob es sich um die Angabe einer Hintergrundfarbe handelt oder etwas anderes damit bezweckt wird. Wenn man am Anfang des Programms einmal eine Konstante für die Hintergrundfarbe definiert und die dann überall verwendet, kann man die Hintergrundfarbe an dieser einen Stelle verändern, ohne das gesamte Programm durchgehen zu müssen.
Das erstellen des Menüs wird unnötigerweise durch das erstellen von anderen Widgets unterbrochen. Das ist unübersichtlich. Genau wie in Aufsätzen sollte man erst einen Gedanken zuende ausformulieren, bevor man etwas anderes anfängt.
`anzahlFenster` ist ein falscher Name weil der nicht an die Anzahl von Fenstern gebunden wird sondern an eine Zeichenkette die die Anzahl der Noten bedeutet. Allerdings noch nicht zu dem Zeitpunkt wo diese Zuweisung stattfindet, weil der Benutzer da noch gar keine Chance hatte irgend etwas einzugeben. Ausserdem wird der Name nach der Zuweisung nirgendwo verwendet.
Die Anweisung das man die Anzahl der Noten in das Eingabefeld eingeben soll, sollte *sofort* dort stehen. Das weiss sonst kein Mensch der das Programm das erste mal startet oder es sich nicht vom letzten mal her gemerkt hat.
Wenn man das ganze ein bisschen aufräumt und tatsächlich Funktionen so benutzt wie sie gedacht sind, dann kommt man ungefähr bei so etwas heraus:
Code: Alles auswählen
# coding: utf8
from functools import partial
import tkinter as tk
from tkinter import messagebox
BACKGROUND_COLOR = 'lightblue'
INFO_MESSAGE = (
'************************\n'
'Autor: xxxx\n'
'************************'
)
HELP_MESSAGE = (
'************************\n'
'1. Nachdem sie die Anzahl Noten eingegeben haben, drücken sie auf'
' Weiter und füllen sie danach die Felder aus.\n'
'************************'
)
show_info_dialog = partial(messagebox.showinfo, 'Infos')
def calculate_needed_mark(marks, desired_mark):
pass # TODO
def on_marks_entered(window, entry, info_label, button, mark_entries):
try:
desired_mark = float(entry.get())
except ValueError:
info_label['text'] = 'Bitte den gewünschten Schnitt oben eingeben.'
entry.select_range(0, tk.END)
entry.focus()
else:
marks = list()
for mark_entry in mark_entries:
try:
mark = float(mark_entry.get())
except ValueError:
info_label['text'] = 'Bitte alle Noten unten eingeben.'
mark_entry.select_range(0, tk.END)
mark_entry.focus()
return
else:
marks.append(mark)
info_label['text'] = ''
needed_mark = calculate_needed_mark(marks, desired_mark)
result_text = 'Ergebnis ...' # TODO
tk.Label(
window, text=result_text, background=BACKGROUND_COLOR
).pack(side=tk.TOP)
button.config(text='Beenden', command=window.quit)
def on_mark_count_entered(window, entry, info_label, button):
try:
mark_count = int(entry.get())
except ValueError:
entry.select_range(0, tk.END)
entry.focus()
else:
mark_entries = list()
for _ in range(mark_count):
mark_entry = tk.Entry(window)
mark_entry.pack(side=tk.TOP)
mark_entries.append(mark_entry)
entry.delete(0, tk.END)
info_label['text'] = (
'Wunschschnitt (oben) und vorhandene Noten (unten) eingeben'
)
button['command'] = partial(
on_marks_entered, window, entry, info_label, button, mark_entries
)
def main():
root = tk.Tk()
root.configure(background=BACKGROUND_COLOR)
root.title('Notenprogramm')
main_menu = tk.Menu(root)
datei_menu = tk.Menu(main_menu)
datei_menu.add_command(label='Beenden', command=root.quit)
main_menu.add_cascade(label='Datei', menu=datei_menu)
help_menu = tk.Menu(main_menu)
help_menu.add_command(
label='Info', command=partial(show_info_dialog, INFO_MESSAGE)
)
help_menu.add_command(
label='Hilfe', command=partial(show_info_dialog, HELP_MESSAGE)
)
main_menu.add_cascade(label='Hilfe', menu=help_menu)
root.config(menu=main_menu)
entry = tk.Entry(root, borderwidth=5)
entry.pack(side=tk.TOP)
info_label = tk.Label(
root, text='Gib die Anzahl der Noten ein.', background=BACKGROUND_COLOR
)
info_label.pack(side=tk.TOP)
button = tk.Button(root, text='Weiter')
button.pack(side=tk.TOP)
button['command'] = partial(
on_mark_count_entered, root, entry, info_label, button
)
root.mainloop()
if __name__ == '__main__':
main()
Hier muss man `functools.partial()` verwenden um die benötigten Werte in die Funktionen transportieren zu können. Wenn man das verstanden hat, kann man aber auch gleich objektorientiert programmieren (OOP), denn das kommt von der Komplexität im Grunde auf das gleiche hinaus, nur dass das in Python eigentlich der offensichtlichere Weg ist.
Man könnte bei der GUI natürlich noch nachbessern. Beispielsweise bei der Eingabe der Anzahl unsinnige Werte (0, negative, zu grosse) und bei der Eingabe von Noten die Grenzen der üblichen Noten (1-6 oder 0 bis 15) erzwingen. Bei einer OOP-Lösung würde es sich anbieten entsprechende Eingabeelemente mit Bereichsprüfung zu implementieren und die Noteneingaben noch einmal in einem Objekt zu kapseln das alle Eingaben validieren kann.
Die eigentliche Programmlogik fehlt auch noch. Also die im Moment noch leere Funktion, die aus den gegebenen Noten und dem gewünschten Schnitt ausrechnet welche Note man noch benötigt. Das ist eigentlich die Funktion mit der Ich angefangen hätte. Erst einmal muss das was ein Programm leisten soll funktionieren. Dann kann man da eine GUI drauf setzen. Eine GUI mit der man am Ende das gestellte Problem nicht lösen kann, nützt nicht viel. Und normalerweise zählt bei Hausaufgaben eine durchdachte und getestete, und damit funktionierende Programmlogik mehr als eine GUI. Das was Du da bei `benötigteNote` als Rechnung stehen hast ist nicht ganz korrekt (sofern die Variablen die entsprechenden Werte hätten). An der Stelle ist es besonders bei Hausaufgaben nützlich Herleitung der verwendeten Formel zu dokumentieren, damit der Lehrer sehen kann was man sich dabei gedacht hat, und falls es falsch ist, auch eine Idee davon bekommt an welcher Stelle man sich geirrt hat oder ”falsch abgebogen” ist.