Wie Zustandsänderung "von außen" mitteilen?

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
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Sorry, eine bessere Überschrift ist mir nicht eingefallen... :K

Zu meiner Frage: In der grafischen Oberfläche für mein Projekt Launchit werden Vorschläge zu Texteingaben des Benutzers angezeigt, welche sich sozusagen auf verfügbare Befehle/Dateien zu einer Eingabe beziehen. Man kennt das ja von Suchmaschinen, Browsern, anderen Launchern, usw. Der Benutzer fängt also an zu tippen und während er das tut, werden Vervollständigungen für ihn angezeigt, die mit jedem weiteren Buchstaben besser eingegrenzt werden können. Als kleines Extra-Feature markiert Launchit die aktuelle Texteingabe innerhalb des jeweiligen Vorschlags. Also z.B. so: gedit. Oder auch so: gnome-terminal. Das ist alles von der Funktionalität her kein Problem, mir geht es im Folgenden eigentlich nur um das Design meiner Implementierung.

Das, worüber ich immer wieder beim Lesen meines Codes stolpere, ist die Mitteilung des "Text-Bruchstücks", im Folgenden schlicht "Fragment" genannt. Die Klasse, welche das Zeichnen einer Vervollständigung mit entsprechender Markierung übernimmt, muss halt irgendwie informiert werden, welches das aktuell zu verwendende Fragment und damit auch die zu zeigende Markierung ist. Zum besseren Verständnis stelle ich den Ablauf mal schematisch dar:

Benutzer drückt Taste -> `textEdited`-Signal wird ausgelöst mit aktuellem Text als Argument -> an das Signal gebundene Methode `completer.update()` nimmt Argument entgegen, aktualisiert Vorschlagsliste und teilt dem "Zeichner" (Delegate) das neue Fragment mit.

Die Mitteilung dieser Änderung geschieht mittels `delegate.fragment = neues_fragment`. Dies ist für das Delegate der einzige Kontakt "nach außen" oder besser gesagt: *von* außen. Denn das Delegate interessiert sich im Grunde nur für sein `.fragment`-Attribut - völlig egal, wer es gesetzt hat. Und an dieser Stelle stecke ich in einem kleinen Zwiespalt: Einerseits ist ein direkter Attributzugriff in Python ja möglich und häufig auch gewünscht. Andererseits habe ich irgendwie den Drang, für eine so häufig genutzte Aktion doch lieber eine Setter-Funktion zu schreiben. Ich weiß, das ist ein bißchen mutella-Style (nichts für ungut), aber mich würde mal interessieren, wie ihr in einer solchen Situation vorgehen würdet.

Damit auch spätere Leser den Sachverhalt noch nachvollziehen können, hier die derzeit aktuelle `gui.py`: https://github.com/seblin/launchit/blob ... hit/gui.py (in Zeile 153 wird das Attribut gesetzt).
senft
User
Beiträge: 25
Registriert: Montag 31. März 2008, 14:47

Ich weiß nicht, ob das jetzt zu einfach wäre (und glaub nicht, dass du da noch nicht dran gedacht hast) aber was spricht gegen ein Observer-Pattern?
Grüße
lunar

Der direkte Durchgriff an den Delegate ist meines Erachtens unschön, egal ob per Attributzugriff oder per Setter. Besser wäre es, Delegate und Completer vollständig zu entkoppeln, so dass der Completer vom Delegate gar nichts weiß. Die Änderung des Texts und somit die Anpassung des zu hervorhebenden Textfragments im Delegate kannst Du doch viel einfacher vornehmen, indem Du ".textEdited()" zu einem passenden Slot im Delegate verbindest.

Allgemeine Anmerkungen: Du erzeugst fast alle Qt-Objekte ohne Angabe eines Vater-Objekts. Insbesondere den Completer und den Delegate solltest Du mit Vater-Objekte erzeugen, um Qt die Speicherverwaltung zu ermöglichen.

Zudem ist QItemDelegate durch QStyledItemDelegate ersetzt.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

