Vor Multi-Calls schützen



  • Hi,

    wenn ich mit signals/slots arbeite, kann ich auch mal in eine Endlosschleife geraten, z.B. wenn ein View den anderen alarmieren soll, dass sich etwas geändert hat, der nach Änderung dieselbe Änderung wieder dem ersten View mitteilt usw.

    Wie man das am besten lösen sollte, ist hier die Frage: https://www.c-plusplus.net/forum/334557

    Eine Lösung wäre in meinen Augen ein Guard. Was haltet ihr von dieser Implementierung? http://ideone.com/MEBxar

    Gruß
    Eisflamme



  • Ganz ehrlich:
    Ich hab noch nie eine Endlosschleife mit Events gebaut.



  • Hi,

    dann hast du aber irgendeinen Mechanismus, der dich davor schützt.

    Sagen wir, View1 ist ein Slider und View2 ist eine Spinbox, beide arbeiten aber mit dem gleichen Wert. Jetzt ändert der Benutzer den Wert vom Slider, dadurch wird ein Event ausgelöst und der Handler setzt beim Model den Wert. Das Model alarmiert natürlich alle Views über den neuen Wert, somit auch den Slider. Somit meldet er es wieder ans Model zurück usw.



  • Wenn ein View vom Modell über einen neuen Wert benachrichtig wird, dann sollte es nicht seinerseits das Model darüber benachrichtigen, dass es grad einen neuen Wert bekommen hat.. Wenn das nämlich so laufen würde, dann müsstest Du schon bei einem einzigen Wert in solche Endlosschleife reinlaufen...



  • Eisflamme schrieb:

    Hi,

    dann hast du aber irgendeinen Mechanismus, der dich davor schützt.

    Sagen wir, View1 ist ein Slider und View2 ist eine Spinbox, beide arbeiten aber mit dem gleichen Wert. Jetzt ändert der Benutzer den Wert vom Slider, dadurch wird ein Event ausgelöst und der Handler setzt beim Model den Wert. Das Model alarmiert natürlich alle Views über den neuen Wert, somit auch den Slider. Somit meldet er es wieder ans Model zurück usw.

    Zu mindestens ist es AFAIK so nicht in Qt. Wenn ein View (z.b. der Slider) daten vom model bekommt so sendet dieses keine change signal weiter.
    Und wiso sollte er auch?
    Falls du das ganze selbst gemacht hast (z.b. ein eigener ItemDelegate) dann kann man folgendes machen (am Beispiel des Slieders).

    Wenn vom model ein "data changed" signal kommt:
    - wird der "data changed" signalhandler (welche die daten ins model schreibt) des sliders disconnected
    - der Wert dem slider für die anzeige mitgeteilt
    - der "data changed" signalhandler des sliders wieder connected



  • Hi,

    firefly:
    Und statt dieses Mechanismuses finde ich einen Guard schöner. Dis- und reconnecten finde ich in QT nämlich nicht schön, man kann sich ja nicht mal die Connection sichern, um zu disconnecten. Also muss ich disconnect mit denselben Parametern aufrufen wie connect... dann ändere ich was und muss es an zwei Stellen ändern.

    Vielleicht ein etwas anderes Beispiel:

    Ich habe ein Model. Es gibt jetzt andere Models, welche die Daten dieses Models abgewandelt nutzen. Ich habe also eine Hierarchie:

    `

    • Daten

    -- Model1

    --- View1

    --- View2

    -- Model2

    --- View3

    --- View4

    `

    Model2 ist an Änderungen von Model1 interessiert, genau so andersrum. View1/2 sind natürlich auch an Änderungen von Model1 interessiert. Zudem werden View1/2 auch Model1 alarmieren, wenn sich etwas geändert hat.

    Wenn ich jetzt vorgehe wie von Jester vorgeschlagen, müsste ich zwei Arten von Settern haben. SetXByModel() und SetXByView(). Der eine teilt nur den Views Änderungen mit (SetXByModel), der andere teilt anderen Models und den Views Änderungen mit...

    Klappt.

    Finde ich aber blöd zu bedienen. Und ist auch blöd erweiterbar, wenn ich später noch tiefergehende Models habe oder cascaded Views mit eigenen Controllern etc.



  • Eisflamme schrieb:

    Wenn ich jetzt vorgehe wie von Jester vorgeschlagen, müsste ich zwei Arten von Settern haben. SetXByModel() und SetXByView(). Der eine teilt nur den Views Änderungen mit (SetXByModel), der andere teilt anderen Models und den Views Änderungen mit...

    Wo hast Du solche Setter? Im View? Es ist nicht die Aufgabe einer View andere Views zu benachrichtigen... das erledigt doch das Model.

    Eigentlich gibt es nur zwei Sachen 1) durch Interaktion mit einer View ändern sich Werte; darüber benachrichtigt die View das Model und das wars. 2) Das Model benachrichtigt eine View (oder mehrere) darüber, dass die Darstellung aktualisiert werden muss. Das macht die jeweilige View und das wars. Es gibt also genau eine Stelle im Code wo Benachrichtigungen verschickt werden und genau eine wo die Darstellung aktualisiert wird.



  • Okay, blöd beschrieben. Daten ist bei mir auch eine Klasse.

    Nehmen wir lieber das hier:

    `

    SuperModel

    • Model1

    -- View1

    -- View2

    • Model2

    -- View3

    -- View4`

    Wenn sich in Model2 was ändert, teilt es das SuperModel mit, über ein Signal etwa, ist ja egal.

    SuperModel alarmiert jetzt alle Models über eine Änderung (na ja, oder eben nicht Model2, hier haben wir ja schon das Problem, aber ignorieren wir das erstmal).

    Jetzt stellt sich die Frage, wie wird Model1 alarmiert? Sagen wir model1->SetX(value); , dann ist das blöd. SetX würde ja die Views über ein Signal alarmieren, damit die den neuen Content darstellen. Zusätzlich alarmiert es aber auch das SuperModel, weil das ja die anderen Models benachrichtigen soll... das wollen wir ja eigentlich nicht.

    Also bräuchten wir für beide Richtungen Setter. Für die Richtung View -> Model und SuperModel -> Model.

    Knackpunkt ist hier also: Die Models sollen sich gegenseitig über Änderungen informieren.

    Edit: Für diejenigen, welche das wundert: Die Models sind hier in der Tat QT-Models... für ein vernünftiges Design nutzt man die nur als Adapter, da sie schließlich auch Darstellungsinfos enthalten. Wenn ich beispielsweise eine tabellarische Darstellung und einmal eine textbasierte Darstellung habe, benötige ich dafür zwei Models. Der eigentliche Content sollte natürlich in einer separaten Klasse thronen und nur aufbereitet werden müssen. Diese separate Klasse wäre dann hier das Supermodel.



  • Prüfe doch, ob sich der Wert tatsächlich geändert hat und sende das Signal nur dann aus.



  • Hatte ich auch schon überlegt, löst ja aber nur ein Teilproblem. Der View, der aussendet, kriegt dann ja trotzdem vom Model eine Änderung zurückgeliefert.

    Jetzt kann der die Änderung natürlich auch prüfen... Aber je nach Aufbereitung hat er dann denselben Aufwand, als wenn er es neu setzen würde.



  • Man mag mich jetzt auslachen, aber ich hatte schon öfter mal ähnliche Probleme.
    Gute Lösung die allgemein anwendbar wäre hab' ich dafür bisher auch keine gefunden.

    Vielleicht bin ich aber auch einfach nur doof. Oder halt nicht besonders gut als GUI Programmierer geeignet (mach' ich auch kaum, und in nicht-GUI-Programmen hatte ich solche oder ähnlche Probleme bisher nicht) 🙂



  • Hi!

    Also ich würde dem Problem mit folgender Philosophie begegnen: "GUI-Elemente sind keine Datenspeicher, sondern Modelle sind Datenspeicher".
    GUI-Elemente sind lediglich Anzeige-Objekte, "Bildschirme" die mir einen Blick auf die Daten getatten.

    Dementsprechend gibt es in den GUI-Elementen auch kein "MeineDatenHabenSichGeändert"-Signal,
    sondern lediglich einen "ModellDatenHabenSichGeändert"-Slot, der im Modell eingehängt ist.

    Wenn ich nun z.B. einen Slider verändere, oder Text in eine Eingabefeld eintippe, dann verändern diese Input-Ereignisse
    lediglich die Daten den Modells - die Aktualisierung der Anzeige erfolgt dann über das Ereignis, das dann vom Modell zurückkommt.

    Natürlich haben die GUI-Elemente auch ihre aktuellen Werte gespeichert, das sind dann aber keine "Nutzdaten" sondern lediglich
    intern zwischengespeicherte Werte die fürs Rendering oder die Funktion des GUI-Elements notwendig sind.

    Die Struktur ist also baum-ähnlich: Event-Elemente kommunizieren nicht mehr kreuz- und quer untereinander sondern immer über ihren "Elternknoten",
    das Modell. So werden Zyklen im Event-Graph unterbrochen und die ganze Struktur wird meines Erachtens auch noch etwas übersichtlicher.

    Gruss,
    Finnegan

    P.S.: In Bezug auf den ersten Post des Fragestellers heisst das dann:
    Ein View alarmiert keinen anderen View, sondern er verändert lediglich die Daten im Modell. Das Modell ist dann das Objekt das den "anderen" View alarmiert.



  • Ja und genau das passiert hier doch auch, was andres meine ich nie behauptet zu haben.

    Nach wie vor bleibt jetzt das Problem: was, wenn ich statt Model und view drei Ebenen habe, z.B. Supermodel, Model, view oder auch Model, Adapter, view... Signale laufen entlang der Hierarchie, aber ein Model (also mittlere Ebene) kann eben von links oder rechts beeinflusst werden, was an die andere Ebene (rechts oder links respektive) übertragen werden muss und nur an die.

    Proxy Models von QT lösen das mit Source und Target, aber dann muss man das auch immer fest definieren, ich will es sprechender.

    Oder man definiert das nicht fix, eine Änderung ist einfach eine Änderung und dann wären die Guards praktisch.

    Fände es schön die Diskussion da nochmal anzusetzen.



  • Eisflamme schrieb:

    Ja und genau das passiert hier doch auch, was andres meine ich nie behauptet zu haben.

    Wo ich eigentlich drauf hinaus wollte, wenn sich sage, dass "GUI-Elemente keine Datenspeicher" sind ist dass sie kein "Meine Daten haben sich verändert"-Ereignis kennen,
    sondern nur "Jemand hat an meinen Schräubchen gedreht". Andere Elemente, die auf Veränderung der Daten reagieren sollen, hängen sich
    also nicht in das GUI-Element ein, sondern in dessen Eltern-Model.

    Eisflamme schrieb:

    Nach wie vor bleibt jetzt das Problem: was, wenn ich statt Model und view drei Ebenen habe, z.B. Supermodel, Model, view oder auch Model, Adapter, view... Signale laufen entlang der Hierarchie, aber ein Model (also mittlere Ebene) kann eben von links oder rechts beeinflusst werden, was an die andere Ebene (rechts oder links respektive) übertragen werden muss und nur an die.

    Okay, ich hätte ich das mit der "Baum-Struktur" noch in meine Philosophie aufnehmen sollen 😃
    Ich hoffe ich habe da nichts falsch verstanden, aber wenn man eine strikte Baum-Struktur einhält sollte das eigentlich keine Probleme verursachen.
    Dann sind nämlich "rechts/links-Beeinflussungen" nicht möglich sondern alles muss über einen gemeinsamen Elternknoten laufen (bzw. genauer: einen gemeinsamen Vorfahren).
    Kinder (ob nun GUI-Elemente in den Blattknoten des Baums oder Models/hybride Models/GUI-Elemente in den inneren Knoten) setzen die neuen Daten immer im Eltern-Model,
    und diese fordern dann alle ihre Kinder auf, ihre intern gespeicherten Daten auf den neuesten Stand zu bringen.

    D.h die Models der Zwischeebenen setzen eben nicht ihre eigenen Daten, sondern nur das "Supermodel".

    Oder kurz: Baum-Struktur und jeder ändert immer nur die Daten in seinem Elternknoten. Die internen Daten werden erst dann aktualisiert, wenn die Benachrichtigung vom Elternknoten kommt.

    Finnegan



  • @Finnegan
    Mag sein dass das gut funktioniert, bloss halten sich die Widgets der mir bekannten GUI Frameworks nicht daran.
    (WPF mal aussen vor, wie das Data-Binding von WPF intern wirklich funktioniert weiss ich nicht -- hab bisher nur mini-Projekte mit WPF gemacht, und da hat einfach alles "automagisch" so funktioniert wie ich es brauchte.)

    D.h. du hast da nen Slider, den der User verschieben kann. Und natürlich kann der Controller den Wert des Sliders setzen.
    Der Slider feuert aber in beiden Fällen den selben "mein Wert hat sich geändert" Event.

    Wenn der Controller kein "I am changing the silder value myself, ignore the change event" Flag* verwendet, dreht man also zumindest eine unnötige Ehrenrunde. Bzw. wenn man dann noch ein wenig pfuscht, dann kann man damit auch schnell ne infinite-recursion bauen 😉

    * Was wenn ich das richtig verstanden habe so einem Eisflamme-"Guard" entspricht.



  • Huhu,

    technisch kann ich hier fast nicht mitreden, ich will aber trotzdem meinen Senf dazugeben:

    Ich habe mal mit Qt ein kleines Programm, "Color Picker", geschrieben, weil ich ganz oft irgendwelche Farben aussuchen wollte, und es mir zu blöd war dafür immer ein Bildbearbeitungsprogramm zu nehmen.

    Jedenfalls gibt es da auch zum einen Slider (jeweils für RGB) und zum anderen SpinEdits oder wie die Dinger auch heißen. Und beide aktualisieren sich gegenseitig (verwenden den selben Wert). Ich habe noch nie Probleme damit bekommen und es funktioniert einwandfrei.

    Das sieht dann irgendwie so aus:

    connect(ui->slider, SIGNAL(valueChanged(int)),
                ui->spinBox, SLOT(setValue(int)));
    //...
    connect(ui->spinBox, SIGNAL(valueChanged(int)),
                ui->slider, SLOT(setValue(int)));
    

    Vielleicht war es ja ganz anders gemeint, in dem Fall bitte einfach ignorieren :s

    LG
    HarteWare

    P.S.: Oh, könnte gut sein, dass ich es nicht ganz kapiert hab. Mit irgendwelchen "Models" arbeite ich da nicht 😞



  • @HarteWare: Ja, eigentlich ist bei solchen Diskussionen davon auszugehen, dass die anderen über das Stadium solch trivialer Erkenntnisse schon hinaus sind 😉

    @Eisflamme: ich glaube, ich hatte selten den Fall, dass ich ein Model in mehreren Views benutzen wollte, dann hats aber gereicht zu prüfen, ob sich der Wert tatsächlich geändert hat.
    Dein Guard wäre grundsätzlich auch eine Möglichkeit (die Zeile mit return und der Zuweisung im bool Operator gefällt mir nicht). Bin mir aber nicht sicher, wie sich das mit Queued Connections usw. verhält, müsste man wahrscheinlich genauer drüber nachdenken.



  • Hallo,

    danke für die Antworten. 🙂 Die Lösungsvorschläge sind alle richtig und wichtig, haben jedoch in meinem erweiterten Szenario auch viele Unschönheiten.

    Daher möchte ich nochmal mein Szenario vorstellen, um die Unschönheiten der Lösungsansätze genau zu zeigen. Das Szenario umfasst ja etwas mehr als Views:

    - Supermodel
    -- Model1
    --- View1
    --- View2
    -- Model2
    --- View3
    --- View4

    Supermodel enthält die eigentlichen Daten. In QT braucht man für einen View stets (meistens) ein "Model" (Model ist aber eher ein Adapter als ein Model im MVC-Sinne). D.h. die Models bereiten die Daten vom Supermodel in einer bestimmten Form auf, um die Anzeige /Handhabung über Views zu ermöglichen. (vielleicht ist die Info, dass QT-Views die MVC-Konzepte von View und Controller verschmelzen noch wichtig, spielt hier aber imo gerade keine Rolle)

    View1 und View2 könnten z.B. zwei Arten von Textdarstellung sein, View3 und View4 könnten zwei Arten von Tabellendarstellung sein. Alle operieren jedoch auf den eigentlich gleichen Daten, die sich in Supermodel wiederfinden.

    Model1 könnte z.B. ein Model für einen String sein (hat in QT klassischerweise kein Model, habe ich aber eingefügt, weil es verschiedene Text-UI-Widgets gibt). Model2 ist ein Model für eine kompliziertere grafische Darstellung, der Einfachheit halber sagen wir mal eine Tabelle.

    Wenn ich mich nur auf View1 und View2 beziehe (z.B. Spinbox und Slider), dann kann ich eben eine dieser Möglichkeiten wählen:
    - mein hausgemachter Guard (wie korrekt von Mechanics erkannt klappt das bei Queued Connections so nicht, aber die benutze ich stdmäßig eh nicht und ich habe nur einen UI-Thread)
    - signals dis/connecten (finde ich eklig in QT, boost::signals könnten das; aber klappt grundsätzlich)
    - einfach irgendein Flag setzen, das zeigt, dass die Funktion im aktuellen Call bereits aufgerufen wird (finde ich auch unschön, aber klappt)
    - Methoden wie setValue doppelt schreiben wie es in QT üblich ist... manche setValues signalisieren eine Änderung, andere nicht... ja, richtig, QT hat nicht im Interface zwei mal setValue, aber trotzdem gibt es grundsätzlich die Möglichkeit zur Datenmanipulation, die signalisiert wird oder eben nicht, in meinem Szenario hätte ich solche Methoden eben doppelt, ob im public Interface oder nicht (wird schnell unübersichtlich... wer emitted jetzt, wer lässt es sein?)
    - prüfen, ob Änderungen stattfinden

    In meiner Struktur oben (Supermodel/Model/View), die ich grundsätzlich nach viel Nachdenken für den besten Kompromiss halte, gibt es jetzt eben Probleme. Wenn über eine Model/View-Einheit Daten verändert werden, muss das andere Model natürlich informiert werden. Wenn sich in einem View etwas ändert, passiert also das hier:

    View3 geändert -> Model2 aufbereitete Daten geändert (passiert mehr oder minder automatisch) -> Supermodel geändert -> Model1 wird benachrichtigt -> View1 und View2 werden benachrichtigt

    Jetzt haben die Lösungsansätze von oben vertiefte Probleme:
    - Guard (ist ja anscheinend nicht so intuitiv; und der würde dann in den Views, im Model und im Supermodel zum Tragen kommen)
    - signals dis/connecten (auch dieser recht eklige Mechanismus ist jetzt in Models/Views vertreten; in Wahrheit müssen wir aber oft nicht Signals sondern Slots blockieren, was nur über Flags geht)
    - einfach irgendein Flag setzen (auch das ist jetzt in jedem Model/View drin, wäre eine Möglichkeit)
    - Methoden wie setValue doppelt schreiben (ist meine aktuelle Lösung, jetzt aber noch unübersichtlicher; setValue1 könnte vom View aufgerufen werden, setValue2 könnte vom SuperModel aufgerufen werden, je nachdem, ob etwas durch den View oder ein andres Model geändert werden müsste; die Folge sind jetzt pro Änderung zwei verschiedene Signale, je nach Zielgruppe)
    - prüfen, ob Änderung geschehen ist -> das ist nicht-trivial und kostet daher Performance; die gesamte Aufbereitung von Daten vom Supermodel in Model1 oder Model2 muss geschehen, um das festzustellen, ich spare also nichts)

    Hat jemand eine Tendenz zu einem Lösungansatz, einen anderen Vorschlag oder würde sogar mein Basisdesign zerschießen wollen? Oder natürlich Nachfragen, warum das bei mir so ist, wie es ist. Freue mich über jeden Input 🙂



  • Eisflamme schrieb:

    dann hast du aber irgendeinen Mechanismus, der dich davor schützt.

    Nein. Ich hatte so eine Situation nie. Denn Events werden ja nicht random abgefeuert sondern man listened explizit auf spezielle Events und da ergibt sich das eigentlich von alleine dass es keine Endlosschleifen gibt.

    Sagen wir, View1 ist ein Slider und View2 ist eine Spinbox, beide arbeiten aber mit dem gleichen Wert. Jetzt ändert der Benutzer den Wert vom Slider, dadurch wird ein Event ausgelöst und der Handler setzt beim Model den Wert. Das Model alarmiert natürlich alle Views über den neuen Wert, somit auch den Slider. Somit meldet er es wieder ans Model zurück usw.

    Da ist dein Fehler. Wieso meldet die View an das Model dass es gerade vom Model eine Wertänderung bekommen hat?

    Die Views schicken das Event "UserHatWertEingabeGetätigt" und das Model schickt "DerNeueWertLautet". Das 2. Event kann nie nie nie nie das erste auslösen.

    Dein ganzes Problem ist IMHO einfach nur ein Problem der Events die du definieren willst. Events müssen nicht zwangsläufig neue Events generieren. Du kannst auch das bestehende Event einfach weiterreichen. Wenn zB View2 auf View1 lauscht, dann kann View1 einfach die "DerNeueWertLautet" Nachricht einfach weiter geben. Dazu muss View1 nicht ein neues "MeinWertHatSichGeändert" Event abschießen.

    idR ist das alles nur Event Design und simpler ist besser.



  • Shade Of Mine schrieb:

    Die Views schicken das Event "UserHatWertEingabeGetätigt" und das Model schickt "DerNeueWertLautet". Das 2. Event kann nie nie nie nie das erste auslösen.

    Wenn ich den Thread richtig verstehe, passiert aber genau das! Wenn das 2. Event an dem anderen Widget ankommt (irgendwo steht ein widget2.set(new_value); ), so denkt das 2. widget, dass der User den Wert geändert hat und feuert ein neues Event. Dieses Event triggert ein widget1.set(new_value); , was wieder ein Event auslöst ⇒ endlos Rekursion.
    Das Problem liegt also an Qt, was auch Events auslöst, wenn der Wert im Programmfluss geändert wird und nicht nur vom User.

    Jester schrieb:

    Wenn ein View vom Modell über einen neuen Wert benachrichtig wird, dann sollte es nicht seinerseits das Model darüber benachrichtigen, dass es grad einen neuen Wert bekommen hat.. Wenn das nämlich so laufen würde, dann müsstest Du schon bei einem einzigen Wert in solche Endlosschleife reinlaufen...

    Dies ist nämlich der springende Punkt. Wie hindert man Qt daran ein Event zu feuern, wenn man den Wert des Widgets ändert.


Log in to reply