GUI-Anwendung / MVC / Zusammenspiel

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
CasualCoding.org

Hallo Forum!

Eines meiner Hauptprobleme beim Programmierenlernen war bisher bei jedem Anlauf, dass ich bei GUI-Programmierung kläglich gescheitert bin, weil mir das Zusammenspiel zwischen GUI und Logik einfach nicht klar werden wollte. Entsprechende Toolkit-Dokus haben da für meinen Geschmack auch immer einen Schritt übersprungen, während die Lehrbücher einen Schritt zu früh aufgehört haben.

Ich habe mir nun das PyQt-Buch von Peter Bouda gekauft, und seit ich damit arbeite, ist mir vieles klarer geworden.

Jetzt arbeite ich an einem ersten Projekt und habe doch noch eine Frage.

Das Projekt wird ein (erstmal) sehr einfaches Hangman. Aufteilung habe ich so vorgenommen: Eine Klasse für das eigentliche Ratewort, wie bereits in einem älteren Thread von mir diskutiert. Eine Klasse für die GUI, die sich allein um das Programmfenster und das Event-Handling kümmert. Eine Controller-Klasse, die als Verbindung zwischen GUI und Ratewort-Klasse dienen soll.

Mir ist jetzt ein bisschen unklar, welche Verbindung am günstigsten ist... zwei Ideen:

1. Ich gebe der GUI-Klasse ein Controller-Objekt und ein Ratewort-Objekt als Attribut, also

Code: Alles auswählen

self.controller = Controller()
self.wort = Wort()
Vorteil: Ich kann in den PyQt-Slots direkt mit den Objekten arbeiten. Nachteil: Irgendwie findet jetzt doch alles in der GUI-Klasse statt -> Übersichtlichkeit?

2. Ich gebe der Controller-Klasse die GUI und das Wort als Attribute, also

Code: Alles auswählen

self.fenster = GUI()
self.wort = Wort()
Vorteil: GUI komplett getrennt. Problem: Wie kann ich in den PyQt-Slots nun auf die Logik zugreifen? Einfach bestimmte Werte zurückgeben, die ich in der Controller-Klasse dann abfrage und entsprechend reagiere?

Ist mein Problem verständlich? Wie würdet ihr sowas angehen / lösen?
BlackJack

@SolitaryMan: Ich persönlich würde den Controller erst einmal weg lassen bis ich diese Abstraktion tatsächlich *brauche*, Also wenn Controller und/oder Views austauschbar sein müssen. Und einen Controller würde ich nicht einfach nur `Controller` nennen.

Und ob die GUI jetzt das *Wort* einzeln kennen muss? Zur Programmlogik von Hangman und zum Zustand gehört ja mindestens noch wie viele Versuche es gesamt gibt, und welches der aktuelle ist. Fang doch erst einmal mit einer Klasse `Hangman` an die tatsächlich die gesamte Spiellogik enthält und die man im ”Einzelschritt” spielen kann. Also wo die Ereignisschleife die das ganze antreibt ausserhalb der Klasse angesiedelt ist.
CasualCoding.org

@Blackjack - erstmal danke für deine Antwort.

Die Klassen heißen bei mir natürlich anders, ich habe das hier nur mal so aufgeführt, damit klar wird, was ich meine. Das war kein Ausschnitt aus meinem Code, sondern nur ein Beispiel, um die Zusammenhänge zu verdeutlichen.

Ein so kleines Programm benötigt sicherlich kein striktes MVC (wobei ich einiges an Features im Kopf habe und das ganze Projekt schon noch größer wird). Mir ging es aber gerade darum, nicht erstmal irgendwas zusammen zu schustern, sondern mal ein grundlegendes Verständnis dafür zu bekommen, wie die Komponenten in einer GUI-Anwendung am sinnvollsten zusammengesetzt werden.

Ich habe ja mit deiner Hilfe im letzten Jahr schonmal eine spielbare Konsolenversion hinbekommen und mich dabei an deinem Vorschlag für die Wort-Klasse orientiert. Darauf wollte ich nun aufbauen und daraus ein möglichst sauberes GUI-Projekt bauen. Sicherlich auch mit dem Ziel, das ganze zu veröffentlichen, aber vorrangig erstmal mit dem Ziel, für mich selbst ein lauffähiges und sauberes Beispiel für ein GUI-Programm zu haben.

Die konkrete Aufteilung bisher ist:

1. Die Wortklasse, die das Lösungswort hält, ein Set mit den Buchstaben des Lösungswortes, und ein Set mit den bereits geratenen Buchstaben. Außerdem eine Methode, um einen bestimmten Buchstaben auf Vorkommen im Lösungswort zu prüfen und eine Methode, um einen String mit den bereits geratenen Buchstaben zurückzugeben.

2. Die GUI-Klasse, die ein Label für die Anzeige des aktuellen Standes des Lösungswortes anzeigt (Bsp.: "**ES***S**R*" für "LOESUNGSWORT"), Buttons für die Buchstaben und einen Button zum direkten Lösen.

3. Die Controller-Klasse, die mit den in der GUI ausgelösten Signals auf der Wortklasse arbeiten soll und die Fehlversuche und ähnliches hält.

Die Hauptfrage für mich ist jetzt, wie das Zusammenfügen dieser drei Teile am saubersten und sinnvollsten ist. Welche Klasse hat welche andere(n) Klasse(n) als Attribut? Wo operiert welches Objekt am sinnvollsten auf welchen anderen Objekten?
Benutzeravatar
MagBen
User
Beiträge: 799
Registriert: Freitag 6. Juni 2014, 05:56
Wohnort: Bremen
Kontaktdaten:

Folgendes Konzept hat sich in meinen Qt-Projekten bewährt. Der Trick ist, Du hast nicht einen Controller, sondern zwei.

Model:
Das Model kennt weder den Controller noch die View.
Wenn die Daten des Models geändert werden, dann sendet das Model ein Signal (z.B. dataChanged).

View:
Die View hat eine Referenz auf das Model. Die View greift nur lesend auf das Model zu. Die View hat einen Slot der mit dem dataChanged Signal des Models verbunden werden soll. In diesem Slot aktualisiert die View die angezeigten Daten.
Wenn der Benutzer irgendwas tut (z.B. Knopf drücken oder Texteingabe), dann sendet die View jeweils ein Signal.

Laufzeit-Controller:
Der Controller hat eine Referenz auf das Model.
Der Controller hat Slots die mit den Signalen der View verbunden werden. In diesen Slots werden Änderungen an den Daten des Models gemacht.

Startup-Controller:
Der Startup-Controller hat die main-Funktion und erstellt das QApplication Objekt und startet die Qt Event-Queue.
Der Startup-Controller erstellt das Model, die View und den Laufzeit-Controller und verbindet die Signale mit den passenden Slots.

Test ob das Design funktioniert:
Erstelle eine zweite View und lasse beide Views gleichzeitig erscheinen, ohne dass die erste View, das Model oder der Laufzeit-Controller geändert werden müssen. Der Benutzer kann nun abwechselnd und völlig durcheinander mal in dem einen Fenster und mal in dem anderen Fenster was machen, trotzdem sind immer beide Fenster korrekt.
a fool with a tool is still a fool, www.magben.de, YouTube
CasualCoding.org

Hallo MagBen, auch dir ein herzliches 'Dankeschön'.

Wie sieht das Senden und Empfangen von Signalen zwischen Model, View und Controller in der Praxis aus? Machst du das mit irgendwelchen Methoden, die Werte zurückgeben und in der Empfänger-Klasse aufgefangen werden? Oder läuft das über das Definieren von eigenen Signals und Slots (das Thema habe ich in meinem Buch noch nicht angefangen, weil ich dachte, das würde ich am Anfang noch nicht brauchen - ich kenne bisher nur vordefinierte und klasseninterne)?
Benutzeravatar
MagBen
User
Beiträge: 799
Registriert: Freitag 6. Juni 2014, 05:56
Wohnort: Bremen
Kontaktdaten:

SolitaryMan hat geschrieben:Wie sieht das Senden und Empfangen von Signalen zwischen Model, View und Controller in der Praxis aus?
Qt Signals und Slots. Wobei in PyQt (im Gegensatz zu C++ Qt) Slots und Signals nicht extra deklariert werden müssen. Alles was von QObject abgeleitet ist, kann mit der Methode emit Signals senden und mit der Methode connect von QObject kannst Du beliebige Methoden von beliebigen Objekten als Slots benutzen. Es müssen lediglich die Signaturen der Signals und der Slots zueinander passen.

z.B. so:

Code: Alles auswählen

import sys
from PyQt4.QtCore import SIGNAL, QObject, QTimer, QCoreApplication

class A(QObject):
    def tuWas(self):
        print("A: tu was")
        self.emit(SIGNAL("tuWas"))

class B:
    def machJaSchon(self):
        print("B: bitte sehr")

app = QCoreApplication(sys.argv)

a = A()
b = B()
QObject.connect(a,SIGNAL("tuWas"), b.machJaSchon)

QTimer.singleShot(100, a.tuWas)
QTimer.singleShot(1000, sys.exit)

app.exec_()
a fool with a tool is still a fool, www.magben.de, YouTube
CasualCoding.org

Super - das ist genau das, was ich gesucht habe! Und ein feiner, sauberer Aufbau obendrein, gefällt mir so echt gut.

Jetzt werde ich aber flugs das entsprechende Kapitel im Lehrbuch nacharbeiten. Und dann gehts wieder ans Projekt.

Vielen lieben Dank euch beiden!
BlackJack

Wobei ich Signale ”deklarieren” würde, dann kann man das `connect()` vom Signal verwenden und muss nicht mit dem veralteten `SIGNAL` und Zeichenketten herum hantieren.

Eine Frage die man sich dabei Stellen sollte ist ob man in der Programmlogik Signale haben möchte und sich die dadurch dann von Qt abhängig macht. Ich persönlich möchte das in der Regel nicht.
Antworten