QML Layout



  • Ich hab jetzt wieder angefangen, etwas mit QML rumzuspielen. Diese Komponente habe ich vor etwa einem Jahr geschrieben, und krieg sie nach wie vor nicht zum Laufen. Hier sind einige unterwegs, die sich viel besser mit QML auskennen, vielleicht hat ja jemand eine Idee 🙂

    Die Idee ist eigentlich recht einfach, ich habe "Einstellungen", das ist eine Liste von "Blöcken", und jeder Block hat "Parameter". Für Parameter kommen dann je nach Typ unterschiedliche Widgets hin, aber erstmal will ich das ganze als Liste untereinander darstellen.
    Und mein Problem ist, dass sich alle Blöcke überlagern. Also alles, was im blockDelegate im Column (hatte auch schon ein ColumnLayout probiert) drin ist, liegt übereinander, und nicht untereinander.

    import QtQuick 2.6
    import QtQuick.Layouts 1.13
    import QtQuick.Controls 2.6
    
    Item {
        property var settings
        id: container
    
        ListView {
            id: blockList
            model: container.settings
    
            anchors.fill: parent
            delegate: blockDelegate
        }
    
        Component {
            id: blockDelegate
            Column {
                anchors.fill: parent
                Text {
                    text: name
                    color: "white"
                }
    
                Loader {
                    property var paramModel: paramNames
                    sourceComponent: paramDelegate
                    onStatusChanged: if (status === Loader.Ready) item.model = paramModel
                }
            }
        }
    
        Component {
            id: paramDelegate
            ColumnLayout {
                property alias model: paramRepeater.model
                Repeater {
                    id: paramRepeater
                    delegate: Row {
                        Text {
                                x: 20
                                height: 20
                                width: 100
                                text: modelData.displayName
                                color: "white"
                        }
                    }
                }
            }
        }
    }
    


  • In einem Listview funktioniert anchor.fill nicht.

    Nutz stattdessen width: parent.width wenn das item die komplette breite des Listview einnehmen soll.

    Siehe auch das "Qt Quick Application - Scroll" template vom QtCreator:

    import QtQuick 2.12
    import QtQuick.Controls 2.5
    
    ApplicationWindow {
        visible: true
        width: 640
        height: 480
        title: qsTr("Scroll")
    
        ScrollView {
            anchors.fill: parent
    
            ListView {
                width: parent.width
                model: 20
                delegate: ItemDelegate {
                    text: "Item " + (index + 1)
                    width: parent.width
                }
            }
        }
    }
    


  • Hmm, okay. Habs geändert, hat zumindest bei diesem Problem nicht geholfen. Fällt dir sonst was auf?



  • Irgendwelche anderen Vorschläge, wie man das (einfacher) machen könnte? Ich brauche auch die ListView nicht unbedingt. Ich will einfach nur eine Liste von gruppierten Einstellungen untereinander darstellen.



  • Also:

    Block1
    Param1
    Param2
    Block2
    Param1
    Param2
    Param3
    Block3
    ...

    so?

    Dann kommt es noch etwas auf die Anforderungen an:

    • Wie soll die Liste den Platz nutzen?
      • Hat sie einen ganzen Bildschirm / Container für sich und soll die Blöcke dort gleichmäßig verteilen und dynamisch das Spacing zwischen den ändern (Also responsive sein)
      • Soll die Liste eine Maximal Größe haben und einfach alles untereinander darstellen. Wenn der Platz zu groß ist, dass ganze dann Scrollable?
    • Wie sieht es mit der Dynamik aus? Stehen alle Items zu Beginn fest, werden Einträge dynamisch gelöscht / hinzugefügt?

    Davon abängig ist im Endeffekt, ob es eine ListView oder ein Repeater, eine Column oder ein ColumnLayout wird etc.

    Hier mal 4 verschiedene Varianten, wovon die ersten drei zum selben visuellen Ergebnis führen (aber dennoch nicht equivalent sind)

    import QtQuick 2.6
    import QtQuick.Layouts 1.13
    import QtQuick.Controls 2.6
    
    ApplicationWindow {
        id: root 
        width: 400; height: 400
        visible: true 
        
        Pane {
            id: container
            anchors.fill: parent
        
            ListView {
                anchors.fill: parent
    
                model: ListModel {
                    ListElement { name: "Block1"; paramModel: [ ListElement {displayName: "Param1"}, ListElement {displayName: "Param2"}] }
                    ListElement { name: "Block2"; paramModel: [ ListElement {displayName: "Param1"}] }
                    ListElement { name: "Block3"; paramModel: [ ListElement {displayName: "Param1"}, ListElement {displayName: "Param2"}, ListElement {displayName: "Param3"}] }
                }
    
                delegate: Column {
                    Text {
                        text: name
                    }
                    
                    ListView {
                        width: contentItem.childrenRect.width
                        height: contentItem.childrenRect.height
                        
                        model: paramModel 
                        
                        delegate: Text {
                            text: displayName
                        }
                    }
                }
            }
        }
    }
    
    // ApplicationWindow {
    //     id: root 
    //     width: 400; height: 400
    //     visible: true 
    //     
    //     Pane {
    //         id: container
    //         anchors.fill: parent
    //     
    //         ListView {
    //             anchors.fill: parent
    // 
    //             model: ListModel {
    //                 ListElement { name: "Block1"; paramModel: [ ListElement {displayName: "Param1"}, ListElement {displayName: "Param2"}] }
    //                 ListElement { name: "Block2"; paramModel: [ ListElement {displayName: "Param1"}] }
    //                 ListElement { name: "Block3"; paramModel: [ ListElement {displayName: "Param1"}, ListElement {displayName: "Param2"}, ListElement {displayName: "Param3"}] }
    //             }
    // 
    //             delegate: Column {
    //                 Text {
    //                     text: name
    //                 }
    //                 
    //                 Repeater {
    //                     model: paramModel 
    //                     
    //                     delegate: Text {
    //                         text: displayName
    //                     }
    //                 }
    //             }
    //         }
    //     }
    // }
    
    // ApplicationWindow {
    //     id: root 
    //     width: 400; height: 400
    //     visible: true 
    //     
    //     Pane {
    //         id: container
    //         anchors.fill: parent
    //         
    //         Column {
    //             anchors.fill: parent
    //             
    //     
    //             Repeater {
    //                 model: ListModel {
    //                     ListElement { name: "Block1"; paramModel: [ ListElement {displayName: "Param1"}, ListElement {displayName: "Param2"}] }
    //                     ListElement { name: "Block2"; paramModel: [ ListElement {displayName: "Param1"}] }
    //                     ListElement { name: "Block3"; paramModel: [ ListElement {displayName: "Param1"}, ListElement {displayName: "Param2"}, ListElement {displayName: "Param3"}] }
    //                 }
    // 
    //                 delegate: Column {
    //                     Text {
    //                         text: name
    //                     }
    //                     
    //                     Repeater {
    //                         model: paramModel 
    //                         
    //                         delegate: Text {
    //                             text: displayName
    //                         }
    //                     }
    //                 }
    //             }
    //         }
    //     }
    // }
    
    // ApplicationWindow {
    //     id: root 
    //     width: 400; height: 400
    //     visible: true 
    //     
    //     Pane {
    //         id: container
    //         anchors.fill: parent
    //         
    //         ColumnLayout {
    //             anchors.fill: parent
    //             
    //     
    //             Repeater {
    //                 model: ListModel {
    //                     ListElement { name: "Block1"; paramModel: [ ListElement {displayName: "Param1"}, ListElement {displayName: "Param2"}] }
    //                     ListElement { name: "Block2"; paramModel: [ ListElement {displayName: "Param1"}] }
    //                     ListElement { name: "Block3"; paramModel: [ ListElement {displayName: "Param1"}, ListElement {displayName: "Param2"}, ListElement {displayName: "Param3"}] }
    //                 }
    // 
    //                 delegate: Column {
    //                     Text {
    //                         text: name
    //                     }
    //                     
    //                     Repeater {
    //                         model: paramModel 
    //                         
    //                         delegate: Text {
    //                             text: displayName
    //                         }
    //                     }
    //                 }
    //             }
    //         }
    //     }
    // }
    
    


  • Danke, mit einer einfacheren Variante komme ich schon mal etwas weiter 🙂 Muss da noch etwas rumprobieren...

    Mir ist auch wieder eingefallen, warum ich das so kompliziert gemacht hatte. Ich hatte damals die Idee, je nach Einstellungstyp unterschiedliche Widgets zu erstellen, und dann hab ich geschaut, wie man das macht und bin auf sowas gekommen. Ist aber zur Zeit nicht so wichtig.



  • Kein Problem 🙂

    Das wäre durchaus eben auch möglich, indem du dann im Delegate (Zeile 34) kein Text Element nimmst, sondern eben einen Container mit nem Loader Element drin. Und dann hast du Daten in deinem Model mit denen du dann weißt, was genau du laden musst. Es ist denke ich einfacher und auch flexibler, wenn der Loader ganz innen ist statt wie bei dir auf Block Ebene eins höher.



  • Das Problem ist, dass ich bei QML den grundlegenden Aufbau einfach nicht verstehe. Mir ist nicht klar, wie das alles konkret zusammenspielt.
    Ich hatte schon mit so vielen GUI Frameworks zu tun, und jetzt hab ich endlich eins gefunden, das ich nicht kapiere...

    Ich hab dein erstes Beispiel genommen, das funktioniert so.
    Dann ersetze ich das ListModel wieder durch container.settings (ein QStandardItemModel) und es funktoniert nicht mehr, kommt in der Konsole, dass er name und paramNames nicht kennt. Warum? Das hat doch vorher mit meinem kaputten Layout auch funktioniert. Wo ist der Unterschied?

    Das ist auch genau das, was mich an QML stört. Ich finds einfach undurchsichtig und komm nicht dahinter, wie das funktioniert. Und da ist so viel Code und Dynamik dazwischen, dass man da auch schlecht reindebuggen kann (außer, man weiß schon, wie das funktioniert, und was man debuggen muss).



  • Dieser Beitrag wurde gelöscht!


  • @Mechanics Kannst du mal dein Model zeigen?



  • Den eigentlichen Code würde ich ungern zeigen...
    Die roleNames setze ich, bzw. habe ich überschrieben. Ansonsten werden QStandardItem´s erstellt und ins Model gehängt (und die haben Daten unter den entsprechenden Rollen).

    Was interessant ist, in roleNames kommt er nicht rein. Wenn ich das auf meinen ursprünglichen Aufbau reverte, kommt er in roleNames rein.
    Das geht schon vorher beim Binding schief...

    Das property settings wird aus einer anderen Qml gesetzt. Da gebe ich objectName in der Konsole aus, das ist auch das, was ich erwarte.



  • Basierend auf deinen Beschreibungen kann ich nicht sagen, woran das jetzt genau liegt. Ich würde das ganze mal vereinfachen.

    Wenn du Zeile 28 - 37 mal auskommentierst, also die ListView in der ListView klappt es dann? Oder meckert er, dass er immer noch das "name" in Zeile 25 nicht kennt?
    Sollte er da meckern, würde ich "name" in Zeile 25 mal durch einen statischen String ersetzen und gucken, ob es dann funktioniert, um schon mal sicher zu sein, dass er das model lädt.



  • Bin ich blöd 😞
    Ich hatte noch ein Element dazwischengeschoben, damit war mein property settings auf dem falschen Element deklariert.

    Jetzt klappt das, aber mit displayName hat er noch ein Problem. Das ist eine QVariantList, und die Element da drin sind QGadgets. Muss aber eigentlich wieder ein anderes Problem sein, hatte ja auch schon geklappt.
    Aber das schau ich mir morgen an.



  • Deine Kritik kann ich trotzdem unterschreiben, das debuggen fällt mir bei QML auch schwer. Meistens läuft es auf ein Ausprobieren hinaus ... schrittweises auskommentieren bzw. beim entwickeln einfach anfangen und direkt immer testen, um Probleme frühzeitig zu entwickeln.
    Sobald ich dann mal ein Code Schnippsel umschreibe, funktioniert es oft genug nicht und man sucht lange nach dem Fehler.

    Zusätzlich vermittelt QML auch den Eindruck, man müsste keine Ahnung haben, was man da macht ... so deklerativ kann ja jeder und so. Bei QML kommt man aus meiner Sicht nicht drum herum zu lernen wie es wirklich geht.



  • Ja, vor allem ist mir der Aufbau auch tatsächlich nicht klar. Selbst bei WPF (und das dürfte nochmal komplizierter sein) fand ich das logisch und gut nachvollziehbar, wie was aufgebaut oder aufzubauen ist. Bei QML erschließt sich mir vieles einfach nicht und schaut dann nach Black Magic aus.

    Ich debug das jetzt seit zwei Stunden und kapiere immer noch nicht, warum das mit der QVariantList und den Q_GADGETS nicht geht.
    Alles was ich bisher angeschaut habe, geht. Er holt aus dem QStandardItemModel die QVariantList raus, konvertiert die Liste und alle Elemente in der Liste (Q_GADGET) in QJSvalue Objekte.
    Dann kommt QQuickItemView::setModel, er bekommt ein QJSValue rein und macht wieder eine QVariantList draus, wo die richtigen Objekte drin sind.
    Alles total umständlich mit vielen unnötigen Kopien usw. Aber es geht.

    Dann kommt QQuickListViewPrivate::addVisibleItems. Und das createItem hier geht schief:

    if (!(item = static_cast<FxListItemSG*>(createItem(modelIndex, incubationMode))))
    

    Und das ist nicht mehr lustig zum Debuggen.

    Das ist der Callstack von createItem bis zu der Exception:

     	Qt5Qmld.dll!QV4::ExecutionEngine::throwReferenceError(const QV4::Value & value) Line 1200	C++
     	Qt5Qmld.dll!QV4::Lookup::resolveGlobalGetter(QV4::ExecutionEngine * engine) Line 135	C++
     	Qt5Qmld.dll!QV4::QQmlContextWrapper::getPropertyAndBase(const QV4::QQmlContextWrapper * resource, QV4::PropertyKey id, const QV4::Value * receiver, bool * hasProperty, QV4::Value * base, QV4::Lookup * lookup) Line 356	C++
     	Qt5Qmld.dll!QV4::QQmlContextWrapper::resolveQmlContextPropertyLookupGetter(QV4::Lookup * l, QV4::ExecutionEngine * engine, QV4::Value * base) Line 478	C++
     	Qt5Qmld.dll!QV4::Moth::VME::interpret(QV4::CppStackFrame * frame, QV4::ExecutionEngine * engine, const char * code) Line 641	C++
     	Qt5Qmld.dll!QV4::Moth::VME::exec(QV4::CppStackFrame * frame, QV4::ExecutionEngine * engine) Line 511	C++
     	Qt5Qmld.dll!QV4::Function::call(const QV4::Value * thisObject, const QV4::Value * argv, int argc, const QV4::ExecutionContext * context) Line 69	C++
     	Qt5Qmld.dll!QQmlJavaScriptExpression::evaluate(QV4::CallData * callData, bool * isUndefined) Line 211	C++
     	Qt5Qmld.dll!QQmlBinding::evaluate(bool * isUndefined) Line 209	C++
     	Qt5Qmld.dll!QQmlNonbindingBinding::doUpdate(const QQmlJavaScriptExpression::DeleteWatcher & watcher, QFlags<enum QQmlPropertyData::WriteFlag> flags, QV4::Scope & scope) Line 245	C++
     	Qt5Qmld.dll!QQmlBinding::update(QFlags<enum QQmlPropertyData::WriteFlag> flags) Line 187	C++
     	Qt5Qmld.dll!QQmlBinding::setEnabled(bool e, QFlags<enum QQmlPropertyData::WriteFlag> flags) Line 552	C++
     	Qt5Qmld.dll!QQmlObjectCreator::finalize(QQmlInstantiationInterrupt & interrupt) Line 1385	C++
     	Qt5Qmld.dll!QQmlIncubatorPrivate::incubate(QQmlInstantiationInterrupt & i) Line 345	C++
     	Qt5Qmld.dll!QQmlEnginePrivate::incubate(QQmlIncubator & i, QQmlContextData * forContext) Line 91	C++
     	Qt5Qmld.dll!QQmlComponentPrivate::incubateObject(QQmlIncubator * incubationTask, QQmlComponent * component, QQmlEngine * engine, QQmlContextData * context, QQmlContextData * forContext) Line 1097	C++
     	Qt5Qmld.dll!QQmlDelegateModelPrivate::object(QQmlListCompositor::Group group, int index, QQmlIncubator::IncubationMode incubationMode) Line 1081	C++
     	Qt5Qmld.dll!QQmlDelegateModel::object(int index, QQmlIncubator::IncubationMode incubationMode) Line 1115	C++
     	Qt5Quickd.dll!QQuickItemViewPrivate::createItem(int modelIndex, QQmlIncubator::IncubationMode incubationMode) Line 2292	C++
    

    Ich hab da auch nicht so ganz gerafft, was er mit den Daten aus dem Model macht. Das createItem muss ja erstmal ein Text Item erstellen. Und der Zugriff auf das displayName erfolgt erst innerhalb vom Text Item. Aber wie das jetzt durchgereicht wird, habe ich erstmal nicht gesehen. Hab jetzt aber erstmal wieder keine Lust mehr. Ich dreh noch durch damit...

    Fällt jemandem mit den Informationen vielleicht noch was offensichtliches auf, wo ich mich vertan habe? Es muss ja eigentlich was offensichtliches im QML Markup sein.



  • Ohne vollständiges minimales Beispiel wird dir wohl keiner helfen können.



  • Ok, habs jetzt noch schnell durch Raten hinbekommen, es geht mit modelData (modelData.displayName).

    Finde ich vom Namen her auch nicht abwegig, nur verstehe ich das nicht ganz. In der Doku steht:

    "Models that do not have named roles (such as the QStringList model shown below) will have the data provided via the modelData role. The modelData role is also provided for models that have only one role. In this case the modelData role contains the same data as the named role."

    Das würde erklären, warum name und paramNames gehen, die kommen aus einem QStandardItemModel und ich habe role names definiert.

    D.h. im Endeffekt, das geht wirklich nur mit Models und roleNames, und ein QObject oder ein Q_GADGET (die auch auslesbare Property Namen haben) brauchen immer ein modelData?

    @firefly : Mimimalbeispiel wäre das:

    Das settings ist dabei ein QStandardItemModel, und paramNames (der Name macht wenig Sinn, hatte Leon in seinem Beispiel besser genannt) ist eine QVariantList mit Q_GADGET Objekten. Die haben ein Property displayName und damit hatte ich eben Probleme, bis ich versuchshalber modelData dazugeschrieben hatte.

    Pane {
            Layout.alignment: Qt.AlignTop
    
            ListView {
                width: contentItem.childrenRect.width
                height: contentItem.childrenRect.height
    
                model: container.settings
    
                delegate: Column {
                    Text {
                        text: name
                    }
                    
                    ListView {
                        width: contentItem.childrenRect.width
                        height: contentItem.childrenRect.height
                        
                        model: paramNames
                        
                        delegate: Text {
                            text: modelData.displayName
                        }
                    }
                }
            }
        }
    


  • Ja, siehe https://doc.qt.io/qt-5/qtquick-modelviewsdata-cppmodels.html#qobjectlist-based-model.

    Es hält dich grundsätzlich aber keiner davon ab ein Model in einem Model zu verwenden (passend zu deiner View in einer View). In dem Artikel oben kannst du ja auch nachlesen, was die Vorteile davon sein könnten. Da kommt es halt auf deinen Anwendungsfall an.


Anmelden zum Antworten