MVC mal anders?

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Sophus hat geschrieben:Meinen zweiten Beispiel verstehe ich auch, aber nur solange dies in einem Modul ist. Würde ich das alles auseinander klamüsern, zum Beispiel LisView, Combox und ListView jeweisl ein eigenes Modul (View), dann hätte ich erst einmal drei separate Views. Dann bräuchte ich noch ein Controller und ein Model. Im Model würde ich die Programmlogik packen.
Du hast doch auch so schon *drei* Views! Die UI-Elemente fungieren als View egal, *wo* sie im Code stehen. Das hatte ich Dir oben doch schon gesagt - vergiss mal für einen Augenblick Module und Code-Aufteilung! Ein View bleibt ein View - egal, wo er im Code steht!

Ein ``QListView`` ist doch auch ein View - und der "steht" ja auch in einem anderen als Deinem eigenen Modul in Beispiel 2!

Die Intention von MVC ist letztlich das Erreichen einer losen Kopplung von Komponenten. Dies leitet sich unmittelbar von den grundlegenden OOP Prinzipien ab, wie z.B. dem "Single Responsability Principle" aus SOLID. (Ich denke mal, damit habe ich Dich bereits öfter konfrontiert) Das alles hat letztlich den Sinn, Software besser entwickeln zu können. Testbarkeit, Parallelisierbarkeit der Entwicklung, leichtere Wartung, Wechsel / Austausch von Komponenten (Open Closed Principle), uvm.

Ob man das selber alles so machen will, bleibt einem selber überlassen. IdR. ist es eine gute Idee, *etablierte* Pattern zu übernehmen - das gilt ja allgemein im Leben ;-)

Wie ich schon sagte ist Dein erstes Beispiel gar kein MVC; es fehlt dort schlicht am gemeinsamen Modell. Dein zweites Beispiel zeigt es schon besser: Du hast *ein* Datenmodell, welches in verschiedenen UI-Komponenten dargestellt wird.

Ich würde Dir raten, mal ein simples "Standardmodell" zu nehmen, wie ein Adressbuch, TODO-Liste usw. Diese kannst Du UI-technisch als Liste darstellen, in der lediglich eine *kurze* Information zur Identifikation enthalten ist, wie etwa Vor- und Nachname. Daneben kannst Du dann eine UI-Komponente bestehend aus Labeln und verschiedenen Eingabefeldern basteln, die *alle* Details eines Datensatzes anzeigt. Wählt man in der Liste einen anderen Datensatz, so ändern sich die Details. Editiert man ein Detail (und übernimmt dieses), so ändert sich sofort die Darstellung in der Liste. Schaffe Dir ein Modell, welches die Datensätze nach außen, also für die UI-Komponenten kapselt. Dann integriere dieses Modell in die *beiden* verschiedenen UI-Komponenten. Danach stelle von fixen Demo-Datensätzen auf serialisierte um, die Du aus einer JSON-Datein einliest. Dann stelle das ganze auf eine kleine SQLite-DB um und füge ein ``QtSqlModell`` (sollte so ähnlich heißen!) anstelle des bisherigen ein. Dann baue die Möglichkeit zu filtern und zu sortieren ein (Decorator-Pattern durch Proxy-Klassen). Dabei wirst Du ein Gespühr bekommen, wieso es sinnvoll ist, die Darstellung und Kommunikation bei Aktionen über ein zentrales Modell laufen zu lassen :-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@Hyperion: Deine aufgegebenen Hausaufgaben werde später vielleicht einmal befolgen :-)

Aber damit es etwas konkreter wird habe ich mir mal ein kleines Beispiel zusammen gebastelt:

bmi.py Hauptprogramm - (View1)

Code: Alles auswählen

#!/usr/bin/env python
import sys
from PyQt4.QtGui import QApplication, QVBoxLayout, QPushButton, QLineEdit, QDialog, QCompleter, QStringListModel
from func_bmi import calculate_bmi

# load form_one and form_two
from form_one import ResultOne
from form_two import ResultTwo