lunar hat geschrieben:Der direkte Durchgriff an den Delegate ist meines Erachtens unschön, egal ob per Attributzugriff oder per Setter. Besser wäre es, Delegate und Completer vollständig zu entkoppeln, so dass der Completer vom Delegate gar nichts weiß. Die Änderung des Texts und somit die Anpassung des zu hervorhebenden Textfragments im Delegate kannst Du doch viel einfacher vornehmen, indem Du ".textEdited()" zu einem passenden Slot im Delegate verbindest.
Wo ist hier eigentlich der "Gefällt mir"-Button? :P
lunar hat geschrieben:Allgemeine Anmerkungen: Du erzeugst fast alle Qt-Objekte ohne Angabe eines Vater-Objekts. Insbesondere den Completer und den Delegate solltest Du mit Vater-Objekte erzeugen, um Qt die Speicherverwaltung zu ermöglichen.
Ich verstehe ehrlich gesagt den Vorteil davon nicht. Delegate und Completer werden doch exakt einmal erzeugt. Meines Wissens ist die Vater-Kind-Beziehung doch erst dann von Nutzen, wenn ständig Exemplare erstellt und wieder gelöscht werden, oder sehe ich das falsch?
lunar hat geschrieben:Zudem ist QItemDelegate durch QStyledItemDelegate ersetzt.
Ja, die von mir verwendete Klasse ist schon länger deprecated. Allerdings bietet `QStyledItemDelegate` kein `.drawBackground()` mehr an und ich war bisher zu bequem, dies nachzubauen, zumal der dafür nötige Code auch nicht der Schönste ist.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

senft hat geschrieben:Ich weiß nicht, ob das jetzt zu einfach wäre (und glaub nicht, dass du da noch nicht dran gedacht hast) aber was spricht gegen ein Observer-Pattern?
Dafür nutze ich lieber das Signal-Slot-System meines Frameworks. ;)
lunar

@snafu: Wie kommst Du auf die Idee, die Vater-Kind-Beziehung wäre nur in Verbindung mit kurzlebigen Objekte von Nutzen?! Im Gegenteil, sie ist gerade bei langlebigen Objekten nützlich, da diese dann mindestens solange leben wie sie von Qt benötigt werden ohne dass auf Python-Seite eine Referenz gehalten wird.

Du hast momentan überhaupt Glück, dass der Quelltext funktioniert, da Du nie Referenzen auf die erzeugten, aber noch benötigten Qt-Objekte hältst. Ins Blaue geraten funktioniert der Quelltext sowieso nur, weil Qt oder PyQt die Vater-Kind-Beziehungen für die beteiligten Objekte selbstständig anpasst, so dass die lokalen Objekte nicht gelöscht werden. Qt-Objekte sollte immer einen passenden Vater erhalten, wenn es einen gibt. Hier beim Completer das TextEdit, beim Delegate der View.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ich glaube, es liegt eher daran, dass ich die Exemplare als Attribute binde. Mir war bisher nicht bewusst, dass es Probleme machen kann, wenn ich ein Exemplar übergebe, ohne eine Referenz auf das Exemplar zu halten. Ich habe mich bisher auch noch gar nicht näher mit dem eigentlichen Sinn hinter `parent` beschäftigt. :oops:
lunar

Wo bindest Du den Completer oder den Delegate denn an Attribute?!
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Hast Recht.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@lunar: Ich verstehe es richtig, dass du empfiehlst, ein Exemplar des Delegates innerhalb von `LaunchEdit()` zu erzeugen, auf diesem Exemplar die entsprechende Signal-Slot-Verbindung zu setzen und dann ein `self.completer().popup().setItemDelegate()` aufzurufen? Ist es wirklich sauber, wenn eine "höhere" Klasse so weit absteigt, um dort eine Aufgabe/Änderung zu erledigen? Ist es nicht vielleicht sinnvoller, dem Completer ein `fragmentChanged`-Signal zu spendieren, auf welches der Delegate anspringt?
lunar

