Vor Multi-Calls schützen



  • @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.



  • Biolunar schrieb:

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

    temporär ausgehende Signale für das entsprechende Widget blockieren:

    http://doc.qt.io/qt-5/qobject.html#blockSignals



  • Wie gesagt, mein Problem geht ein bisschen weiter. Mir ist bewusst, dass es für das einfache Slider/Spinbox-Beispiel keine Probleme gibt und man Signals deaktivieren könnte.

    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.

    Edit: Verstehe ich. Das Pendant für komplexere Gegebenheiten wäre bei einem Model dann aber wieder, dass ich einmal ein rechtsseitiges und einmal ein linksseitiges Event anbieten würde: changedByModel, changedByUI... Views listen auf changedByModel, das übergeordnete Model listened auf changedByUI. Wenn ich eine weitere Ebene dazwischenschiebe, passen die Namen nicht mehr, dann habe ich evtl. zwischen Model und UI noch ein ProxyModel o.ä.

    Generell wäre Eingehen auf meinen längeren Beitrag, in dem ich mein Supermodel/Model1/Model2-Design erläutere, sehr hilfreich. Diese Trivialitäten zu besprechen ist in meinen Augen recht nutzlos. Ich bin recht sicher alle "einfachen" Lösungen bereits durchgegangen zu sein, die habe ich doch auch extra in besagtem Beitrag mit Vor- und Nachteilen aufgelistet.



  • Biolunar schrieb:

    Das Problem liegt also an Qt, was auch Events auslöst, wenn der Wert im Programmfluss geändert wird und nicht nur vom User.

    Dann hat Qt dafür sicher eine Standard Lösung. Am besten du fragst in einem Qt Forum mal nach was das Vorgehen dabei ist.

    Manchmal kann man Events explizit unterdrücken, manchmal geht es gleich ganz anders. Im Prinzip muss man MIT seinem Framework arbeiten und nicht dagegen.



  • Also wie gesagt... auch wenn hier vielfach anders erwähnt, mein Problem ist nicht QT-basiert (nur, dass QT-Models keine echten Models sind, ändert am Grundsätzlichen aber nichts).



  • Was sind das für Widgets und wie ist das Binding an das Model implementiert? Machst du das selber oder sind des Standardwidgets?
    Vielleicht solltest du das konkreter erklären. Ich glaube nicht, das das Problem mit den Standardwidgets besteht (hatte solche Use Cases aber noch nicht). Nehmen wir z.B. ein QTableView. Wenn dataChanged vom Model reinkommt, wird auf dem Delegate setEditorData aufgerufen. Glaube nicht, dass es dazu führt, dass wieder ein setData auf dem Model aufgerufen wird.



  • Hi Mechanics,

    also ich nutze QTableView, QTreeView, QListView und eigene Views mit eigenen Models (z.B. Text mit bestimmter Syntax), je nach Anwendung. Und dataChanged ist in der Tat nicht das Problem, aber auch keine Allheil-Änderungs-Signal. Ich interagiere ja wie gesagt auf einem Haufen Daten. Der kann aber tabellarisch oder als Text interpretiert werden. Und für tablebasierte Views habe ich natürlich auch ein Table-Model und für textbasierte Views ein Text-Model (das ist in diesem Fall Eigenkreation).

    Die eigentlichen Daten sind aber eine eigene Entität, die ich hier eben in meinem "Supermodel" halte. Table und Text sind eben lediglich Interpretationen/Darstellungsformen dieser Daten. Daher bedarf es natürlich ein Update anderer Models, wenn sich Daten in einem Model geändert haben. Und das läuft darüber, dass erst das SuperModel benachrichtigt wird und das die anderen Models benachrichtigt. Wenn ich SuperModel aber auf dataChanged vom Model verbinde, habe ich bereits wieder mein Problem:

    Model1 wird geändert, signal Model1::dataChanged -> slot Supermodel::onModelDataChanged -> Model1::setData, Model2::setData -> signal dataChanged -> slot Supermodel::onModelDataChanged

    Also benötige ich wieder irgendeinen Mechanismus, um das zu verhindern ("Event Handler Design" a la ShadeOfMine, also zwei verschiedene signals, ist dabei eben EINE Möglichkeit; unschön jedoch, weil dataChanged dann ein schlechter Name ist, aus dem nicht hervorgeht, für wen sich Daten geändert haben).



  • Eisflamme schrieb:

    Also benötige ich wieder irgendeinen Mechanismus, um das zu verhindern ("Event Handler Design" a la ShadeOfMine, also zwei verschiedene signals, ist dabei eben EINE Möglichkeit; unschön jedoch, weil dataChanged dann ein schlechter Name ist, aus dem nicht hervorgeht, für wen sich Daten geändert haben).

    Dann nenn die halt anders? dataChanged und dataSynced zb? verstehe jetzt dein problem nicht so ganz



  • dataChanged ist QT-Vorgabe für die QT-Models...

    Und dieser Mechanismus löst auch nicht alle Probleme, denn View-Aktualisierung läuft über das Model, also:

    View1 geändert, Model1->setValue(x), Model1 emittiert signal Model::valueChanged, worauf die Views listenen... so auch View1

    Klar, jetzt kann View1 eben temporär das Signal oder seinen Slot disconnecten. Dann stellt sich aber die Frage, wofür wir überhaupt unterschiedliche Signals nutzen, wenn die Views ja dann doch wieder aufpassen müssen, dass sie nicht mehrfach aktualisiert werden.

    Wie löst ein "vernünftiges" Eventhandler-Design das? Sehe ich nicht. Man braucht ohnehin einen weiteren Mechanismus.



  • Eisflamme schrieb:

    Model1 wird geändert, signal Model1::dataChanged -> slot Supermodel::onModelDataChanged -> Model1::setData, Model2::setData -> signal dataChanged -> slot Supermodel::onModelDataChanged

    Das gefällt mir spontan nicht... Mit dataChanged würde ich Änderungen nach oben weiterreichen, nicht nach unten.

    Ich führ noch eine GUI Ebene ein:

    Widget changed -> Model1::setData -> SuperModel::setData -> Signal dataChanged:

    -> Model1::onDataChanged -> Widget Slot onDataChanged löst KEIN setData mehr aus. Dürfte doch hinkommen?



  • Meinst du mit "oben" das UI? Denn falls nicht: dataChanged aktualisiert automatisch die Views (oder Delegates meinetwegen nur, aber die Trennung finde ich blöd), darauf habe ich also keinen Einfluss.

    In deinem Ansatz kennt das Model also jeweils sein Supermodel?

    Was mir daran nicht gefällt: Die Models sollen theoretisch auch ohne Supermodel arbeiten können. Hatte ich nicht erwähnt, aber es ist quasi ne Art von Zusammensetzung. Die Models können in Interaktion miteinander wirken, müssen die aber nicht.

    Daher finde ich es schöner, wenn Models signalisieren, wenn was passiert ist, statt Direktaufrufe zu machen.

    Aber andere Frage: mein SuperModel ist kein QT-Model, sondern hausgemacht. Das hat also kein dataChanged; in deiner Aufrufhierarchie sehe ich jetzt nicht, wie Views geupdated werden.



  • Eisflamme schrieb:

    Was mir daran nicht gefällt: Die Models sollen theoretisch auch ohne Supermodel arbeiten können. Hatte ich nicht erwähnt, aber es ist quasi ne Art von Zusammensetzung. Die Models können in Interaktion miteinander wirken, müssen die aber nicht.

    Irgendwie widersprichst du dir da:

    Eisflamme schrieb:

    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.

    Eisflamme schrieb:

    Alle operieren jedoch auf den eigentlich gleichen Daten, die sich in Supermodel wiederfinden.

    Was jetzt, wenn das Supermodel die "eigentlichen" Daten enthält und die Models die Daten des Supermodells nur aufbereiten wie sollen die dann ohne einander arbeiten können?

    Eisflamme schrieb:

    dataChanged ist QT-Vorgabe für die QT-Models...

    Und dieser Mechanismus löst auch nicht alle Probleme, denn View-Aktualisierung läuft über das Model, also:

    Kann sein, aber das ist ja rein Qt-spezifisch und du hast ja gemeint:

    Eisflamme schrieb:

    Also wie gesagt... auch wenn hier vielfach anders erwähnt, mein Problem ist nicht QT-basiert

    Was spricht denn gegen die Variante mit dem Flag? Einfach und schnell, was willst du mehr?

    Aber ich glaub eher dass dein Design nicht ganz durchdacht ist (siehe oben).



  • Hi,

    stimmt schon, ist wieder nicht maximal genau. Eigentlich sieht es so aus:

    * class RealData
    * class SuperModel
    * class Model1
    * class Model2

    Model1 und Model2 agieren direkt auf RealData. SuperModel dient eigentlich mehr oder minder nur als Notifier für die anderen Models. Ich hab in meiner Darstellung zur Vereinfachung jetzt RealData und SuperModel verbunden, so ist es eben nicht, sollte jedoch auch keine allzu großen Unterschiede machen.

    Aber ich glaub eher dass dein Design nicht ganz durchdacht ist (siehe oben).

    Es ist ja bereits umgesetzt und hat sehr viele Vorteile ggü. vielen anderen Designideen, die ich auch durchdacht habe. Mit allen Details in die Diskussion zu starten führt halt meistens zu nichts. Ich bin auch immer noch der Meinung, dass diese ganzen Extradetails für den Großteil der Diskussion nicht so wichtig sind und würde es gerne abstrakter besprechen, aber da ergibt sich leider auch keine Diskussion. Immer schwierig, wie weit man geht.

    Was spricht denn gegen die Variante mit dem Flag? Einfach und schnell, was willst du mehr?

    Genau das würde ich ja auch gerne diskutieren,

    Flags immer und überall zu haben hat natürlich diverse Nachteile. Jeder View, jedes Model, jeder Controller muss die ständig vorhalten, es könnten leicht Bugs, man bläht damit das Interface auf und ich habe das bisher nicht breit und umfangreich so umgesetzt gesehen. Zudem wirkt es unelegant ständig bei jedem neuen View und Model solche Flags einzusetzen, es verschändelt den Code ja leider auch.


  • Mod

    SuperModel dient eigentlich mehr oder minder nur als Notifier für die anderen Models.

    Sehr interessant! Arbeitest du bei Victorias Secret?



  • Eisflamme schrieb:

    stimmt schon, ist wieder nicht maximal genau. Eigentlich sieht es so aus:

    Model1 und Model2 agieren direkt auf RealData. SuperModel dient eigentlich mehr oder minder nur als Notifier für die anderen Models. Ich hab in meiner Darstellung zur Vereinfachung jetzt RealData und SuperModel verbunden, so ist es eben nicht, sollte jedoch auch keine allzu großen Unterschiede machen.

    Eine Sache ist immer noch unklar, haben die Modells Kopien der Daten (in anderer Form) als das Supermodel? Oder sind die Daten wirklich nur einmalig hinterlegt?

    Eisflamme schrieb:

    Genau das würde ich ja auch gerne diskutieren,

    Flags immer und überall zu haben hat natürlich diverse Nachteile. Jeder View, jedes Model, jeder Controller muss die ständig vorhalten, es könnten leicht Bugs, man bläht damit das Interface auf und ich habe das bisher nicht breit und umfangreich so umgesetzt gesehen. Zudem wirkt es unelegant ständig bei jedem neuen View und Model solche Flags einzusetzen, es verschändelt den Code ja leider auch.

    Ja aber das ist halt der standard Qt-Weg. Beliebiges Beispiel aus einem Tutorial:

    void Counter::setValue(int value)
    {
        if (value != m_value) {
            m_value = value;
            emit valueChanged(value);
        }
    }
    

    Und ob du jetzt value != m_value vergleichst oder ein flag benutzt weil ein vergleich für deine Daten zu teuer ist, ist doch Jacke wie Hose?


Anmelden zum Antworten