class BMICalculator(QDialog):

    def __init__(self, parent=None):
        QDialog.__init__(self, parent)
        
        self.layout = QVBoxLayout(self)

        # Create a widgets and add them to the main layout
        self.lineEdit_height = QLineEdit()
        self.layout.addWidget(self.lineEdit_height)
        self.lineEdit_weight = QLineEdit()
        self.layout.addWidget(self.lineEdit_weight)
        
        self.calculate_button = QPushButton("Calculate", self)
        self.layout.addWidget(self.calculate_button)
        self.close_button = QPushButton("Close", self)
        self.layout.addWidget(self.close_button)

        self.set_action_button()
        self.create_form_one()
        self.create_form_two()

    def set_action_button(self):
        self.calculate_button.clicked.connect(self.on_calculate)
        self.close_button.clicked.connect(self.on_finished)

    def on_finished(self):
        self.close()

    def closeEvent(self, event):
        print "Windows closes"

    def on_calculate(self):
        size = self.lineEdit_height.text()
        weight = self.lineEdit_weight.text()
        try:
            result = str(calculate_bmi(int(size), int(weight)))
        except ZeroDivisionError:
            result = ''
        self.on_model(result)
        
    def on_model(self, data):
        model = QStringListModel(data)
        self.window_one.lineEdit_result_one.setModel(model)
        self.window_two.lineEdit_result_two.setModel(model)
        
    def create_form_one(self):
        self.window_one = ResultOne()
        self.window_one.show()

    def create_form_two(self):
        self.window_two = ResultTwo()
        self.window_two.show()       
 
def main():
    application = QApplication(sys.argv)
    bmi_calculator = BMICalculator()
    bmi_calculator.show()
    sys.exit(application.exec_())
 
 
if __name__ == '__main__':
    main()
form_one.py (View2)

Code: Alles auswählen

#!/usr/bin/env python
import sys
from PyQt4.QtGui import QVBoxLayout, QLineEdit, QDialog

class ResultOne(QDialog):

    def __init__(self, parent=None):
        QDialog.__init__(self, parent)
        
        self.layout = QVBoxLayout(self)

        # Create a widgets and add them to the main layout
        self.lineEdit_result_one = QLineEdit()
        self.layout.addWidget(self.lineEdit_result_one)
form_two.py (View3)

Code: Alles auswählen

#!/usr/bin/env python
import sys
from PyQt4.QtGui import QVBoxLayout, QLineEdit, QDialog

class ResultTwo(QDialog):

    def __init__(self, parent=None):
        QDialog.__init__(self, parent)
        
        self.layout = QVBoxLayout(self)

        # Create a widgets and add them to the main layout
        self.lineEdit_result_two = QLineEdit()
        self.layout.addWidget(self.lineEdit_result_two)
func_bmi.py Programmlogik- (Model)

Code: Alles auswählen

from __future__ import division
from decimal import Decimal

def calculate_bmi(height, weight):
    return (weight / height**2)
Was sehen wir hier? Ich habe hier insgesamt drei Views, ein Hauptfenster, in denen die Größe und das Gewicht eingegeben wird, und dann habe ich zwei weitere Fenster, in denen die Ergebnis gleichermaßen angezeigt werden. Über Sinn und Unsinn wollen wir mal nicht diskutieren. Als nächstes habe ich dann die Programmlogik, in der der BMI-Wert ausgerechtet und anschließend das Ergebnis zurückgegeben werden soll. Hier möchte ich lernen, wie ich die Ergebnisse über das MVC-Konzept verteilen kann. Die beiden anderen Fenster enthalten nur eine QLineEdit, in denen die Ergebnisse angezeigt werden soll.

WICHTIG: Mein Beispiel funktioniert insofern nicht, weil QLineEdit keine setModel()-Methode besitzt.

Aber was ich hier deutlich machen wollte, ist, dass ich nicht weiß, wohin mit dem Controller? Oder übersehe ich hier was? Ist meine Grundidee hier erst einmal richtig bzw. geht meine Idee in die richtige Richtung?
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Hallo Leute,

ist es auch möglich ein MVC zu kreieren und das unabhängig vom QT? Denn ein QStringListModel zum Beispiel ist ja ein QT-Rahmenwerk, richtig? Und ich möchte mich weitestgehend von QT kapseln. Denn auch das Arbeiten mit MySQL-Datenbank bewerkstellige ich auch ohne QT, denn es gibt ja auch dieses QSql soweit ich weiß. Da arbeite ich lieber mit der Bibliothek MySQLdb. Mir geht es am Ende darum, dass ich - angenommen ich entscheide mich später für tkinter - die GUI austauschen kann, ohne dass ich dann das MVC-Konzept neu bearbeiten muss, weil hier dann QT weg fällt. Ist das möglich? Und wenn ja, hat jemand ein kleines Beispiel wie das aussehen würde?

Gruß
Sophus
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

@Sophus: Qt liefert halt schon viel von V und C mit. Wenn man sich von Qt unabhängig machen will, muß man diesen Teil selbst implementieren und eine Abstraktionsschicht einziehen, die dann wieder für Qt oder ein anderes Rahmenwerk konkretiesiert werden muß.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@Sirius3: Gibt es dazu irgendwo Beispiele? Denn ich habe absolut keine Vorstellung von dem. Also wie man ein MVC-Konzept ohne Qt bewerkstelligen kann. Mir gehtes da viel mehr um das Model, denn beim. View (soweit ich esrichtig verstehe) werden die ui-Dateien dynamisch geladen.
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