@snafu: Für mich ist das keine Frage der „höheren„ oder „tieferen“ Klasse, sondern eher eine Frage nach der Verantwortlichkeit, also welche Klasse für ein Feature, in diesem Fall die Hervorhebung, verantwortlich ist.

Die Hervorhebung ist ja Bestandteil der Anzeige, und dafür ist meines Erachtens das Label, also die graphische Komponente verantwortlich. Der Completer dagegen ist eine darunter liegende Komponente ohne direkte „Verbindung“ zur graphischen Oberfläche, und sollte daher imho auch keine Annahmen über seine Darstellung treffen. Es könnte ja immerhin auch sein, dass Du diesen Completer später mal in einem anderen Kontext verwenden willst, indem es kein Popup, keinen View und auch keinen Delegate gibt. Dann müsstest Du den Completer anpassen.

Es spricht per se natürlich auch nichts dagegen, den Delegate im Completer zu konfigurieren. Dann aber wäre es sauberer, die Kommunikation über ein Signal abzuwickeln, und die eigentliche Aufgabe, nämlich das Bereitstellen der Vervollständigung, in eine Basisklasse zu extrahieren, um diesen Teil getrennt verwenden zu können.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Du hast Recht. Der Completer sollte sich in der Tat im Wesentlichen um die Verwaltung des Models kümmern - das Merken der Markierung gehört zum View. Die Konfiguration des Delegates werde ich dann konsequenterweise in einer abgeleiteten View-Klasse implementieren. Die konkrete Darstellung der gemerkten Markierung für jedes Element wird natürlich weiterhin vom Delegate übernommen. Das dürfte dann auch am ehesten dem Model-View-Konzept von Qt entsprechen.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Hatte mal wieder den Kopf frei, um ein bißchen was an dem Projekt zu machen...

So ziemlich jedes Widget hat jetzt brav das Widget, in welchem es erstellt wurde, als Parent erhalten (Commit). Das war wohl teilweise ein bißchen zuviel des Guten, da an einigen Stellen so oder so eine entsprechende Vater-Kind-Beziehung hergestelllt wurde. Eventuell nehm ich da später noch ein paar Änderungen vor.

Mein Problem ist jetzt, dass nach dem Erstellen eines eigenen Views (Commit) plötzlich mein angepasstes Delegate nicht mehr verwendet wird. Beim Testen sieht man zumindest keine Markierungen mehr in dem Popup für die Completions. Ich stehe gerade etwas auf dem Schlauch, da mir der wesentliche Unterschied nicht ganz klar ist. Das Delegate wird ja weiterhin an den View gebunden, so wie es vorher ja auch schon war. Was übersehe ich also?

Achso, und der Thread kann ggf. ruhig ins Qt-Forum verschoben werden. :)
lunar

@snafu: Wozu genau dient da jetzt der View? Wieso implementierst Du den ".update_fragment()"-Slot nicht direkt im Delegate und verbindest diesen dann mit dem "fragment_updated()"-Signal?
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

lunar hat geschrieben:@snafu: Wozu genau dient da jetzt der View? Wieso implementierst Du den ".update_fragment()"-Slot nicht direkt im Delegate und verbindest diesen dann mit dem "fragment_updated()"-Signal?
Weil ich den View als Container für die einzelnen Completions ansehe. Er soll das jeweils aktuelle Fragment enthalten. Der Plan war eigentlich, dass der Delegate den View nach dem zu verwendenden Fragment befragen wird, bevor er zeichnet. Allerdings sehe ich gerade, dass Qt offenbar keine direkte Verbindung zwischen Delegate und View anbietet, so dass die Idee vielleicht doch nicht so gut ist...

Nichtsdestortrotz würde mich schon interessieren, wieso der Delegate hier nicht von Qt benutzt wird.
lunar

@snafu: Ich habe keine Ahnung, soll heißen, ich sehe es dem Commit nicht direkt an. Insofern müsste ich die Anwendung selbst ausprobieren und debuggen, wozu mir ehrlich gesagt die Zeit fehlt.
Antworten