C++-modellen verbinden naar uw QML gebruikersinterface
Gegevens uit de C++-backend naar de QML-frontend
Zoals getoond in de vorige handleiding, kunt u C++ code verbinden met QML door een klasse aan te maken die behandeld zal worden als nog een component in QML. U zou echter meer gecompliceerde gegevens willen representeren, zoals gegevens die moeten acteren als een klant ListModel of op de een of andere manier gedelegeerd moeten worden uit een Repeater.
We kunnen onze eigen modellen aanmaken vanaf de C++ kant en declareren hoe de gegevens uit dat model gerepresenteerd moeten worden op de QML-frontend.
Het wordt sterk aangeraden dat u de handleiding Lijstweergaven leest voor deze.
globalDrawer:Kirigami.GlobalDrawer{isMenu:trueactions:[Kirigami.Action{text:i18n("Exposing to QML")icon.name:"kde"onTriggered:pageStack.push(Qt.createComponent("org.kde.tutorial.components","ExposePage"))},Kirigami.Action{text:i18n("C++ models in QML")icon.name:"kde"onTriggered:pageStack.push(Qt.createComponent("org.kde.tutorial.components","ModelsPage"))},Kirigami.Action{text:i18n("Quit")icon.name:"application-exit-symbolic"shortcut:StandardKey.QuitonTriggered:Qt.quit()}]}
Maak dan een nieuw bestand src/components/ModelsPage.qml met de volgende inhoud:
1
2
3
4
5
6
7
8
9
importQtQuickimportQtQuick.LayoutsimportQtQuick.ControlsasControlsimportorg.kde.kirigamiasKirigamiKirigami.ScrollablePage{title:"C++ models in QML"// ...
}
En voeg het tenslotte toe aan src/components/CMakeLists.txt:
Dit dient als werkveld voor deze handleidingpagina.
Ruwe tekenreeksen gebruiken
Om deze handleiding gemakkelijker te maken te begrijpen hoe het model wordt gevuld, schakelen we een functie uit die de KDE-toepassing die extra-cmake-modules (ECM) standaard gebruiken en die tekenreekscode optimaliseert. Hierdoor hoeven we niet telkens QStringLiteral() te schrijven wanneer een string in onze C++-code wordt geïntroduceerd, wat handig zal zijn voor de code in het aankomende headerbestand.
In de hoofdmap bestand CMakeLists.txt, voeg het volgende toe:
Het uitschakelen van deze CMake-vlag is uitsluitend voor didactische doeleinden. In productiecode moet waar mogelijk QStringLiteral() of de Qt string literals namespace worden gebruikt.
De klasse voorbereiden
We zullen een klasse aanmaken die een QMap bevat, waar een QString wordt gebruikt als een sleutel en objecten QStringList worden gebruikt als waarden. De frontent zal in staat zijn de sleutels en de waarden te lezen en te tonen en eenvoudig te gebruiken net als een een-dimensionaal array. Het zou er uit moeten zien als een QML-ListModel.
Om dit te doen moeten we een klasse maken die erft van QAbstractListModel. Laten we ook enige gegevens toevoegen aan de QMap. Deze declaraties zullen gelokaliseerd zijn in model.h.
Maak twee nieuwe bestanden aan: src/components/model.h en src/components/model.cpp.
Voeg deze twee nieuwe bestanden toe aan src/components/CMakeLists.txt:
Voeg het volgende als de initiële inhoud toe aan src/components/model.h:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#pragma once
#include<QAbstractListModel>#include<qqmlintegration.h>classModel:publicQAbstractListModel{Q_OBJECTQML_ELEMENTpublic:private:QMap<QString,QStringList>m_list={{"Feline",{"Tigress","Waai Fuu"}},{"Fox",{"Carmelita","Diane","Krystal"}},{"Goat",{"Sybil","Toriel"}}};};
Natuurlijk kunnen we deze klasse niet gewoon tonen zoals deze is. We moeten aan QML ook vertellen hoe deze gegevens in de klasse te representeren. We kunnen dit doen door drie essentiële functies te overschrijven.
rowCount() - denk aan deze functie als een manier om QML te vertellen hoeveel items het model zou moeten representeren.
roleNames() - u kunt denken aan rolnamen als eigenschapnamen gekoppeld aan gegevens in QML. Deze functie biedt u het aanmaken van die rollen.
data() - deze functie wordt aangeroepen wanneer u de gegevens, die corresponderen met de rolnamen uit het model, wilt ophalen.
Notitie
De aangepaste rolnamen aangemaakt met roleNames() zijn alleen te gebruiken wanneer een model wordt gedelegeerd en zijn daar buiten niet bruikbaar. Zie Modellen en weergaven.
Notitie
Technisch worden modellen in Qt gerepresenteerd als tabellen, met rijen en kolommen. Dus, wat overschrijven van rowCount() doet aan Qt vertellen hoeveel rijen er in een model zitten. Omdat we van doen hebben met met een een-dimensionaal array in deze handleiding, kunt u gewoon denken aan "rijen" als "aantal elementen."
Overschrijven en implementeren rowCount()
Laten we de functie in het header-bestand src/components/model.h overschrijven. De rowCount() komt met zijn eigen parameter, maar het zal niet gebruikt worden in dit voorbeeld en dus hoeft het geen naam te hebben.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#pragma once
#include<QAbstractListModel>#include<qqmlintegration.h>classModel:publicQAbstractListModel{Q_OBJECTQML_ELEMENTpublic:introwCount(constQModelIndex&)constoverride;private:QMap<QString,QStringList>m_list={{"Feline",{"Tigress","Waai Fuu"}},{"Fox",{"Carmelita","Diane","Krystal"}},{"Goat",{"Sybil","Toriel"}}};};
Laten we daarna declareren hoeveel rijen er in dit model in src/components/model.cpp zijn:
Voordat we roleNames() overschrijven, moeten we declareren wat de rollen zijn aan de C++ kan met gebruik van een publieke enum. De reden hiervoor is omdat deze enum-waarden doorgegeven worden in data() elke keer dat QML toegang pakt tot een bijbehorende rol en als zodanig kunnen we data() terug laten geven wat we willen.
Laten we beginnen met de enum voor rollen aan te maken in src/components/model.h, waar elke waarde een rol is voor de C++ kant.
#pragma once
#include<QAbstractListModel>#include<qqmlintegration.h>classModel:publicQAbstractListModel{Q_OBJECTQML_ELEMENTpublic:enumRoles{SpeciesRole=Qt::UserRole,CharactersRole};QHash<int,QByteArray>roleNames()constoverride;introwCount(constQModelIndex&)constoverride;private:QMap<QString,QStringList>m_list={{"Feline",{"Tigress","Waai Fuu"}},{"Fox",{"Carmelita","Diane","Krystal"}},{"Goat",{"Sybil","Toriel"}}};};
Nadat we dat hebben vastgesteld kunnen we tenslotte aanmaken wat deze rollen zijn aan de kant an QML met een QHash waar de sleutels de enumerated waarden zijn gepaard met QByteArrays. Dit zou gaan naar src/components/model.cpp. De tekst in de QByteArray is wat wordt gebruikt in de actuele QML code.
In ons voorbeeldmodel kan de rol "soorten" gebruikt worden om de QString-sleutel "Feline", "Fox", "Goat" op te halen, elk in een gescheiden gedelegeerde. Hetzelfde kan gedaan worden met de QStringList waarden voor de karakternamenlijst.
Overschrijven en implementeren data()
Er zijn twee parameters die doorgegeven worden aan data(): index en role. De index is de positie van de gegevens in het model. Zoals eerder gesteld, role wordt gebruikt door QML om specifieke gegevens terug te krijgen wanneer er toegang toe wordt gevraagd tot een rol.
In data() kunnen we een switch statement gebruiken om de toepasselijke gegevens en gegevenstype terug te geven afhankelijk van de rol, wat mogelijk is als data() een QVariant teruggeeft. We moeten echter nog steeds zeker maken dat we de toepasselijke locatie van de gegevens krijgen. In dit onderstaande voorbeeld kunt u zien dat een nieuwe iterator-variabele gedeclareerd wordt, die is gezet vanaf het begin van de lijst plus de rij van de index en de gegevens waar de iterator naar wijst is wat wordt teruggegeven.
We kunnen echter niet gewoon teruggeven welke gegevens dan ook die we willen. We kunnen proberen gegevens te binden aan een eigenschap met een niet compatibel type gegeven, zoals een QStringList aan een QString. U moet misschien conversie van gegevens doen om het gegeven juist weer te geven. Hiervoor maken we een nieuwe private, statische functie genaamd formatList().
Dit resulteert in de volgende code in src/components/model.cpp:
#pragma once
#include<QAbstractListModel>#include<qqmlintegration.h>classModel:publicQAbstractListModel{Q_OBJECTQML_ELEMENTpublic:enumRoles{SpeciesRole=Qt::UserRole,CharactersRole};introwCount(constQModelIndex&)constoverride;QHash<int,QByteArray>roleNames()constoverride;QVariantdata(constQModelIndex&index,introle)constoverride;private:QMap<QString,QStringList>m_list={{"Feline",{"Tigress","Waai Fuu"}},{"Fox",{"Carmelita","Diane","Krystal"}},{"Goat",{"Sybil","Toriel"}}};staticQStringformatList(constQStringList&list);};
Gebruik van klasse in QML
Het QML-bestand dat wordt gebruikt zal slechts drie Kirigami.AbstractCard componenten bevatten, waar de sleutel de kop is en de waarde de inhoud. Deze kaarten worden aangemaakt door het delegeren van AbstractCard met gebruik van een Repeater, waar het aangepaste model dat we aanmaakten als het model acteert. De toegang tot gegevens is via het woord model, gevolgd door de rollen gedeclareerd in roleNames().
importQtQuickimportQtQuick.LayoutsimportQtQuick.ControlsasControlsimportorg.kde.kirigamiasKirigamiKirigami.ScrollablePage{title:"C++ models in QML"Model{id: customModel}ColumnLayout{anchors.left:parent.leftanchors.right:parent.rightRepeater{model:customModeldelegate:Kirigami.AbstractCard{header:Kirigami.Heading{text:model.specieslevel:2}contentItem:Controls.Label{text:model.characters}}}}}
Modificatie van gegevens
Gegevens bewerken met gebruik van dataChanged() en setData()
U kunt een situatie tegenkomen waar u de gegevens in het model wilt wijzigen en de wijzigingen gereflecteerd wilt zien aan de kant van de frontend. Elke keer dat we gegevens in het model wijzigen, moeten we het signaal dataChanged() uitzenden die die wijzigingen toepast aan de kant van het frontend in de specifieke cellen gespecificeerd in zijn argumenten. In deze handleiding, kunnen we gewoon het argument index van setData() gebruiken.
setData() is een virtuele functie die u kunt overschrijven zo dat wijzigen van de gegevens uit de kant van de frontend automatisch deze wijzigingen aan de kant van de backend reflecteert. Het vereist drie parameters:
index - de locatie van de gegevens.
waarde - de inhoud van de nieuwe gegevens.
role - in deze context, de rol hier wordt gebruikt om weergaven te vertellen hoe ze de gegevens zouden moeten behandelen. De rol hier zou moeten zijn Qt::EditRole.
De parameter role in dit geval wordt gebruikt om te verzekeren dat setData() kan worden bewerkt via invoer van de gebruiker (Qt::EditRole). Door index te gebruiken kunnen we dat gebruiken om de locatie te bepalen waar de gegevens zouden worden bewerkt met de inhoud van value.
#pragma once
#include<QAbstractListModel>#include<qqmlintegration.h>classModel:publicQAbstractListModel{Q_OBJECTQML_ELEMENTpublic:enumRoles{SpeciesRole=Qt::UserRole,CharactersRole};introwCount(constQModelIndex&)constoverride;QHash<int,QByteArray>roleNames()constoverride;QVariantdata(constQModelIndex&index,introle)constoverride;boolsetData(constQModelIndex&index,constQVariant&value,introle)override;private:QMap<QString,QStringList>m_list={{"Feline",{"Tigress","Waai Fuu"}},{"Fox",{"Carmelita","Diane","Krystal"}},{"Goat",{"Sybil","Toriel"}}};staticQStringformatList(constQStringList&list);};
Laten we de QML-code bijwerken zodat we een prompt kunnen openen die ons biedt het model te bewerken met een Controls.Button aangekoppeld aan de kaarten.
Voeg de volgende Kirigami.PromptDialog toe aan de src/components/ModelsPage.qml, samen met een nieuwe bewerkingsknop:
importQtQuickimportQtQuick.LayoutsimportQtQuick.ControlsasControlsimportorg.kde.kirigamiasKirigamiimportorg.kde.tutorial.componentsKirigami.ScrollablePage{title:"C++ models in QML"Model{id: customModel}ColumnLayout{anchors.left:parent.leftanchors.right:parent.rightRepeater{model:customModeldelegate:Kirigami.AbstractCard{Layout.fillHeight:trueheader:Kirigami.Heading{text:model.specieslevel:2}contentItem:Item{implicitWidth:delegateLayout.implicitWidthimplicitHeight:delegateLayout.implicitHeightColumnLayout{id: delegateLayoutControls.Label{text:model.characters}Controls.Button{text:"Edit"onClicked:{editPrompt.text=model.characters;editPrompt.model=model;editPrompt.open();}}}}}}}Kirigami.PromptDialog{id: editPromptpropertyvarmodelpropertyaliastext:editPromptText.texttitle:"Edit Characters"standardButtons:Kirigami.Dialog.Ok|Kirigami.Dialog.CancelonAccepted:{constmodel=editPrompt.model;model.characters=editPromptText.text;editPrompt.close();}Controls.TextField{id: editPromptTextonAccepted:editPrompt.accept()}}}
Nu, wanneer de waarden van het model in de frontend wijzigen, zouden de automatisch in de backend moeten worden bijgewerkt.
Het toevoegen van rijen
We hebben een manier toegevoegd om de gegevens te wijzigen in bestaande sleutels van de QMap en in de frontend, dit wordt gereflecteerd als wijzigen van de inhoud in de AbstractCards. Maar wat als we een nieuwe sleutelitem in de QMap willen toevoegen en dat gereflecteerd moet worden aan the QML-kant? Laten we dat doen door een nieuwe methode aan te maken die op te roepen is aan the QML-kant om deze taak uit te voeren.
Om de methode zichtbaar te maken in QML moeten we de methode declareren met macro Q_INVOKABLE. Deze methode zal ook een tekenreeksparameter omvatten, die bedoeld is om de nieuwe sleutel in de QMap te zijn.
#pragma once
#include<QAbstractListModel>#include<qqmlintegration.h>classModel:publicQAbstractListModel{Q_OBJECTQML_ELEMENTpublic:enumRoles{SpeciesRole=Qt::UserRole,CharactersRole};introwCount(constQModelIndex&)constoverride;QHash<int,QByteArray>roleNames()constoverride;QVariantdata(constQModelIndex&index,introle)constoverride;Q_INVOKABLEvoidaddSpecies(constQString&species);private:QMap<QString,QStringList>m_list={{"Feline",{"Tigress","Waai Fuu"}},{"Fox",{"Carmelita","Diane","Krystal"}},{"Goat",{"Sybil","Toriel"}}};staticQStringformatList(constQStringList&list);};
Binnen deze methode moeten we aan Qt vertellen dat we meer rijen in het model aan willen maken. Dit wordt gedaan door beginInsertRows() aan te roepen om onze rij toevoegen te beginnen, gevolgd door in te voegen wat we nodig hebben, gebruik daarna endInsertRows() om de bewerking te beëindigen. We moeten echter nog steeds dataChanged() aan het einde uitsturen. Deze keer gaan we alle rijen bijwerken, vanaf de eerste rij tot de laatste omdat de QMap zichzelf alfabetisch zal reorganiseren en we dat over alle rijen moeten vangen.
Bij aanroepen van beginInsertRows() moeten we eerst een QModelIndex klasse doorgeven om te de locatie te specificeren waar de nieuwe rijen toegevoegd zouden moeten worden, gevolgd door wat de nieuwe eerste en laatste rijnummers gaan worden. In deze handleiding zal het eerste argument gewoon QModelIndex() zijn omdat er geen noodzaak is de parameter hier te gebruiken. We kunnen gewoon de huidige rijgrootte voor het eerste en laatste rijnummer gebruiken, omdat we eenvoudig één rij aan het eind van het model zullen toevoegen.
De functie dataChanged() gebruikt QModelIndex als het type gegeven voor zijn parameters. We kunnen echter gehele getallen in QModelIndex typengegevens converteren met de functie index().
Laten we de QML code bijwerken zodat we de mogelijkheid geven om een nieuwe sleutel aan de QMap toe te voegen.
importQtQuickimportQtQuick.LayoutsimportQtQuick.ControlsasControlsimportorg.kde.kirigamiasKirigamiimportorg.kde.tutorial.componentsKirigami.ScrollablePage{title:"C++ models in QML"actions:[Kirigami.Action{icon.name:"list-add-symbolic"text:"Add New Species"onTriggered:{addPrompt.open();}}]Model{id: customModel}ColumnLayout{// ...
}Kirigami.PromptDialog{id: addPrompttitle:"Add New Species"standardButtons:Kirigami.Dialog.OkonAccepted:{customModel.addSpecies(addPromptText.text);addPromptText.text="";// Clear TextField every time it's done
addPrompt.close();}Controls.TextField{id: addPromptTextLayout.fillWidth:trueonAccepted:addPrompt.accept()}}Kirigami.PromptDialog{id: editPrompt// ...
}}
Nu zouden een nieuwe actie bovenaan de toepassing moeten geven die een prompt laat verschijnen waarmee een nieuw element aan het model kan worden toegevoegd, met onze eigen aangepaste gegevens.
Rijen verwijderen
De manier om rijen te verwijderen is gelijk aan rijen toevoegen. Laten we een andere methode aanmaken die we in QML zullen aanroepen. Deze keer zullen we een extra parameter gebruiken en dat is een geheel getal die het rijnummer is. De naam ervan wordt gebruikt om de sleutel uit de QMap te verwijderen, terwijl het rijnummer gebruikt zal worden om de rij in de frontend te verwijderen.
Voeg een nieuwe Q_INVOKABLE-functie met de naam deleteSpecies() toe in src/components/model.h:
#pragma once
#include<QAbstractListModel>#include<qqmlintegration.h>classModel:publicQAbstractListModel{Q_OBJECTQML_ELEMENTpublic:enumRoles{SpeciesRole=Qt::UserRole,CharactersRole};introwCount(constQModelIndex&)constoverride;QHash<int,QByteArray>roleNames()constoverride;QVariantdata(constQModelIndex&index,introle)constoverride;Q_INVOKABLEvoidaddSpecies(constQString&species);Q_INVOKABLEvoiddeleteSpecies(constQString&speciesName,constint&rowIndex);private:QMap<QString,QStringList>m_list={{"Feline",{"Tigress","Waai Fuu"}},{"Fox",{"Carmelita","Diane","Krystal"}},{"Goat",{"Sybil","Toriel"}}};staticQStringformatList(constQStringList&list);};
Met een overeenkomstige implementatie in src/components/model.cpp:
Laten we nu de toepassing bijwerken zodat een knop "Verwijderen" in een RowLayout verschijnt naast de knop Bewerken, binnen onze AbstractCard en verbindt het met uw methode voor verwijderen.
importorg.kde.kirigamiasKirigamiimportorg.kde.tutorial.componentsKirigami.Page{title:"Exposing to QML Tutorial"Kirigami.Heading{anchors.centerIn:parenttext:Backend.introductionText}}
src/components/backend.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#pragma once
#include<QObject>#include<qqmlintegration.h>classBackend:publicQObject{Q_OBJECTQML_ELEMENTQML_SINGLETONQ_PROPERTY(QStringintroductionTextREADintroductionTextWRITEsetIntroductionTextNOTIFYintroductionTextChanged)public:explicitBackend(QObject*parent=nullptr);QStringintroductionText()const;voidsetIntroductionText(constQString&introductionText);Q_SIGNALvoidintroductionTextChanged();private:QStringm_introductionText=QStringLiteral("Hello World!");};
importQtQuickimportQtQuick.LayoutsimportQtQuick.ControlsasControlsimportorg.kde.kirigamiasKirigamiimportorg.kde.tutorial.componentsKirigami.ScrollablePage{title:"C++ models in QML"actions:[Kirigami.Action{icon.name:"list-add-symbolic"text:"Add New Species"onTriggered:{addPrompt.open();}}]Model{id: customModel}ColumnLayout{anchors.left:parent.leftanchors.right:parent.rightRepeater{model:customModeldelegate:Kirigami.AbstractCard{Layout.fillHeight:trueheader:Kirigami.Heading{text:model.specieslevel:2}contentItem:Item{implicitWidth:delegateLayout.implicitWidthimplicitHeight:delegateLayout.implicitHeightColumnLayout{id: delegateLayoutControls.Label{text:model.characters}RowLayout{Layout.fillWidth:trueControls.Button{text:"Edit"onClicked:{editPrompt.text=model.characters;editPrompt.model=model;editPrompt.open();}}Controls.Button{text:"Delete"onClicked:{customModel.deleteSpecies(model.species,index);}}}}}}}}Kirigami.PromptDialog{id: addPrompttitle:"Add New Species"standardButtons:Kirigami.Dialog.OkonAccepted:{customModel.addSpecies(addPromptText.text);addPromptText.text="";// Clear TextField every time it's done
addPrompt.close();}Controls.TextField{id: addPromptTextLayout.fillWidth:trueonAccepted:addPrompt.accept()}}Kirigami.PromptDialog{id: editPromptpropertyvarmodelpropertyaliastext:editPromptText.texttitle:"Edit Characters"standardButtons:Kirigami.Dialog.Ok|Kirigami.Dialog.CancelonAccepted:{constmodel=editPrompt.model;model.characters=editPromptText.text;editPrompt.close();}Controls.TextField{id: editPromptTextonAccepted:editPrompt.accept()}}}
#pragma once
#include<QAbstractListModel>#include<qqmlintegration.h>classModel:publicQAbstractListModel{Q_OBJECTQML_ELEMENTpublic:enumRoles{SpeciesRole=Qt::UserRole,CharactersRole};introwCount(constQModelIndex&)constoverride;QHash<int,QByteArray>roleNames()constoverride;QVariantdata(constQModelIndex&index,introle)constoverride;boolsetData(constQModelIndex&index,constQVariant&value,introle)override;Q_INVOKABLEvoidaddSpecies(constQString&species);Q_INVOKABLEvoiddeleteSpecies(constQString&speciesName,constint&rowIndex);private:QMap<QString,QStringList>m_list={{"Feline",{"Tigress","Waai Fuu"}},{"Fox",{"Carmelita","Diane","Krystal"}},{"Goat",{"Sybil","Toriel"}}};staticQStringformatList(constQStringList&list);};