@Sophus: MVC ist, wie jerch schon schrieb, eine Frage der Architektur. Wenn Du einfach nur linear Daten verarbeitest, ist dies recht einfach: input -> processing -> output. Bei GUI-Programmen (oder Webapplikation) fällt diese Trennung vielen schwierig, bzw. wird erst gar nicht beachtet (z.B. in vielen einfach gestrickten PHP-Programmen, da PHP zur Vermischung der MVC-Komponenten geradezu einlädt).
Einfach gesagt: das Model sind die Daten, die du bearbeitest und ggf. auch wieder speicherst. Das Model gibt auch die Struktur dieser Daten wider. Der Controller ist das, wo du was mit diesen Daten machst - gerne auch 'Business Logik' genannt. Im obigen input -> processing -> output Beispiel wäre dies das processing. Die View ist das, wo du die Daten, ggf. per Controller bearbeitet, dem Anwender darstellst, bzw. präsentierst. Deshalb gerne auch Präsentationsschicht genannt.
Programme nach diesem Schema sind oft leichter zu pflegen, da die einzelnen Komponenten nicht wild miteinander verwoben werden. Also eine Frage der Architektur. Und der Disziplin, diese einzuhalten.
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

@Sophus: Das MVC-Pattern kann man IMO nicht verstehen, wenn man es auf das rein technische daran reduziert. Statt dessen muss man die Motivation dahinter begreifen. Trygve Reenskaug, einer der Erfinder von MVC, hat diese so formuliert:
Trygve Reenskaug hat geschrieben:The essential purpose of MVC is to bridge the gap between the human user's mental model and the digital model that exists in the computer. The ideal MVC solution supports the user illusion of seeing and manipulating the domain information directly. The structure is useful if the user needs to see the same model element simultaneously in different contexts and/or from different viewpoints.
Hier steht mehr dazu: http://heim.ifi.uio.no/~trygver/themes/ ... index.html
In specifications, Murphy's Law supersedes Ohm's.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Hallo kbr und pillmuncher,

trennen tue ich sowieso alles. Ich habe für jede ui-Datei ein Modul, die dafür sorgt, dass diese Dateien dynamisch geladen werden. Dort werden dann auch Einstellungen für das ui-Fenster festgelegt. Und dann habe ich zum Beispiel für das Arbeiten mit MySQL ein extra Modul, worin die Verbindungsfunktion, das Auslesen der Daten, das Speichern der Daten etc abgewickelt wird. Also vermischen werde ich nichts. Das haben mir die Leute, unter anderem pillmuncher, BlackJack, jerch, Hyperion etc mit eingebleut und es sitzt. Aber beim MVC geht es noch einen Schritt weiter, soweit ich es verstanden habe. Das heißt, wenn man mehrere GUI-Fenster offen hat, und man nimmt im einen Fenster eine Veränderung durch, dann werden diese dann auf all die anderen Fenster zeitgleich angezeigt. Dazu habe ich ja ein Beispiel gleich auf der ersten Seite des Thema, gleich im 11. Beitrag. Aber dort hängt alles vom Qt-Rahmenwerk ab, und ich fragte mich, ob das auch ohne Qt-Rahmenwerk möglich ist, und dabei konzentriere ich mich auf den Model.

Code: Alles auswählen

from PyQt4 import QtGui, QtCore, uic
import sys
 
if __name__ == '__main__':
 
    app = QtGui.QApplication(sys.argv)
    app.setStyle("cleanlooks")
   
    #DATA
    data = QtCore.QStringList()
    data << "one" << "two" << "three" << "four" << "five"
 
    listView = QtGui.QListView()    
    listView.show()
 
    model = QtGui.QStringListModel(data)
   
    listView.setModel(model)
   
   
    combobox = QtGui.QComboBox()
    combobox.setModel(model)
    combobox.show()
   
    listView2 = QtGui.QListView()
    listView2.show()
    listView2.setModel(model)
   
 
    sys.exit(app.exec_())
Wir sehen hier also, dass das Model eine QStringListModel-Klasse ist, also eine Klasse vom Qt. Was ist aber, wenn ich mich später mit tkinter auseinander setzen sollte? Dann muss der ganze Model wieder überarbeitet werden. Ich weiß nicht wie man sowas unter tkinter bewerkstelligt, jedoch denke ich, ist es wieder ganz anders. Und da sehe ich einfach eine Abhängigkeit. Und das ist eben, was mich ein Bisschen wurmt.
Benutzeravatar
MagBen
User
Beiträge: 799
Registriert: Freitag 6. Juni 2014, 05:56
Wohnort: Bremen
Kontaktdaten:

