Wunschnotendurchschnitt mit GUI

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Matze162
User
Beiträge: 2
Registriert: Mittwoch 8. Dezember 2021, 10:06

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
Sirius3
User
Beiträge: 17738
Registriert: Sonntag 21. Oktober 2012, 17:20

Als erstes mußt Du lernen, wie man ein sauberes GUI-Programm schreibt, dafür gibt es genug schlechte Beispiele im Netz und die verbesserten Versionen davon hier im Forum in etlichen Beiträgen. Einfach mal stöbern.
Matze162
User
Beiträge: 2
Registriert: Mittwoch 8. Dezember 2021, 10:06

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_()
Benutzeravatar
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.
Benutzeravatar
__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.

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
Antworten