Vor Multi-Calls schützen



  • 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?



  • Eisflamme schrieb:

    Meinst du mit "oben" das UI?

    Ja.

    Eisflamme schrieb:

    In deinem Ansatz kennt das Model also jeweils sein Supermodel?

    Ja. So habe ich deine Beschreibung auch verstanden, siehe auch Antwort von hf2. Finde ich auch ok so. Das über Signale zu entkoppeln... Hmm, kann man vielleicht machen, ich will nicht unbedingt behaupten, dass das schlecht wäre. Gefallen tuts mir spontan aber auch nicht 😉 Ich denke, so wie ich das beschrieben habe, müsste es funktionieren und wäre einfacher. Aber wenn du das unbedingt entkoppeln willst, führ doch ein zweites Signal ein, dataModified oder so.

    Eisflamme schrieb:

    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.

    Es kann tatsächlich ziemlich ausarten, wenn man mit Qt etwas wirklich komplexes bauen will. Ich habe damit auch schon einige Probleme. Man hat vielleicht eine Idee und fängt mit einem sauberen Design an. Jahre später hat man tausend andere Anforderungen und jeder will irgendwie mitreden... Ich musste teilweise schon stundenlang rumsuchen, bis ich überhaupt kapiert habe, warum etwas nicht funktioniert. Ist jetzt nicht unbedingt ein Qt Problem, ich kenne keine besseren Frameworks (WPF ist vielleicht mächtiger aber ich musste damit nie so komplexe GUIs bauen wie mit Qt, kann jetzt schlecht abschätzen, ob es evtl. einfacher gewesen wäre). Ist einfach schwierig, alle möglichen Anforderungen unter ein Konzept zu bringen. Ich hab mittlerweile teilweise schon einige Hilfskonstrukte und Hacks drin.

    Arcoth schrieb:

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

    Sehr interessant! Arbeitest du bei Victorias Secret?

    Ich hab auch paar Seiten gebraucht, bis ich mich dran gewöhnt habe, dass er von anderen Supermodels redet ^^



  • Arcoth schrieb:

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

    Sehr interessant! Arbeitest du bei Victorias Secret?

    😃 😃 😃 lmao 😃 😃 😃



  • Hey, ich bin bei Victoria's Street sehr erfolgreich, aber lacht nur.

    hf2:
    Die Daten sind in den Models in anderer Form hinterlegt, genau. QT interessiert sich bei einer Tabelle nach den Werten, wie sie in einer Tabelle stehen. In Textform gibt es ebenfalls eine bestimmte Aufbereitung und sie sind demnach so hinterlegt. RealData hingegen hat eine kompakte binäre Form der Daten.

    Die Daten sind also doppelt und dreifach gespeichert, korrekt. (Das zu verhindern würde die Performance unzumutbar beeinträchtigen)

    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?

    Wenn ich entsprechende Event-Handler verwende, benötige ich die Abfrage jedoch gar nicht. Wären Flags oder changed != m_changed unvermeidbar, wäre die Sache klar.

    Die meisten QT-Tutorials verwenden in meinen Augen ziemlich schlechte Designs, die bei Einsatz in größeren Projekten viele, viele Nachteile aufzeigen, ist für mich also jetzt erstmal kein wirkliches Argument (außer, du kannst ein bestimmtes Tutorial verlinken, in dem der Autor gut argumentiert und auch andere Ansätze ausprobiert hat).

    Mechanics:
    Ja, war mein Fehler, ich habe es in der Tat schlecht beschrieben.

    Es gibt eben Fälle, in denen ich eine Model-View-Kombination für sich alleine genutzt wird. Dann hätte ich ein Supermodel, bei dem sich keine anderen Models angemeldet haben und somit die Benachrichtigung sinnlos wäre. Erscheint mir nicht schön.

    Aber wenn du das unbedingt entkoppeln willst, führ doch ein zweites Signal ein, dataModified oder so.

    Da siehst du das Problem: dataChanged und dataModified... ziemlich unschön, oder? Zudem muss ich es wie gesagt kombinieren mit einer Abfrage, ob was geändert wurde bzw. in Kauf nehmen, dass ich ein doppeltes Update habe:

    View1 wird durch Benutzereingabe geändert -> Model1::setData -> dataChanged und dataModified müssen jetzt alarmiert werden, denn: sowohl andre Views als auch Supermodel müssen ja Update erfahren.



  • Eisflamme schrieb:

    Da siehst du das Problem: dataChanged und dataModified... ziemlich unschön, oder?

    Ich bin da pragmatisch und sehe kein Problem. Du hast die Anforderunng, dass du einmal bei Änderungen Notifications versenden willst, und einmal nicht. Da gibt es die paar Möglichkeiten, die wohl schon alle in dem Thread besprochen wurden, was anderes fällt mir jetzt auch nicht ein. Du hast also die Wahl, zwei Signale einzuführen oder ein Flag oder einen Guard zu verwenden. Du kannst dich im Endeffekt so oder so entscheiden. Mir persönlich würden zwei Signale denke ich besser gefallen. Damit ist schon in der Schnittstelle klar definiert, dass es die zwei Möglichkeiten gibt, die gehören dazu. Flags oder Guard schaut auf den ersten Blick nach einem Workaround aus. Den kann man irgendwo vergessen oder übersehen, ist ja nicht sofort klar, dass man das braucht. Und du wirst dann evtl. Probleme mit Queued Connections (meiner Erfahrung nach kommt man bei komplexen GUIs ohne kaum aus) und mehreren Threads bekommen (unwahrscheinlich im Model/View Konzept, aber wer weiß).



  • Eisflamme schrieb:

    Wenn ich entsprechende Event-Handler verwende, benötige ich die Abfrage jedoch gar nicht. Wären Flags oder changed != m_changed unvermeidbar, wäre die Sache klar.

    Du könntest die ja noch in einer parent klasse verstecken, so zb. (ist boost, mag qt nicht):

    #include <iostream>
    #include <string>
    #include <boost\signals2\signal.hpp>
    using namespace boost::signals2;
    
    template <typename Data>
    struct GuardedData {
    
    	Data data;
    	bool syncing = false;
    
    	virtual void setData(Data data) = 0;
    
    	void setDataGuarded(Data data) {
    
    		if (!syncing) {
    			syncing = true;
    			setData(data);
    			syncing = false;
    		}
    	}
    };
    
    struct DataModel : GuardedData<int> {
    
    	int value;
    	signal<void(int)> dataChanged;
    
    	void setData(int newValue) {
    		value = newValue;
    		dataChanged(newValue);
    	}
    };
    
    struct DataStringModel : GuardedData<int> {
    
    	std::string valueFormated;
    	signal<void(int)> dataChanged;
    
    	void setData(int newValue) {
    		valueFormated = "str(" + std::to_string(newValue) + ")";
    		dataChanged(newValue);
    	}
    };
    
    int main() {
    
    	DataModel dm;
    	DataStringModel dsm;
    
    	dm.dataChanged.connect([&dsm](int i) { dsm.setDataGuarded(i); });
    	dsm.dataChanged.connect([&dm](int i) { dm.setDataGuarded(i); });
    
    	dm.setData(3);
    	std::cout << "dm: " << dm.value << std::endl;
    	std::cout << "dsm: " << dsm.valueFormated << std::endl;
    
    	dsm.setData(5);
    	std::cout << "dm: " << dm.value << std::endl;
    	std::cout << "dsm: " << dsm.valueFormated << std::endl;
    }
    

    Dabei halten sich DataModel und DataStringModel gegenseitig aktuell ohne endlos-schleife.

    Slots sind sauber, signals sind sauber. Alles was du machen musst ist deine jeweilige klasse von GuardedData<T> abzuleiten- 👍 oder 👎 ?



  • setDataGuarded und setData ist im Endeffekt dasselbe wie mehrere Signals anzubieten? Eine Variante, die benachrichtigt, und eine, die es evtl. nicht tut? Ich seh grad keinen grundlegenden Unterschied.


Anmelden zum Antworten