Povežite modele z vašim uporabniškim vmesnikom QML
Kot je prikazano v prejšnjem učbeniku, lahko kodo C++ povežete s QML tako, da ustvarite arazred, ki bo obravnavan kot le še ena komponenta v QML. Vendar pa morda želite predstaviti bolj zapletene podatke, kot so podatki, ki morajo delovati kot ListModel ali jih je potrebno nek način je treba delegirati iz Repeater.
Ustvarimo lahko lastne modele s strani C++ in navedite, kako naj bodo podatki iz tega modela predstavljeni na vmesniku QML.
Priprava razreda
V tem učbeniku bomo ustvarili razred, ki vsebuje QMap, kjer se QString uporablja kot ključ, objekti QStringList pa kot vrednosti. Začelje bo lahko prebralo in prikazalo ključe in vrednosti ter bo preprosto za uporabo kot enodimenzionalni niz. Videti mora biti podobno kot QMLListModel.
Da bi to naredili, moramo deklarirati razred, ki deduje odQAbstractListModel. Dodajmo še nekatere dodatne podatke v QMap. Te deklaracije se bodo nahajale v model.h
.
Opomba
Če sledite, ne pozabite posodobiti svoje datotekeCMakeLists.txt
!#pragma once
#include <QAbstractListModel>
class Model : public QAbstractListModel {
private:
QMap<QString, QStringList> m_list = {
{"Feline", {"Tigress", "Waai Fuu"}},
{"Fox", {"Carmelita", "Diane", "Krystal"}},
{"Goat", {"Sybil", "Toriel"}}
};
};
Seveda tega razreda ne moremo prikazati takšnega, kot je. Povedati moramo tudi QML o tem, kako te podatke predstaviti v razredu. To lahko storimo s preglasitvijo treh virtualnih funkcij, ki so bistvenega pomena za to, in za vse svoje naloge.
rowCount()
– Zamislite si to funkcijo kot način, kako QML povedati, koliko elementovje v modelu, ki ga predstavljajo.roleNames()
– Imena vlog si lahko predstavljate kot imena lastnosti, priloženapodatkom v QML. Ta funkcija vam omogoča ustvarjanje teh vlog.data()
– Ta funkcija se pokliče, ko želite pridobiti podatke, ki ustrezajo imenom vlog iz modela.
Opomba
Imena vlog po meri, ustvarjena zroleNames()
, 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 preglasitevrowCount()
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()
Preglasimo funkcijo v datoteki glave. Funkciji rowCount()
je priloženlasten parameter, vendar v tem primeru ne bo uporabljen in je izključen.
class Model : public QAbstractListModel {
...
public:
int rowCount(const QModelIndex &) const override;
};
Nato deklarirajmo, koliko vrstic je v tem modelu v model.cpp
.
#include "model.h"
int Model::rowCount(const QModelIndex &) const {
return m_list.count();
}
Preglasitev in implementacija roleNames()
Preden preglasimo roleNames()
, moramo deklarirati, v čem so vloge strani C++ z uporabo javne spremenljivke enum
. Razlog za to je, ker se te vrednosti iz spremenljivke enum
vsakič posredujejo v data()
kadar QML dostopa do ustrezne vloge in tako lahko vrnemo prek data()
kar želimo.
Začnimo z ustvarjanjem spremenljivke enum
za vloge, kjer je vsaka vrednost vloga za stran C++.
class Model : public QAbstractListModel {
...
public:
enum Roles {
SpeciesRole = Qt::UserRole,
CharactersRole
};
...
QHash<int, QByteArray> roleNames() const override;
};
Ko bomo to uredili, lahko končno ustvarimo, v čem so te vloge strani QML z uporabo QHash, kjer so ključi oštevilčene vrednosti v paru z QByteArrays. Besedilo v QByteArray je tisto, kar se uporablja v dejanski kodi QML.
QHash<int, QByteArray> Model::roleNames() const {
return {
{SpeciesRole, "species"},
{CharactersRole, "characters"}
};
}
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()
V data()
sta posredovana dva parametra: index
in role
. indeks
je lokacija, kjer so podatki, ko so delegirani. Kot kot je bilo že navedeno, QML uporablja role - vlogo
za vrnitev določenih podatkov, kodostopa do vloge.
V data()
lahko uporabimo stavek switch
, da vrnemo ustrezne podatke in podatkovne tipe, odvisno od vloge, kar je mogoče, saj data()
vrne QVariant. Še vedno se moramo prepričati, da dobimo primerno lokacijo podatkov. V tem primeru spodaj lahko viditeda se deklarira nova spremenljivka iteratorja, ki je nastavljena iz začetka seznama plus vrstica indeksa in podatki, ki jih iterator, na katerega kaže prav tisto, kar je vrnjeno.
Ne moremo pa kar tako vrniti podatkov, ki jih želimo. Morda se poskušamo zavezatipodatkov v lastnost z nezdružljivim tipom podatkov, kot je QStringList vQString. Morda boste morali izvesti pretvorbo podatkov, da bodo podatki lahkoprikazani pravilno.
QVariant Model::data(const QModelIndex &index, int role) const {
const auto it = m_list.begin() + index.row();
switch (role) {
case SpeciesRole:
return it.key();
case CharactersRole:
return formatList(it.value());
default:
return {};
}
}
QString Model::formatList(const QStringList& list) {
QString result;
for (const QString& character : list) {
result += character;
if (list.last() != character) {
result += ", ";
}
}
return result;
}
Dovolite, da se razred deklarira v QML
Ne pozabimo narediti našega razreda uporabnega v QML.
int main(int argc, char *argv[]) {
...
qmlRegisterType<Model>("CustomModel", 1, 0, "CustomModel");
...
}
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 kotmodel. Do podatkov dostopamo z besedo model
, ki ji sledijo vloge deklarirane v roleNames()
.
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as Controls
import org.kde.kirigami as Kirigami
import CustomModel 1.0
Kirigami.ApplicationWindow {
id: root
title: "Tutorial"
CustomModel {
id: customModel
}
pageStack.initialPage: Kirigami.ScrollablePage {
ColumnLayout {
Repeater {
model: customModel
delegate: Kirigami.AbstractCard {
header: Kirigami.Heading {
text: model.species
level: 2
}
contentItem: Controls.Label {
text: model.characters
}
}
}
}
}
}
Spreminjanje podatkov
Urejanje podatkov z uporabo dataChanged()
in setData()
Lahko se zgodi, da želite spremeniti podatke v modelu, in naj se spremembe odražajo na začelju. Vsakič, ko spremenimo podatke v modelu, moramo oddati signal dataChanged()
, ki bo uveljavil te spremembe na sprednji strani v posebnih celicah, ki so določene v njegovih argumentih. V tem učbeniku lahko samo uporabimo argument index
od setData()
.
setData()
je navidezna funkcija, ki jo lahko preglasite, tako da poskus spreminjanja podatkov na začelju samodejno odraža te spremembe na ozadju. Zahteva tri parametre:
index
– Lokacija podatkov.value
– Vsebina novih podatkov.role
– V tem kontekstu se role - vloga tukaj uporablja, da pogledom pove, kako morajo ravnati s podatki. Vloga tukaj bi morala bitiQt::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
.
bool Model::setData(const QModelIndex &index, const QVariant &value, int role) {
if (!value.canConvert<QString>() && role != Qt::EditRole) {
return false;
}
auto it = m_list.begin() + index.row();
QString charactersUnformatted = value.toString();
QStringList characters = charactersUnformatted.split(", ");
m_list[it.key()] = characters;
emit dataChanged(index, index);
return true;
}
Opomba
setData()
ne odda samodejno dataChanged()
in to je še vedno treba opraviti ročno.Posodobimo kodo QML, da bomo lahko odprli poziv, ki nam to omogoča urejanje modela z gumbom Controls.Button, ki je pritrjen na kartice.
Kirigami.ApplicationWindow {
...
Kirigami.OverlaySheet {
id: editPrompt
property var model
property alias text: editPromptText.text
title: "Edit Characters"
Controls.TextField {
id: editPromptText
}
footer: Controls.DialogButtonBox {
standardButtons: Controls.DialogButtonBox.Ok
onAccepted: {
const model = editPrompt.model;
model.characters = editPromptText.text;
editPrompt.close();
}
}
}
pageStack.initialPage: Kirigami.ScrollablePage {
ColumnLayout {
Repeater {
model: customModel
delegate: Kirigami.AbstractCard {
Layout.fillHeight: true
header: Kirigami.Heading {
text: model.species
level: 2
}
contentItem: Item {
implicitWidth: delegateLayout.implicitWidth
implicitHeight: delegateLayout.implicitHeight
ColumnLayout {
id: delegateLayout
Controls.Label {
text: model.characters
}
Controls.Button {
text: "Edit"
onClicked: {
editPrompt.text = model.characters;
editPrompt.model = model;
editPrompt.open();
}
}
}
}
}
}
}
}
}
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 uporabiti makro Q_OBJECT v razredu in začeti deklaracijo metode z makrom Q_INVOKABLE. Ta metoda bo vključevala tudi parameter niza, ki naj bi bil novi ključ v QMap.
class Model : public QAbstractListModel {
Q_OBJECT;
...
public:
...
Q_INVOKABLE void addSpecies(const QString &species);
};
Znotraj te metode moramo Qt-ju povedati, da želimo ustvariti več vrstic v modelu. To naredimo tako, da pokličemo beginInsertRows()
za začetek naše vrsticeoperacija dodajanja, ki ji sledi vstavljanje vsega, kar potrebujemo, nato uporabaendInsertRows()
za končanje operacije. Še vedno moramo oddajatidataChanged()
na koncu pa. Tokrat bomo posodobili vse vrstice, od prve vrstice do zadnje, kot lahko QMap po abecedi se reorganizira in to moramo ujeti 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.
void Model::addSpecies(const QString& species) {
beginInsertRows(QModelIndex(), m_list.size() - 1, m_list.size() - 1);
m_list.insert(species, {});
endInsertRows();
emit dataChanged(index(0), index(m_list.size() - 1));
}
Opomba
FunkcijadataChanged()
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.
Kirigami.ApplicationWindow {
...
Kirigami.OverlaySheet {
id: addPrompt
title: "Add New Species"
Controls.TextField {
id: addPromptText
}
footer: Controls.DialogButtonBox {
standardButtons: Controls.DialogButtonBox.Ok
onAccepted: {
customModel.addSpecies(addPromptText.text);
addPromptText.text = ""; // Počisti TextField vsakič, ko je narejeno
addPrompt.close();
}
}
}
pageStack.initialPage: Kirigami.ScrollablePage {
actions: [
Kirigami.Action {
icon.name: "add"
text: "Add New Species"
onTriggered: {
addPrompt.open();
}
}
]
...
}
}
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.
class Model : public QAbstractListModel {
Q_OBJECT;
...
public:
...
Q_INVOKABLE void deleteSpecies(const QString &speciesName, const int &rowIndex);
}
void Model::deleteSpecies(const QString &speciesName, const int& rowIndex) {
beginRemoveRows(QModelIndex(), rowIndex, rowIndex);
m_list.remove(speciesName);
endRemoveRows();
emit dataChanged(index(0), index(m_list.size() - 1));
}
Zdaj pa posodobimo aplikacijo, da se gumb »Izbriši« prikaže poleg gumba za urejanje in ga povežimo z našo metodo brisanja.
ColumnLayout {
Repeater {
model: customModel
delegate: Kirigami.AbstractCard {
...
contentItem: Item {
implicitWidth: delegateLayout.implicitWidth
implicitHeight: delegateLayout.implicitHeight
ColumnLayout {
id: delegateLayout
Controls.Label {
text: model.characters
}
RowLayout {
Controls.Button {
text: "Edit"
onClicked: {
editPrompt.text = model.characters;
editPrompt.model = model;
editPrompt.open();
}
}
Controls.Button {
text: "Delete"
onClicked: {
customModel.deleteSpecies(model.species, index);
}
}
}
}
}
}
}
}
Celotna koda
Main.qml
|
|
model.h
|
|
model.cpp
|
|
Več informacij
Za več informacij glejte Uporaba modelov C++ s hitrimi pogledi Qt in Model/View Programming.