Hallo Zusammen,
ich bin Progammiersprachentechnisch komplett blutiger Anfänger. Nun müssen wir in der Uni ein Programmierprojekt realisieren. Die Anforderung von meinem sieht folgendermaßen aus:
Erstellen einer graphischen Benutzeroberfläche für ein Wunschnotendurchschnittsprogramm, welches die folgenden Anforderungen erfüllt:
- Ich kann meine Schulnoten eingeben und es rechnet meinen Notendurschnitt aus und vergleicht ihn mit meinem Wunschnotendurschnitt
- Anzahl Noten und die einzelnen Noten müssen von Hand eingegeben werden.
- Notenschnitt wird per Knopfdruck ausgerechnet.
- Falls sich der Benutzer nicht sinnvoll verhalten sollte ( ungültige Note, zu frühes Klicken) wird der Benutzer auf einen Fehler aufmerksam gemacht.
- Erweitert werden kann das Programm durch abspeicherung der Noten pro Semester in einem Ordner, dort kann man diese dann immer aufrufen.
Erwartet Ergebnisse:
- GUI mit möglicher Noteneingabe um zu berechnen, welche Noten dass ich haben muss um auf meinen Wunschnotenschnitt zu kommen
- Zusätzlich: Abspeicherung vergangener Semester in einem Ordner, welcher aufrufbar ist über das GUI
- Zusätzlich: Datenpersistenz
Herausforderung:
- Richtige Libary finden
- Progammbausteine entwickeln bzw. entwickeln
- GUI erstellen, welches funktioniert
- Abspeicherung der Daten in einen Ordner
- Zusätzlich: Datenpersistenz
Ich habe nun mit dem tkinter Modul ein Modul gefunden mit welchem ich eine GUI erstellen kann, ebenfalls weiß ich mathematisch, wie man einen Notendurchschnitt berechnen.
Die Problematik meinerseits ist nun, dass ich nicht weiß wie ich beide Sachen kombinieren kann.
Könnt ihr mir eventuell bei einem Ansatz meines Problems weiterhelfen. Ich wäre echt froh drum.
Schöne Grüße, M
Wunschnotendurchschnitt mit GUI
Falls jemand diesem Beitrag folgt hier wäre noch eine alternative zu Tkinter:
import time
from PyQt5 import QtCore as qtc
from PyQt5 import QtGui as qtg
from PyQt5 import QtWidgets as qtw
class Box:
def __init__( self, value, type, labeltext="", dimtext=""):
"""
Usage:
parameter = Box( 42, float, "Geschwindigkeit", "m/s")
"""
self.__typecast = type
self.__value = self.__typecast(value) # Aus Integer wird ein Float gemacht, ansonsten könnte der Wert nicht verarbeitet werden
self.__labeltext = labeltext
self.__dimtext = dimtext
self.__callbacks = []
return
def dimtext( self, neuer_dimtext= None):
"""
Usage:
text = irgendwas.dimtext() # Lesen
irgendwas.dimtext("Angstrom/Woche") # Schreiben
"""
if neuer_dimtext is None:
return self.__dimtext
self.__dimtext = neuer_dimtext
return self
#if arg is None:
# return self.__dimtext
#self.__dimtext = arg
#return self
def labeltext( self, arg = None):
if arg is None:
return self.__labeltext
self.__labeltext = arg
return self
def labeltext( self, arg = None):
if arg is None:
return self.__labeltext
self.__labeltext = arg
return self
def reqS_on_value_was_modified( self, callback: callable):
self.__callbacks.append( callback)
def value( self, new_value = None):
if new_value is None:
return self.__value
self.__value = self.__typecast( new_value)
self._on_value_was_modified_() # Das ist das "Signal"
return self
def _on_value_was_modified_( self):
for callback in self.__callbacks:
callback( self)
return( self)
###############################################################################
class Dataview( qtw.QWidget):
def __init__( self, databox: Box):
super().__init__() # Super = Das Darüberliegende -> wird das QWidget wird aufgerufen
self.__databox = databox
# Unser Backend
self.__databox.reqS_on_value_was_modified( self._on_databox_changed_)
self.__is_update_needed = True
# Teile des Widgets
self.__labelwidget = qtw.QLabel( self.__databox.labeltext())
self.__valuewidget = qtw.QLineEdit( str( self.__databox.value()))
self.__valuewidget.setReadOnly( True)
self.__valuewidget.setAlignment( qtc.Qt.AlignCenter) # Zahlenwerte werden Zentral angezeigt
self.__dimwidget = qtw.QLabel( self.__databox.dimtext())
# Das Layout
lout = qtw.QHBoxLayout() # Horizontale Box
lout.addWidget( self.__labelwidget)
lout.addWidget( self.__valuewidget)
lout.addWidget( self.__dimwidget)
self.setLayout( lout)
# Der Timer, der das Update macht, wenn das Dirty-Flag "needs_be_updated" gesetzt ist
self.__updatetimer = qtc.QTimer()
self.__updatetimer.setInterval( 500)
self.__updatetimer.timeout.connect( self._on_updatetimer_has_elapsed_)
self.__updatetimer.start()
def _on_databox_changed_( self, *args):
self.__is_update_needed = True
"""
Wert in Box hat sichgeändert.
Wir haben nun 2 Möglichkeiten: Falg setzen, damit zeitgesteuert ein Update
erfolgt oder Update gleich durchführen
"""
#self._update_()
return
def _on_updatetimer_has_elapsed_( self, *args):
if self.__is_update_needed:
self._update_()
self.__is_update_needed = False
return
def _update_( self):
self.__valuewidget.setText( str( self.__databox.value()))
return
###############################################################################
class Mainwindow( qtw.QMainWindow):
def __init__( self):
super().__init__()
# Wir definieren ein vertikales Layout
mlout = qtw.QVBoxLayout()
# Eine Box sie alle zu knechten - Sie erinnern sich
self.__box_velocity = Box( 42, float, "Geschwindigkeit", "km/h")
# Das Frontend zur Box
self.__dataviews_tab_1 = [\
Dataview( self.__box_velocity)
]
self.__box_acceleration = Box( 4.2, float, "Beschleunigung", "m/s^2")
self.__dataviews_tab_2 = [\
Dataview( self.__box_acceleration)
]
# Nun erzeugen wir die Tabs
tabs = qtw.QTabWidget()
tabs.setTabPosition( qtw.QTabWidget.East)
tabs.setMovable( True)
mlout.addWidget( tabs)
# Tab 1
lout = qtw.QVBoxLayout()
for dataview in self.__dataviews_tab_1:
lout.addWidget( dataview)
tab_1 = qtw.QWidget()
tab_1.setLayout( lout)
tabs.addTab( tab_1, "Registerzunge 1")
# Tab 2
lout = qtw.QVBoxLayout()
for dataview in self.__dataviews_tab_2:
lout.addWidget( dataview)
tab_2 = qtw.QWidget()
tab_2.setLayout( lout)
tabs.addTab( tab_2, "Tab 2")
# Main Widged
mainW = qtw.QWidget()
mainW.setLayout( mlout)
self.setCentralWidget( mainW)
# Ein Exit Button unter die Tabs wäre noch fein
exitbutton = qtw.QPushButton( text = "Close")
exitbutton.clicked.connect( self._on_exitbutton_)
mlout.addWidget( exitbutton)
return
def _on_exitbutton_( self, *args):
self.close()
return
app = qtw.QApplication( [])
mainwindow = Mainwindow()
mainwindow.show()
app.exec_()
import time
from PyQt5 import QtCore as qtc
from PyQt5 import QtGui as qtg
from PyQt5 import QtWidgets as qtw
class Box:
def __init__( self, value, type, labeltext="", dimtext=""):
"""
Usage:
parameter = Box( 42, float, "Geschwindigkeit", "m/s")
"""
self.__typecast = type
self.__value = self.__typecast(value) # Aus Integer wird ein Float gemacht, ansonsten könnte der Wert nicht verarbeitet werden
self.__labeltext = labeltext
self.__dimtext = dimtext
self.__callbacks = []
return
def dimtext( self, neuer_dimtext= None):
"""
Usage:
text = irgendwas.dimtext() # Lesen
irgendwas.dimtext("Angstrom/Woche") # Schreiben
"""
if neuer_dimtext is None:
return self.__dimtext
self.__dimtext = neuer_dimtext
return self
#if arg is None:
# return self.__dimtext
#self.__dimtext = arg
#return self
def labeltext( self, arg = None):
if arg is None:
return self.__labeltext
self.__labeltext = arg
return self
def labeltext( self, arg = None):
if arg is None:
return self.__labeltext
self.__labeltext = arg
return self
def reqS_on_value_was_modified( self, callback: callable):
self.__callbacks.append( callback)
def value( self, new_value = None):
if new_value is None:
return self.__value
self.__value = self.__typecast( new_value)
self._on_value_was_modified_() # Das ist das "Signal"
return self
def _on_value_was_modified_( self):
for callback in self.__callbacks:
callback( self)
return( self)
###############################################################################
class Dataview( qtw.QWidget):
def __init__( self, databox: Box):
super().__init__() # Super = Das Darüberliegende -> wird das QWidget wird aufgerufen
self.__databox = databox
# Unser Backend
self.__databox.reqS_on_value_was_modified( self._on_databox_changed_)
self.__is_update_needed = True
# Teile des Widgets
self.__labelwidget = qtw.QLabel( self.__databox.labeltext())
self.__valuewidget = qtw.QLineEdit( str( self.__databox.value()))
self.__valuewidget.setReadOnly( True)
self.__valuewidget.setAlignment( qtc.Qt.AlignCenter) # Zahlenwerte werden Zentral angezeigt
self.__dimwidget = qtw.QLabel( self.__databox.dimtext())
# Das Layout
lout = qtw.QHBoxLayout() # Horizontale Box
lout.addWidget( self.__labelwidget)
lout.addWidget( self.__valuewidget)
lout.addWidget( self.__dimwidget)
self.setLayout( lout)
# Der Timer, der das Update macht, wenn das Dirty-Flag "needs_be_updated" gesetzt ist
self.__updatetimer = qtc.QTimer()
self.__updatetimer.setInterval( 500)
self.__updatetimer.timeout.connect( self._on_updatetimer_has_elapsed_)
self.__updatetimer.start()
def _on_databox_changed_( self, *args):
self.__is_update_needed = True
"""
Wert in Box hat sichgeändert.
Wir haben nun 2 Möglichkeiten: Falg setzen, damit zeitgesteuert ein Update
erfolgt oder Update gleich durchführen
"""
#self._update_()
return
def _on_updatetimer_has_elapsed_( self, *args):
if self.__is_update_needed:
self._update_()
self.__is_update_needed = False
return
def _update_( self):
self.__valuewidget.setText( str( self.__databox.value()))
return
###############################################################################
class Mainwindow( qtw.QMainWindow):
def __init__( self):
super().__init__()
# Wir definieren ein vertikales Layout
mlout = qtw.QVBoxLayout()
# Eine Box sie alle zu knechten - Sie erinnern sich
self.__box_velocity = Box( 42, float, "Geschwindigkeit", "km/h")
# Das Frontend zur Box
self.__dataviews_tab_1 = [\
Dataview( self.__box_velocity)
]
self.__box_acceleration = Box( 4.2, float, "Beschleunigung", "m/s^2")
self.__dataviews_tab_2 = [\
Dataview( self.__box_acceleration)
]
# Nun erzeugen wir die Tabs
tabs = qtw.QTabWidget()
tabs.setTabPosition( qtw.QTabWidget.East)
tabs.setMovable( True)
mlout.addWidget( tabs)
# Tab 1
lout = qtw.QVBoxLayout()
for dataview in self.__dataviews_tab_1:
lout.addWidget( dataview)
tab_1 = qtw.QWidget()
tab_1.setLayout( lout)
tabs.addTab( tab_1, "Registerzunge 1")
# Tab 2
lout = qtw.QVBoxLayout()
for dataview in self.__dataviews_tab_2:
lout.addWidget( dataview)
tab_2 = qtw.QWidget()
tab_2.setLayout( lout)
tabs.addTab( tab_2, "Tab 2")
# Main Widged
mainW = qtw.QWidget()
mainW.setLayout( mlout)
self.setCentralWidget( mainW)
# Ein Exit Button unter die Tabs wäre noch fein
exitbutton = qtw.QPushButton( text = "Close")
exitbutton.clicked.connect( self._on_exitbutton_)
mlout.addWidget( exitbutton)
return
def _on_exitbutton_( self, *args):
self.close()
return
app = qtw.QApplication( [])
mainwindow = Mainwindow()
mainwindow.show()
app.exec_()
- pillmuncher
- User
- Beiträge: 1484
- Registriert: Samstag 21. März 2009, 22:59
- Wohnort: Pfaffenwinkel
@Matze162: Was sollen die doppelten führenden Unterstriche bei den Attributen bedeuten? Hast du das hier in der offiziellen Dokumentation gelesen? Hast du es verstanden?
In specifications, Murphy's Law supersedes Ohm's.
- __blackjack__
- User
- Beiträge: 13077
- Registriert: Samstag 2. Juni 2018, 10:21
- Wohnort: 127.0.0.1
- Kontaktdaten:
@Matze162: `time` und `QtGui` werden importiert aber nicht verwendet. Man importiert auch keine Module unter kryptischen Abkürzungen. Es gibt da ein paar prominente Ausnahmen, bei Qt ist das aber nicht normal.
Da sind nach vielen öffnenden Klammern für Aufrufe und Funktionssignaturen Leerzeichen die da nicht hingehören.
Es gibt ein paar unnötige ``return``-Anweisungen und welche die das Objekt selbst zurückgeben, nachdem das Objekt verändert wurde. Das macht man in Python nicht.
Doppelte führende Unterstriche bedeuten *nicht* ”private”. Implementierungsdetails werden durch *einen* führenden Unterstrich gekennzeichnet.
Besonders sinnbefreit ist so ein ”private”-Versuch wenn dann Methoden zum setzen und abfragen bereitgestellt werden, die das letztlich dann doch wieder öffentlich zugreifbar machen. Viel sinnlose Tipparbeit.
Für Attributzugriffe bei denen noch zusätzlich etwas passieren soll, gibt es Properties.
Eine Funktion/Methode sollte *eine* Sache machen. Nicht je nach Argument(en) zwei unterschiedliche Aufgaben. Das betrifft beispielsweise die unsinnigen Getter/Setter die die Attribute doch wieder öffentlich zugreifbar machen.
Der Name `typecast` kommt eher auch einer anderen Sprache.
Keine Abkürzungen. Was ist ein `dimtext`? Text der etwas dunkler ist? Wenn da `dimension` gemeint ist, sollte das da auch so stehen. `lout` um `ay` in `layout` zu sparen?
Warum gibt es Methoden mit einem führenden und einem folgenden Unterstrich? Das ist keine bekannte Konvention.
Man muss nicht jedes Zwischenergebnis an Namen binden und schon gar nicht an das Objekt.
Das mit dem QTimer erst ein Flag zu setzen um darauf dann über einen Timer der ständig läuft die GUI zu aktualisieren macht keinen Sinn wenn man an der Stelle direkt aktualisieren kann.
Zeichenketten sind keine Kommentare. Die haben an bestimmten Stellen eine Bedeutung als Docstrings. Einmal direkt in der Sprache, und an anderen Stellen zusätzlich durch Werkzeuge um Dokumentation zu generieren. Kommentare dagegen fangen immer mit einem # an.
Die \ zur Zeilenfortsetzung innerhalb einer literalen Liste sind überflüssig. Der Compiler ist clever genug zu wissen, dass ein Ausdruck noch nicht zuende sein kann, wenn noch schliessende Klammern fehlen.
Die Methode die nur `self.close()` aufruft kann man sich sparen, denn da kann man ja direkt `self.close` als Rückruf registrieren.
Das Hauptprogramm gehört nicht auf Modulebene. Üblicherweise steht das in einer `main()`-Funktion die auch nur ausgeführt wird, wenn man das Modul als Programm ausführt und *nicht* wenn man es importiert.
Da sind nach vielen öffnenden Klammern für Aufrufe und Funktionssignaturen Leerzeichen die da nicht hingehören.
Es gibt ein paar unnötige ``return``-Anweisungen und welche die das Objekt selbst zurückgeben, nachdem das Objekt verändert wurde. Das macht man in Python nicht.
Doppelte führende Unterstriche bedeuten *nicht* ”private”. Implementierungsdetails werden durch *einen* führenden Unterstrich gekennzeichnet.
Besonders sinnbefreit ist so ein ”private”-Versuch wenn dann Methoden zum setzen und abfragen bereitgestellt werden, die das letztlich dann doch wieder öffentlich zugreifbar machen. Viel sinnlose Tipparbeit.
Für Attributzugriffe bei denen noch zusätzlich etwas passieren soll, gibt es Properties.
Eine Funktion/Methode sollte *eine* Sache machen. Nicht je nach Argument(en) zwei unterschiedliche Aufgaben. Das betrifft beispielsweise die unsinnigen Getter/Setter die die Attribute doch wieder öffentlich zugreifbar machen.
Der Name `typecast` kommt eher auch einer anderen Sprache.
Keine Abkürzungen. Was ist ein `dimtext`? Text der etwas dunkler ist? Wenn da `dimension` gemeint ist, sollte das da auch so stehen. `lout` um `ay` in `layout` zu sparen?
Warum gibt es Methoden mit einem führenden und einem folgenden Unterstrich? Das ist keine bekannte Konvention.
Man muss nicht jedes Zwischenergebnis an Namen binden und schon gar nicht an das Objekt.
Das mit dem QTimer erst ein Flag zu setzen um darauf dann über einen Timer der ständig läuft die GUI zu aktualisieren macht keinen Sinn wenn man an der Stelle direkt aktualisieren kann.
Zeichenketten sind keine Kommentare. Die haben an bestimmten Stellen eine Bedeutung als Docstrings. Einmal direkt in der Sprache, und an anderen Stellen zusätzlich durch Werkzeuge um Dokumentation zu generieren. Kommentare dagegen fangen immer mit einem # an.
Die \ zur Zeilenfortsetzung innerhalb einer literalen Liste sind überflüssig. Der Compiler ist clever genug zu wissen, dass ein Ausdruck noch nicht zuende sein kann, wenn noch schliessende Klammern fehlen.
Die Methode die nur `self.close()` aufruft kann man sich sparen, denn da kann man ja direkt `self.close` als Rückruf registrieren.
Das Hauptprogramm gehört nicht auf Modulebene. Üblicherweise steht das in einer `main()`-Funktion die auch nur ausgeführt wird, wenn man das Modul als Programm ausführt und *nicht* wenn man es importiert.
Code: Alles auswählen
#!/usr/bin/env python3
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (
QApplication,
QHBoxLayout,
QLabel,
QLineEdit,
QMainWindow,
QPushButton,
QTabWidget,
QVBoxLayout,
QWidget,
)
class Box:
def __init__(self, value, convert, label_text="", dimension_text=""):
"""
Usage:
parameter = Box(42, float, "Geschwindigkeit", "m/s")
"""
self._convert = convert
self._value = self._convert(value)
self.label_text = label_text
self.dimension_text = dimension_text
self._callbacks = []
@property
def value(self):
return self._value
@value.setter
def value(self, value):
self._value = self._convert(value)
for callback in self._callbacks:
callback(self)
def add_on_change_callback(self, callback):
self._callbacks.append(callback)
class Dataview(QWidget):
def __init__(self, databox):
super().__init__()
self._databox = databox
self._databox.add_on_change_callback(self._update)
layout = QHBoxLayout()
layout.addWidget(QLabel(self._databox.label_text))
self._value_edit = QLineEdit(
str(self._databox.value), readOnly=True, alignment=Qt.AlignCenter
)
layout.addWidget(self._value_edit)
layout.addWidget(QLabel(self._databox.dimension_text))
self.setLayout(layout)
def _update(self):
self._value_edit.setText(str(self._databox.value))
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
main_layout = QVBoxLayout()
tabs = QTabWidget(tabPosition=QTabWidget.East, movable=True)
main_layout.addWidget(tabs)
for tab_text, dataviews in [
(
"Registerzunge 1",
[Dataview(Box(42, float, "Geschwindigkeit", "km/h"))],
),
("Tab 2", [Dataview(Box(4.2, float, "Beschleunigung", "m/s^2"))]),
]:
layout = QVBoxLayout()
for dataview in dataviews:
layout.addWidget(dataview)
tab = QWidget()
tab.setLayout(layout)
tabs.addTab(tab, tab_text)
main_layout.addWidget(QPushButton("Close", clicked=self.close))
widget = QWidget()
widget.setLayout(main_layout)
self.setCentralWidget(widget)
def main():
app = QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
app.exec_()
if __name__ == "__main__":
main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman