Weg mit den Leerzeichen

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Benutzeravatar
pillmuncher
User
Beiträge: 1482
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Sophus hat geschrieben:So wirklich rausrücken will hier nicht so niemand :-) Eher redet bzw. schreibt man viel drum herum, aber zum Punkt komme ich immer noch nicht. Ich habe immer noch das Gefühl im Kreis zu drehen. Oder die Antworten sind mit Absicht so dermaßen abstrakt gehalten.
Ein ganz konkretes Stück Code, Version 2: http://www.python-forum.de/pastebin.php?mode=view&s=422

Models repräsentieren (und kapseln) Werte. Views beobachten Models. Wenn eine View ein Model beobachten soll, dann registriert man es beim dem Model, entweder mittels model.subscribe(view), oder indem man es der __init__() Methode des Models mitgibt. Bei jedem Model können sich beliebig viele Views regsitrieren.

Um den Wert eines Models zu setzen verwendet mann dessen set(value) Methode. Jedes Model besitzt eine - per default leere - Vailierungs-Methode. Diese wird in set(value) aufgerufen, bevor der Wert gespeichert wird. Ist die Validierung erfolgreich, wird der neue Wert gespeichert und alle registrierten Views werden mittels view.on_success(value) darüber benachrichtigt. Scheitert die Validierung, wird die Änderung verworfen und alle Views werden darüber mittels view.on_failure(error) benachrichtigt.

Im konkreten Fall gibt es mehrere Views: BGColorSetter setzen die die Hintergrundfarbe eines Entry-Widgets. Im Erfolgsfall wird der Hintergrund weiß gesetzt, Fehlerfall rot. ValidationState beobachtet den Erfolg der Validierungen aller Models. ValidationState ist nicht nur eine View (also ein Beobachter von Änderungen), sondern auch selbt ein Model. Es repräsentiert den Erfolg aller Validierungen.

Die Funktion save() wird aufgerufen, wenn der Save-Button derückt wird. Zunächst wird dort der ValidationState auf True gesetzt. Dann werden alle Models mit den Werten der Eingaben aktualisiert. Jede Aktualisierung hat entweder Erfolg oder scheitert, je nachdem, was die entsprechende Validierung ergibt. Nachdem alle Aktualisierungsversuche durchgeführt wurden, können wir testen, ob alle Validierungen erfolgreich waren, indem wir den Wert des ValidationStates abfragen. Ist dessen Wert immer noch True, können wir die Models speichern und das Fenster schließen. Ist der Wert jedoch False, informieren wir den Benutzer mit einer Message Box und schließen das Fenster nicht, sodass er seine Eingaben korrigieren kann. Die zu korrigierenden Textfelder sind dann bereits rot markiert, weil ja die BGColorSetter ebenfalls benachrichtigt wurden und die Hintergrundfarbe entsprechend gesetzt haben.

Die Vorteile einer solchen Mikroarchitektur sind vielleicht nicht gleich ersichtlich. Sie liegen in der Trennung der verschiedenen Programmteile. Die Benutzeroberfläche muss nichts über Validierungen oder konkrete Werte wissen. Alles was sie wissen muss, sind Benutzeroberflächendinge. Die Models brauchen nichts über die Benutzerüberfläche zu wissen. Die Validierung wird direkt auf einem Wert ausgeführt, ohne dass das Model wissen muss, dass er aus einem tkinter.Entry Feld kommt. Man könnte sogar mit wenig Aufwand tkinter durch Qt ersetzen, und viele Teile des Programms könnten bleiben, wie sie sind. Und die Teile, die geändert werden müssen, schreien einen geradezu an: "ich bin tkinter-spezifisch". Oder man könnte Tooltips/Balloons erzeugen, die aufpoppen, wenn sich der Mauszeiger über einem Entry-Feld mit feherhafter Eingabe befindet, und so die Ursache des Fehlers ausgeben. In einem MVC-System ist das trivial zu lösen, man braucht nur - analog zu BGColorSetter - eine neue View registrieren, die einen Balloon erzeugt. Dazu sind nur diese Änderungen nötig:

In tkinter gibt es keine Balloons, aber in tkinter.tix:

Code: Alles auswählen

import tkinter.tk as tk
das hier:

Code: Alles auswählen

import tkinter.tix as tk
Diese neue View-Klasse:

Code: Alles auswählen

class BalloonSetter(View):

    def __init__(self, root, entry):
        self.entry = entry
        self.balloon = tk.Balloon(root, initwait=500, state='balloon')

    def on_success(self, value):
        self.balloon.unbind_widget(self.entry)

    def on_failure(self, error):
        self.balloon.bind_widget(self.entry, msg=str(error))
Und statt:

Code: Alles auswählen

        name_entry: Name(BGColorSetter(name_entry), is_valid),
        age_entry: Age(BGColorSetter(age_entry), is_valid),
das hier:

Code: Alles auswählen

        name_entry: Name(BGColorSetter(name_entry), BalloonSetter(window, name_entry), is_valid),
        age_entry: Age(BGColorSetter(age_entry), BalloonSetter(window, age_entry), is_valid),
In specifications, Murphy's Law supersedes Ohm's.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@pillmuncher: Danke für dein Beispiel. Ich werde deinen Code als Lektüre heranziehen, und versuchen im einzelnen zu verstehen.
Antworten