Povežite modele C++ z vašim uporabniškim vmesnikom QML
Podatke iz zaledja C++ s čelnim delom QML
Kot je prikazano v prejšnjem priročniku, lahko kodo C++ povežete s QML tako, da ustvarite razred, ki bo v QML obravnavan kot le še ena komponenta. Vendar pa boste morda želeli predstaviti bolj zapletene podatke, kot so podatki, ki morajo delovati kot prilagojeni ListModel ali pa jih je treba na nek način delegirati iz Repeaterja.
Ustvarimo lahko lastne modele s strani C++ in navedite, kako naj bodo podatki iz tega modela predstavljeni na vmesniku QML.
Zelo priporočljivo je, da pred tem preberete vadnico Seznam pogledov.
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()}]}
Nato ustvarite novo datoteko src/components/ModelsPage.qml z naslednjo vsebino:
1
2
3
4
5
6
7
8
9
importQtQuickimportQtQuick.LayoutsimportQtQuick.ControlsasControlsimportorg.kde.kirigamiasKirigamiKirigami.ScrollablePage{title:"C++ models in QML"// ...
}
In končno ga dodajte v src/components/CMakeLists.txt:
Da bi v tej vadnici lažje razumeli, kako je model poseljen, bomo onemogočili funkcijo, ki jo aplikacije KDE z moduli extra-cmake (ECM) privzeto uporabljajo in ki optimizira kodo nizov. To nam omogoča, da se izognemo pisanju QStringLiteral() vsakič, ko je v naši kodi C++ uveden niz, kar bo uporabno za kodo v prihajajoči datoteki glave.
V korensko datoteko CMakeLists.txt dodajte naslednje:
Onemogočanje te zastavice CMake je narejeno samo v didaktične namene. Produkcijska koda naj namesto tega uporablja QStringLiteral() ali imenski prostor Qt string literals, kjer je to mogoče.
Priprava razreda
Ustvarili bomo razred, ki vsebuje QMap, kjer se kot ključ uporablja QString, kot vrednosti pa objekti QStringList. Sprednji del bo lahko bral in prikazoval ključe in vrednosti ter bo preprost za uporabo, tako kot enodimenzionalno polje. Izgledati mora podobno kot QML ListModel.
Da bi to naredili, moramo ustvariti razred, ki deduje od QAbstractListModel. Dodajmo še nekaj podatkov v QMap. Te deklaracije bodo nameščene v model.h.
Ustvarite dve novi datoteki, src/components/model.h in src/components/model.cpp.
Dodajte ti dve novi datoteki v src/components/CMakeLists.txt:
Kot začetno vsebino v src/components/model.h dodajte naslednje:
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"}}};};
Seveda tega razreda ne moremo kar prikazati takšnega, kot je. QML-ju moramo tudi povedati, kako naj te podatke predstavi v razredu. To lahko storimo s preglasitvijo treh bistvenih virtualnih funkcij:
rowCount() - Predstavljajte si to funkcijo kot način, s katerim QML poveste, koliko elementov naj model predstavi.
roleNames() - Imena vlog si lahko predstavljate kot imena lastnosti, priloženih podatkom v QML. Ta funkcija vam omogoča ustvarjanje teh vlog.
data() - To funkcijo pokličete, ko želite pridobiti podatke, ki ustrezajo imenom vlog iz modela.
Opomba
Imena vlog po meri, ustvarjena z roleNames(), so uporabna samo, ko je model delegiran in jih ni mogoče uporabiti zunaj njega. Glejte Modeli in pogledi.
Opomba
Tehnično so modeli v Qt predstavljeni kot tabele z vrsticami in stolpci. Torej preglasitev rowCount() pove Qt-u, koliko vrstic je v modelu. Ker imamo v tem primeru opravka samo z enodimenzionalnim nizomučbenika, si lahko "vrstice" preprosto predstavljate kot "število elementov."
Preglasitev in implementacija rowCount()
Funkcijo v datoteki glave src/components/model.h prepišimo. Funkcija rowCount() ima svoj parameter, vendar ga v tem primeru ne bomo uporabili, zato ga ni treba poimenovati.
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"}}};};
Nato v datoteki src/components/model.cpp deklarirajmo, koliko vrstic je v tem modelu:
Preden preglasimo roleNames(), moramo vloge deklarirati na strani C++ z uporabo javnega naštevanja. Razlog za to je, ker se te vrednosti naštevanja posredujejo v data() vsakič, ko QML dostopa do ustrezne vloge, in tako lahko data() vrne, kar želimo.
Začnimo z ustvarjanjem naštevanja za vloge v src/components/model.h, kjer je vsaka vrednost vloga za stran C++.
#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"}}};};
Ko to uredimo, lahko končno ustvarimo te vloge na strani QML z uporabo QHash, kjer so ključi naštete vrednosti, povezane z QByteArrays. To bi moralo iti v src/components/model.cpp. Besedilo v QByteArray se uporablja v dejanski kodi QML.
V našem vzorčnem modelu lahko vlogo "species" uporabite za pridobitevQString ključ "Feline", "Fox", "Goat", v vsakem ločenem delegatu. Enako lahko storite z vrednostmi QStringList za seznam imen znakov.
Preglasitev in implementacija data()
Funkciji data() se posredujeta dva parametra: index in role. Index predstavlja položaj podatkov v modelu. Kot smo že omenili, QML uporablja role za pridobitev določenih podatkov, ki se vrnejo, ko dostopa do vloge.
V data() lahko uporabimo stavek switch, da vrnemo ustrezne podatke in podatkovni tip, odvisno od vloge, kar je mogoče, saj data() vrne QVariant. Vendar moramo še vedno zagotoviti, da dobimo ustrezno lokacijo podatkov. V spodnjem primeru lahko vidite, da se deklarira nova spremenljivka iteratorja, ki je nastavljena od začetka seznama plus vrstice indeksa, in podatki, na katere kaže iterator, so tisto, kar se vrne.
Vendar ne moremo kar tako vrniti poljubnih podatkov. Morda poskušamo povezati podatke z lastnostjo z nezdružljivim podatkovnim tipom, na primer QStringList s QString. Morda boste morali izvesti pretvorbo podatkov, da se podatki pravilno prikažejo. Za to ustvarimo novo zasebno, statično funkcijo z imenom formatList().
Rezultat je naslednja koda v datoteki 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);};
Uporaba razreda v QML
Uporabljena datoteka QML bo vsebovala samo tri komponente Kirigami.AbstractCard) , kjer je ključ glava in vrednost vsebina. Te kartice so ustvarjene z delegiranjem AbstractCard z uporabo Repeaterja, kjer model po meri, ki smo ga ustvarili, deluje kot model. Do podatkov dostopamo z besedo model, ki ji sledijo vloge deklarirane v 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}}}}}
Spreminjanje podatkov
Urejanje podatkov z uporabo dataChanged() in setData()
Lahko se znajdete v situaciji, ko želite spremeniti podatke v modelu in te spremembe odražati na strani začelja. Vsakič, ko spremenimo podatke v modelu, moramo oddati signal dataChanged(), ki bo te spremembe na strani začelja uporabil v določenih celicah, navedenih v njegovih argumentih. V tej vadnici lahko uporabimo le argument index funkcije setData().
setData() je virtualna funkcija, ki jo lahko preglasite, tako da spreminjanje podatkov na strani začelja samodejno odraža te spremembe na strani ozadja. Zahteva tri parametre:
index – Lokacija podatkov.
value – Vsebina novih podatkov.
vloga - V tem kontekstu se vloga tukaj uporablja za določitev, kako naj pogledi ravnajo s podatki. Vloga tukaj bi morala biti Qt::EditRole.
Parameter role se v tem primeru uporablja za zagotovitev, da je setData() lahkourejana z uporabniškim vnosom (Qt::EditRole). Z uporabo indexa lahko to uporabimo zadoločanje lokacije, kjer naj bodo podatki urejani z vsebino 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);};
Posodobimo kodo QML, da bomo lahko odprli poziv, ki nam omogoča urejanje modela z uporabo Controls.Button, priloženega karticam.
V src/components/ModelsPage.qml dodajte naslednji Kirigami.PromptDialog skupaj z novim gumbom za urejanje:
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()}}}
Zdaj, ko se vrednosti modela spremenijo v sprednjem delu, se morajo spremembe samodejno posodobiti v zadnjem delu.
Dodajanje vrstic
Dodali smo način za spreminjanje podatkov v obstoječih ključih QMap in v začelju, se to odraža kot spreminjanje vsebine znotraj AbstractCards. Kaj pa, če moramo dodati nov ključni vnos v QMap inse je to odraža na strani QML? Naredimo to tako, da ustvarimo novo metodo, ki jo je mogoče priklicati na strani QML za izvedbo te naloge.
Da bi bila metoda vidna v QML, moramo deklaracijo metode začeti z makrom Q_INVOKABLE. Ta metoda bo vključevala tudi parameter niza, ki naj bi bil novi ključ v QMap.
#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);};
Znotraj te metode moramo Qt-u povedati, da želimo v modelu ustvariti več vrstic. To storimo tako, da za začetek dodajanja vrstic pokličemo beginInsertRows(), nato pa vstavimo, kar potrebujemo, in nato z uporabo endInsertRows() zaključimo operacijo. Na koncu pa moramo še vedno izdati dataChanged(). Tokrat bomo posodobili vse vrstice, od prve do zadnje, saj se lahko QMap abecedno preuredi, in to moramo zaobjeti v vseh vrsticah.
Ko kličemo beginInsertRows(), moramo najprej posredovati razred QModelIndex, da določite lokacijo, kamor naj bodo dodane nove vrstice, ki ji sledijo nove številke prve in zadnje vrstice. V tem učbeniku, bo prvi argument samo QModelIndex(), ker ne ni potrebe uporabiti parametra. Uporabimo lahko samo trenutno velikost vrstice za številko prve in zadnje vrstice, saj bomo dodali samo eno vrstico na koncumodela.
Funkcija dataChanged() uporablja QModelIndex kot podatkovni tip za svoje parametre. Vendar pa lahko pretvorimo cela števila v podatkovne vrste QModelIndex z uporabo funkcije index().
Posodobimo kodo QML, da bomo lahko dodali nov ključ v QMap.
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// ...
}}
Zdaj bi morali dobiti novo dejanje na vrhu aplikacije, ki prikaže poziv, ki omogoča dodajanje novega elementa s podatki v model po lastni meri.
Odstranjevanje vrstic
Način odstranjevanja vrstic je podoben dodajanju vrstic. Ustvarimo drugo metodo, ki jo bomo poklicali v QML. Tokrat bomo uporabili dodaten parameter in to je celo število, ki je številka vrstice. Ime vrste se uporablja za brisanje ključa iz QMap, medtem ko bo številka vrstice uporabljena za brisanjevrstice na začelju.
V datoteko src/components/model.h dodajte novo funkcijo Q_INVOKABLE z imenom deleteSpecies():
#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);};
Z ustrezno implementacijo v src/components/model.cpp:
Zdaj pa posodobimo aplikacijo, da se bo gumb "Izbriši" prikazal v RowLayout poleg gumba za urejanje znotraj naše AbstractCard, in ga povežimo z našo metodo brisanja.
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);};