QT und Templates
-
Das ein Widget auch ein template sein muss, da es ein Klassentemplate als member referenziert ist falsch. Es mag vielleicht deiner Meinung so sein.
Wenn diese Seitenhiebe nicht aufhören, dann lese ich Deine Beiträge einfach nicht mehr. Hältst Du mich für so blöd, dass ich nicht weiß, dass Folgendes funktioniert?
class SomeClass : public QWidget { private: SomeTemplate<int> elem; };
Selbstverständlich ging es - wie auch schon in mindestens 3 Codes in diesem Thread dargestellt - darum, dass der Templateparameter nicht fixiert ist. Verzichtet man zusätzlich auf gemeinsame Basisklasse - was dann aber einfach Laufzeitpolymorphie wäre - geht es eben nicht ohne. Edit: Halt stimmt nicht, CRTP geht natürlich auch noch, aber ich hoffe, das wird hier nicht ernsthaft erwogen (falls doch hier, warum das Käse ist: ich müsste neue Klassen nebst Vererbungshierarchie basteln, nur damit meine UI-Klasse meine Backend-Klasse nutzen könnte? Erscheint ziemlich viel Boilerplate für nichts; wieso nicht Backend-Klassen mit CRTP? Steht unten)
Da du meine Frage nicht beantwortet hast... hier noch einmal etwas abgewandelt:
Wie genau sieht dein Model Konzeptionell aus?(keine konkrete Implementation)
Und wie steht das UI-Element dazu in Beziehung?Ich kann es nicht genauer spezifizieren, ohne Code zu bringen oder hier einen seitenweisen Roman nur dafür zu schreiben und dann müsstest Du Dich noch über Backgrounds von der Thematik informieren - daher führt der Code auf Mechanics Frage zu mehr, der Dir vielleicht auch hilft (s.u.).
Mechanics:
In eurer Sidediscussion bin ich komplett Deiner Meinung. Ich finde aber immer noch die Antworten auf den Stackoverflow-Artikel interessant und schlüssig. Dort wird häufig zwischen lokalem (QT) Verständnis von MVC und vom globalen (applikationsweit) unterschieden. In ersterem sind die Models durchaus "Models", denn sie hier als Controller zu bezeichnen wäre insofern falsch, als dass die Controller (in der QT-Welt) ja mit den Views ohnehin verschmolzen sind. Somit kann es nicht sein, dass es beide sind. Applikationsweit betrachtet - was imo auch interessanter ist, wenn man QT nur als UI-Framework sieht - sind die Models für mich auch eher Controller.Mechanics:
Okay, ich skizziere nochmal kurz was auf die Schnelle. Aber damit wir hier überhaupt zu was kommen, bitte ich Mal nicht zu hinterfragen, dass ich meinen Proxy so gestaltet habe, wie ich es habe.// Nach Mechanics und meiner Definition eine Model-Klasse, sonst Data oder so class SomeClass { public: typedef std::vector<int> NumberType; // trivial const NumberType& GetNumbers() const {return numbers;} public: NumberType numbers; }; // filtert; macht in echt mehr, sodass das Proxydesign imo sehr vernünftig ist; // CRTP will ich nicht, weil ich damit const-correctness (auf Ebene von Proxy als auch verwiesenes Objekt) nicht erzeugen kann wie es z.B. auch bei einem iterator geht; // so oder so, nicht Gegenstand der Diskussion, dass ich hier nen Proxy verwende; // auch das gehört noch zur Model-Schicht template<typename SomeClassPtr> class SomeClassFilterProxy { public: // nutzt someClass->GetNumbers() aber filtert Primzahlen raus const NumberType& GetNumbers() const; // ist in echt natürlich besser gelöst als hier so plump; // es geht darum, dass SomeClassFilterProxy<...> x(&someObj); mit // x->GetNumbers() funktioniert, ein Proxy sich also bei mir wie ein Zeiger verhält SomeClassFilterProxy* operator->() {return this;} SomeClassFilterProxy(SomeClassPtr someClass) : someClass(someClass) {} private: SomeClassPtr someClass; }; // Jetzt UI (M)VC-Klasse (M für firefly's Definition, nach Mechanics und mir eher C, aber ihr wisst, was ich meine, hoffe ich) template<typename SomeClassPtr> class SpecialDisplayTable : public QLabel { // das Ding stellt die Zahlen in einer Matrix dar, also erste Zeile 1-10, // zweite 11-20 oder so; // die Filtervariante ist aber öffentlich, es graut Zahlen aus, die durch Filter // ausgefiltert werden (dafür habe ich jetzt oben keine Methode skizziert, // es geht nur darum, dass es mehr als nur die GetNumbers()-Methode gibt) // und ein QLabel ist es, weil Zeichnen und Mausklicks über Felder hinweg // gehandhabt werden uvm. public: // parent Gedöns ausgespart SpecialDisplayTable(SomeClassPtr someClass) : someClass(someClass) {} void paintEvent(QPaintEvent* event) { // das nutzt hier jetzt alle möglichen Methoden von someClass aus, // es gibt auch weitere Proxys, das ist hier jetzt nur schematisiert // irgendwo jedenfalls: auto filteredOrUnfilteredNumbers/*depending on SomeClassPtr*/ = someClass->GetNumbers(); // läuft für SomeClassPtr == SomeClass* or == SomeClassFilterProxy/* ohne * */ } private: SomeClassPtr someClass; }; // irgendwo in einem übergeordneten Widget aka die "Anwendung des Tables" { SomeClass someNiceNumbers; // wir nehmen an, da stecken jetzt auf wundersame Weise auch Zahlen drin auto someNiceNumbersFiltered = makeSomeClassFilterProxy(&someNiceNumbers); // makeSCFP nicht definiert, aber Funktionsweise ist ja von shared_ptr u.ä. bekannt // jetzt geht beides und das ist für viele Fälle bei mir äußerst praktisch SpecialDisplayTable* table1 = new SpecialDisplayTable(&someNiceNumbers); SpecialDisplayTable* table2 = new SpecialDisplayTable(someNiceNumbersFiltered); // hier kommt noch mehr }
Das ist wieder nur ein Ausschnitt, aber ich hoffe, der repräsentiert jetzt genug. Abweichungen zum eigentlichen Fall sind:
- Es gibt wesentlich mehr Methoden, die vom SpecialDisplayTable auch genutzt werden (noch mehr Sonderfarben, Verbindung über einen anderen Proxy zu einem Tree, was Spezialisierung/andere Traits nach sich zieht, diverse Manipulationsmethoden je nach Maustaste und Strg/Shift/Alt-Dazudruck usw. usf.)
- Der Proxy ist nur einer der möglichen Proxys und der hier auch aufs filtern reduziert, daher installiere ich in SomeClass auch nicht einfach einen Filter übersetFilter(Filter* someFilter)
, was sonst natürlich eleganter wäre
- Es gibt mehr als nur den SpecialDisplayTable, es gibt auch ein QLineEdit, das ein paar Dinge mehr anzeigt (sagen wir z.B. ein kleines Rechteck im rechten Bereich des Textfeldes, wenn es ein Primzahlfilter ist, mit dem Text "Primzahl"), das nutzt als Zugriff jedoch einen "StringAccessor", der wiederum über SomeClassPtr zugreift; aber das ist hier vermutlich irrelevant
- ... (wichtig?)Ich hoffe, diese Untermenge meines Problems findet einen guten Kompromiss zwischen "nicht erschlagend" und "konkret genug". Falls nicht, füge ich sonst noch was hinzu (d.h. im Falle von meiner falschen Annahme bzgl. "nicht erschlagend" wohl besser nicht
).
-
Eisflamme schrieb:
Das ist wieder nur ein Ausschnitt, aber ich hoffe, der repräsentiert jetzt genug.
Hmm, du hast mich noch nicht überzeugt, warum du in der GUI Templates brauchst. Es geht doch eigentlich um SpecialDisplayTable? Die einzige Stelle, wo du da deinen Templateparameter verwendest, ist die Stelle, um an "NumberType" zu kommen. Das ist aber schon wieder für alle Templateargumente gleich. Warum übergibst du dann nicht einfach dein NumberType an das Widget? Mag sein, dass du den Templateparameter noch an anderen Stellen brauchst, solltest du dann hier auch mal zeigen, sonst ist mir das noch nicht klar. Ist jetzt auch nicht komplett durchdacht, ich überlege beim Schreiben.
Was du jetzt als Beispiel gebracht hast, würde aber wieder gut zu dem Model/View Konzept von Qt passen. Vielleicht ist dein eigentlicher Anwendungsfall anders, aber zumindest bei dem Beispiel seh ich jetzt nicht, warum man nicht die Qt Methoden nutzen sollte. Du hast also eine SpecialDisplayTable, die eine Matrix mit Zahlen darstellen soll. Spricht was dagegen, ein AbstractItemView draus zu machen? Sorry, wenn das schon kam, ich hab die ersten Posts nicht sehr aufmerksam verfolgt.
Dann könntest du ein Qt Model übergeben und daraus die Daten ziehen. Und da könntest du statt deinem eigentlichen Model problemlos ein FilterModel übergeben. Das Filtermodel könnte z.B. data überschreiben und für ForegroundRole z.B. grau zurückgeben. Damit wäre auch die Logik von der Darstellung entkoppelt und deine View müsste nicht selber entscheiden, wann ein Element "grau" ist, sondern würde diese Information transparent vom Model bekommen und das implementiert wiederum die Geschäftslogik hier (im FilterModel) und sagt, das ist grau darzustellen. Warum kann dem View egal sein.
Dein QLineEdit müsstest du jetzt nicht unbedingt von AbstractItemView ableiten, hört sich auf den ersten Blick übertrieben an. Aber da brauchst du auch nicht viele Extrainformationen? Vielleicht kann man ja ein std::function Objekt übergeben, das die eine Zusatzinformation liefert, die du brauchst. Und dann übergibst du da eben eine Methode von deiner Templateklasse, die du jetzt ja auch nicht mehr brauchen würdest.
Also, vielleicht machen Templates hier schon Sinn, aber ich hoffe du siehst, warum du mich noch nicht überzeugt hast.
-
@firefly: ja, was man als Modell betrachtet und wo man die Geschäftslogik implementiert kann man natürlich unterschiedlich sehen und auch unterschiedlich implementieren. Für mich ist (wenn ich meine gesamte Anwendung betrachte) das Modell eben das "Datenmodell" von meinem Programm, also das, was ich im Code nutzen würde.
Eine Person kann durchaus ein trivialer DTO sein. Und die Geschäftslogik ist vielleicht gar nicht in der Klasse Person implementiert sondern in völlig anderen Klassen, z.B: IContactExporter::exportContact(const Person& person) mit einer Implementierung OutlookContactExporter. Aber dann wäre es auf jeden Fall immer noch angenehmer und logischer person.name, person.age usw. zu schreiben, statt person->data(PersonModel::NameColumn).toString(), person->data(PersonModel::AgeColumn).toInt() usw. Damit wäre die Klasse Person das Modell und nicht PersonModel. Und selbst wenn du keine "öffentliche" Klasse Person verwendest, sondern tatsächlich nur ein PersonModel (abgeleitet von QAbstractItemModel) anbietest, wie würdest du da intern die Daten speichern? Doch wohl kaum in einer QMap<int, QVariant>? Eher intern ein struct Person. Und dann kann man es auch gleich öffentlich anbieten und das PersonModel als Wrapper drum rum sehen.
Je komplexer die Daten werden, desto weniger eignet sich QAbstractItemModel dafür. Wir entwickeln z.B. Software im CAD Bereich und als "Modell" könnte man bei uns Bauteile und deren Aufbau betrachten. Es gibt Assemblies, Einzelteile, Konfigurationen, Features usw. Und das ganze ist ein Baum. In dem Modell ist jetzt nicht viel Logik an sich implementiert. Aber es sind grundlegende Datenstrukturen, die überall benutzt werden. Und das seh ich eben als Model. Ein von QAbstractItemModel abgeleitetes AssemblyItemModel für die Darstellung in einer Baumansicht gibts natürlich auch, aber das ist nur ein Adapter, das ist kein Model und wird nur für die GUI verwendet. Und überall, wo man in der GUI was mit den Teilen machen will (also im eigentlichen Controller), holt man dann doch die richtigen "Modellklassen" aus dem ItemModel.
Der Controller ist in MVC am schwammigsten definiert. Aus meiner Sicht gibt es dann eigentlich mindestens zwei Controller, das QAbstractItemModel und den GUI Code, der das ganze zusammenhält, also z.B. EventHandler von irgendwelchen Buttons, die dann irgendwelche Aktionen ausführen. Natürlich ist ein QAbstractItemModel kein "richtiger" Controller, aber ich hab echt kein Problem damit, das als Controller zu bezeichnen. Das ist für mich halt eher Controller als Modell.
-
Grundsätzlich kannst du natürlich auch in Qt Widgets als templates verwenden.
Nur hast du dabei folgende Einschränkungen (zumindestens für Qt4):Signal/Slots funktionieren nicht, da (nach meinem Wissen) in Qt4 ein connect zwischen einem Signal und einem Slot übers Metaobject System realisiert wird.
(Dadurch ist ein fehlerhafter connect kein compiletime fehler sondern ein runtime fehler.)Wenn du auf Signale oder Slots nicht verzichten kannst, so musst du es so machen, wie du es in deinem eingangspost beschrieben hast.
Anders wird es nicht gehen. Das ist eine Einschränkung in Qt4.In Qt5 kannst du als Slot auch z.b. lambdas oder beliebiege Funktionen verwenden
http://qt-project.org/wiki/New_Signal_Slot_Syntax
-
Mechanics schrieb:
Eisflamme schrieb:
Das ist wieder nur ein Ausschnitt, aber ich hoffe, der repräsentiert jetzt genug.
Hmm, du hast mich noch nicht überzeugt, warum du in der GUI Templates brauchst. Es geht doch eigentlich um SpecialDisplayTable? Die einzige Stelle, wo du da deinen Templateparameter verwendest, ist die Stelle, um an "NumberType" zu kommen.
Ich habe nun wirklich mehrfach beschrieben, dass ich es darauf reduziert habe und es eigentlich 5-6 Methoden sind. Dass es sonst mit einem std::function erledigt wäre, habe ich auch verstanden und in diesem Thread mindestens drei Mal angemerkt... Hm, andere Informationen beziehen sich wirklich aufs Schreiben, Lesen und auf Informationen, die zur Darstellung notwendig sind.
Spricht was dagegen, ein AbstractItemView draus zu machen? Sorry, wenn das schon kam, ich hab die ersten Posts nicht sehr aufmerksam verfolgt.
Ich bin damit nicht so besonders firm, aber wieso überhaupt ItemView (und nicht TableView)? Und wenn ich die Maus gedrückt halte und über mehrere Felder gehe, kann ich dann Mehrfachmarkierungen machen? Auch für die Grafik: Ich glaube, ich müsste mehr mit Stylesheets und speziellen Event-Überladungen arbeiten, als es Aufwand ist, einfach gleich selbst ein View basierend auf einem Label zu basteln.
Dann könntest du ein Qt Model übergeben und daraus die Daten ziehen. Und da könntest du statt deinem eigentlichen Model problemlos ein FilterModel übergeben.
Gut, dann gibt es noch zwei Arten des Ausgegrautseins, aber das könnte man da reinstecken. ForegroundRole mag ich nicht, ich habe für die Tabelle ein komplett eigenes Farbset, mit Farbverläufen uvm. Man könnte es dennoch entkoppeln, da hast Du Recht. Da das QT-Model aber ohnehin niemals meine Geschäftslogik ersetzt, hätte ich die Form "Geschäftslogik" <-> Model <-> View+Controller, und Model würde hier nur als Adapter (oder laut Dir Controller) fundieren. Ich gewinne dadurch aber dann nichts, wenn M : V+C dem Verhältnis 1:1 entspricht, was auf Grund des Spezialisierungsgrades meiner Anzeige der Fall ist (und bleiben wird).
Entkopplung ist für diesen Fall kein Selbstzweck (oh, da ist die Menge empört :)), wo habe ich hier wirklich Vorteile?
Vielleicht kann man ja ein std::function Objekt übergeben, das die eine Zusatzinformation liefert, die du brauchst.
Habe ich schon mehrfach oben diskutiert, ich bräuchte viel mehr als eins.
Also, vielleicht machen Templates hier schon Sinn, aber ich hoffe du siehst, warum du mich noch nicht überzeugt hast.
Ja, sehe ich.
Resultiert aber imo daraus, dass ich hier nicht mein Gesamtprojekt offenlege, sondern nur einen Teil, was aber auch bei der Komplexität imo kaum anders geht. Aber es ist gut zu checken, ob andere wirklich anders denken oder ob mein Fall tatsächlich ein Spezialfall ist. Ich komme immer mehr zur Schlussfolgerung, dass letzteres der Fall ist. Trotzdem lerne ich hier einiges, ist also in jedem Fall wertvolles Informationsmaterial.
Dazu auch Deine passende Aussage:
Ein von QAbstractItemModel abgeleitetes AssemblyItemModel für die Darstellung in einer Baumansicht gibts natürlich auch, aber das ist nur ein Adapter, das ist kein Model und wird nur für die GUI verwendet.
Meine eigentlichen Daten sind in gewisser Weise schon vielfältig. Tabellenform ist Anzeigeform, Textform ist Anzeigeform oder anders: Es sind Repräsentationen der Daten (es gibt noch eine Dritte). Die Daten selbst sind auch in einem gewissen Datenschema abgelegt, doch das möchte ich ggü. dem QT-Model natürlich blackboxen. Und QT-Models als Adapter für die Anzeige zu verwenden, ist genau richtig. Individuelle und komplexe Typen mit einer stereotypischen Klasse für Listen/Tabellendaten zu erschlagen bzw. da hineinzupressen empfinde ich als schlechten Stil. Wenn es perfekt passt, könnte man es tun, aber selbst dann finde ich es besser Geschäftslogik nochmal für sich abzubilden und erst dann ein QT-Model dazwischenzulegen, auch wenn die Abbildung hier mehr oder minder 1:1 wäre. Grund: Alle Geschäftslogikdaten sind QT-unabhängig und v.a. stehen die auf einer Ebene für sich. Aber das ist für mich nur ein stilistischer Bonus, den man nicht teilen muss (aber sollte :p). Ich schweife ab.
Grundsätzlich kannst du natürlich auch in Qt Widgets als templates verwenden.
Nur hast du dabei folgende Einschränkungen (zumindestens für Qt4).Der OP dieses Threads trägt der Tatsache Rechnung, dass es die Einschränkungen gibt, sprich: Genau diese Dinge waren mir schon vor Beginn des Threads bewusst. Die Folgediskussion hat sich ja erst dadurch ergeben, dass mein Design basierend auf unvollständigen Informationen angezweifelt wurde (aber das öffnet Horizont und führt zu Lernerkenntnissen, daher ist das gar nicht so negativ gemeint, wie es vielleicht klingt :)).