pillmuncher hat geschrieben:Hier steht mehr dazu: http://heim.ifi.uio.no/~trygver/themes/ ... index.html
Der Link ist gut.

Ich denke für ein sinnvolles MVC-Beispiel braucht es etwas mehr, als 5 Strings ohne wesentliche Interaktionsmöglichkeit darzustellen.

Bei einem guten MVC-Beispiel sollte das Model
  • nicht trivial sein,
  • es mehr als eine View geben,
  • der Benutzer sollte was machen können
  • und trotzdem sollte das ganze nicht zu komplex sein.
Als ich vor ein paar Jahren hin und wieder als Java-Trainer gearbeitet habe, habe ich am letzten Tag eines 5-Tägigen Java-Swing Kurses mit den Teilnehmern MVC anhand eines Dateibrowsers behandelt:
Die Verzeichnisse und die Dateien sind das Model. Es gab drei Views: die Verzeichnisstruktur als Baum, den Inhalt eines Verzeichnisses als Tabelle und die Properties einer Datei in einem Formular. Wenn man im Baum herumgeklickt hat, dann hat die Tabelle das aktuelle Verzeichnis angezeigt und wenn man in der Tabelle herumgeklickt hat, dann hat das Formular die Properties der aktuellen Datei angezeigt.

Anhand so eines Beispiels wird die Funktionsweise von MVC und die Vorteile von Qt deutlich (im Java-Kurs natürlich nicht Qt, sondern Swing). Insbesondere was ein Model ist und was nicht:
Wenn der Inhalt eines Verzeichnisses als Tabelle dargestellt wird, dann ist die Tabelle die View. Wer mit Qt arbeitet, der entwickelt aber keine Tabellen, die gibt es schon als Klasse, sondern man implementiert die Ansicht seiner Daten (dem Model), indem man von QAbstractTableModel ableitet. Für Qt ist das ein Model für die Dateibrowser-Anwendung ist das aber Teil der View. Denn eine MVC Anwendung hat zwar in der Regel mehrere Views, auch der Controller kann auf mehrere Teile aufgteilt werden, aber es gibt nur ein Model. Für die Baumansicht der Verzeichnisstruktur wird von QAbstractItemModel abgeleitet, auch das ist für Qt ein Model, für die Dateibrowser-Anwendung gehört es aber zur View.
a fool with a tool is still a fool, www.magben.de, YouTube
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

So wie MagBen es schildert, so hatte ich das MVC-Konzept auch verstanden. In einem Fenster wird etwas verändert, und dies wird dann auf all die anderen Fenster übertragen, und das mittels Model. Aber diese Konstellation habe ich momentan nicht.

Ich habe ein Fenster, auf dem Combobox, lineEdit, Button und eine treeWidget vorhanden sind. Bei einer Suche werden dann entsprechende Daten ausgegeben. Mit einem Doppelklick öffnet sich ein weiters Fenster, in denen detaillierte Informationen über einen bestimmten Datensatz angezeigt werden. Man kann über das Fenster bestimmte Detail-Informationen ändern. Um es konkret zu machen, kann ich hier das Beispiel mit der Film-Datenbank angeben. Über das treeWidget werden Filmtitel ausgegeben. Wenn ich im anderen Fenster einen Filmtitel ändere, dann kann ich diese Aufgabe auch mittel pyqtSignals arbeiten und brauche dazu kein MVC-Konzept oder?
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@Sophus: Das Modell brauchst du eigentlich nur, wenn du mit strukturierten Daten hantierst (oft in Form von Datenbanktabellen). Falls aber lediglich ein bißchen Text in 2 verschiedenen Fenstern erscheinen soll oder eben Änderungen am Text zwischen den Fenstern übernommen werden sollen, dann geht das in der Tat auch mit Signals. Dann wäre MVC sogar totaler Overkill, sofern es wirklich nur um ein einzelnes Textfeld geht.

Aber da du ja was von einer Filmdatenbank schreibst, ist MVC eigentlich schon ganz sinnvoll. Die Änderung des Filmtitels wird dann halt direkt ins Modell geschrieben. Anschließend wird dein View benachrichtigt (über ein Signal), dass es Änderungen am Modell gab. Der View würde daher seine Anzeige aktualisieren. Das wäre MVC-typisches Verhalten.
Antworten