Konektu modelojn al via uzantinterfaco de QML
Kiel montrite de la antaŭa lernilo, vi povas konekti C++-kodon al QML kreante klason, kiu estos traktata kiel nur alia komponanto en QML. Tamen, vi eble volas reprezenti pli komplikajn datumojn, kiel datumojn, kiuj devas funkcii kiel propra ListModel aŭ iel devas esti delegitaj de Ripetilo.
Ni povas krei niajn proprajn modelojn de la C++-flanko, kaj deklari kiel la datumoj de tiu modelo devus esti reprezentitaj sur la QML-fasado.
Preparante la Klason
En ĉi tiu lernilo, ni kreos klason, kiu enhavas QMap, kie QString estas uzata kiel ŝlosilo kaj QStringList-objektoj estas uzataj kiel valoroj. La fasado povos legi kaj montri la ŝlosilojn kaj valorojn kaj esti simpla uzebla same kiel unudimensia tabelo. Ĝi devus aspekti simila al QML ListModel.
Por fari tion, ni devas deklari klason, kiu heredas de QAbstractListModel. Ni ankaŭ aldonu kelkajn aldonajn datumojn al la QMap. Ĉi tiuj deklaroj troviĝos en model.h
.
Noto
Se vi sekvas, bonvolu memori ĝisdatigi vianCMakeLists.txt
dosieron!#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"}}
};
};
Kompreneble, ni ne povas simple montri ĉi tiun klason kiel estas. Ni ankaŭ devas diri al QML pri kiel reprezenti ĉi tiujn datumojn en la klaso. Ni povas fari tion per superregado de tri virtualaj funkcioj, kiuj estas esencaj por fari ĉi tion, kiuj ĉiuj faras siajn proprajn taskojn.
rowCount()
- Pensu pri ĉi tiu funkcio kiel maniero diri al QML kiom da eroj estas en la modelo por reprezenti.roleNames()
- Vi povas pensi pri rolnomoj kiel proprecnomoj ligitaj al datumoj en QML. Ĉi tiu funkcio permesas krei tiujn rolojn.data()
- Ĉi tiu funkcio estas vokita kiam vi volas preni la datumojn kiuj respondas al la rolnomoj de la modelo.
Noto
La propraj rolnomoj kreitaj deroleNames()
estas nur uzeblaj kiam modelo estas delegita, kaj ne estas uzeblaj ekster ĝi. Vidu Modeloj kaj Vidoj.Noto
Teknike, modeloj en Qt estas reprezentitaj kiel tabeloj, kun vicoj kaj kolumnoj. Do, kio superregantarowCount()
faras estas diri al Qt kiom da vicoj estas en modelo. Ĉar ni nur traktas unudimensian tabelon en ĉi tiu lernilo, vi povas simple pensi pri "vicoj" kiel "nombro da elementoj."Anstataŭigi kaj Efektivigi rowCount()
Ni superregu la funkcion en la kapdosiero. La rowCount()
venas kun sia propra parametro, sed ne estos uzata en ĉi tiu ekzemplo kaj estas ekskludita.
class Model : public QAbstractListModel {
...
public:
int rowCount(const QModelIndex &) const override;
};
Tiam, ni deklaru kiom da vicoj estas en ĉi tiu modelo en model.cpp
.
#include "model.h"
int Model::rowCount(const QModelIndex &) const {
return m_list.count();
}
Anstataŭigi kaj Efektivigi roleNames()
Antaŭ ol ni superregas roleNames()
, ni devas deklari kiajn rolojn estas en la C++-flanko uzante publikan enum
variablon. La kialo de tio estas ĉar ĉi tiuj valoroj de la variablo enum
estas transdonitaj en data()
ĉiufoje kiam QML aliras respondan rolon, kaj kiel tia ni povas fari data()
redoni kion ni volas.
Ni komencu kreante la variablon enum
por roloj, kie ĉiu valoro estas rolo por la C++-flanko.
class Model : public QAbstractListModel {
...
public:
enum Roles {
SpeciesRole = Qt::UserRole,
CharactersRole
};
...
QHash<int, QByteArray> roleNames() const override;
};
Post kiam ni havas tion aranĝita, ni povas finfine krei kiajn ĉi tiujn rolojn estas en la QML-flanko uzante QHash kie la ŝlosiloj estas la listigitaj valoroj parigitaj kun QByteArrays. .html). La teksto en la QByteArray estas tio, kio estas uzata en la reala QML-kodo.
QHash<int, QByteArray> Model::roleNames() const {
return {
{SpeciesRole, "species"},
{CharactersRole, "characters"}
};
}
En nia ekzempla modelo, la rolo "specio" povas esti uzata por preni la QString-ŝlosilon "Feline", "Vulpo", "Kaprino", ĉiu en aparta delegito. La sama povas esti farita kun la QStringList-valoroj por la listo de signonomoj.
Anstataŭigi kaj Efektivigi data()
Estas du parametroj kiuj estas pasigitaj en data()
: indekso
kaj rolo
. indekso
estas la loko de kie la datumoj estas delegitaj. Kiel antaŭe dirite, "rolo" estas uzata de QML por ricevi specifajn datumojn resenditajn kiam ĝi aliras rolon.
En data()
, ni povas uzi deklaron switch
por redoni la taŭgajn datumojn kaj datumtipon depende de la rolo, kio eblas ĉar data()
liveras QVariant. Ni ankoraŭ devas certigi, ke ni ricevas la taŭgan lokon de la datumoj, tamen. En ĉi tiu malsupra ekzemplo, vi povas vidi, ke nova iteratora variablo estas deklarita, kiu estas agordita de la komenco de la listo plus la vico de la indekso kaj la datumoj, kiujn la iteratoro montras, estas kio estas resendita.
Ni ne povas simple redoni kiajn ajn datumojn ni volas tamen. Ni eble provas ligi datumojn al propreco kun nekongrua datumtipo, kiel QStringList al QString. Vi eble devos fari datuman konvertiĝon por ke la datumoj estu montritaj ĝuste.
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;
}
Permesu la Klason esti Deklarita en QML
Ni ne forgesu fari nian klason uzebla en QML.
int main(int argc, char *argv[]) {
...
qmlRegisterType<Model>("CustomModel", 1, 0, "CustomModel");
...
}
Klasa Uzado en QML
La QML-dosiero uzata nur enhavos tri Kirigami.AbstractCard komponantojn, kie la ŝlosilo estas la kaplinio kaj la valoro estas la enhavo. Ĉi tiuj kartoj estas kreitaj per delegado de Abstrakta Karto uzante Ripetilon, kie la propra modelo, kiun ni kreis, funkcias kiel la modelo. La datumoj estas aliritaj per vorto modelo
, sekvata de la roloj, kiujn ni deklaris en 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
}
}
}
}
}
}
Modifo de Datumoj
Redaktante Datumojn Uzante dataChanged()
kaj setData()
Vi povas renkonti situacion kie vi volas modifi datumojn en la modelo, kaj havi la ŝanĝojn reflektitaj sur la fasado. Ĉiufoje kiam ni ŝanĝas datumojn en la modelo, ni devas elsendi la signalon dataChanged()
, kiu aplikos tiujn ŝanĝojn ĉe la fasado ĉe la specifaj ĉeloj specifitaj en ĝiaj argumentoj. En ĉi tiu lernilo, ni povas simple uzi la argumenton indekso
de setData()
.
setData()
estas virtuala funkcio, kiun vi povas superregi, por ke provi modifi la datumojn de la fasa flanko aŭtomate reflektas tiujn ŝanĝojn ĉe la malantaŭa flanko. Ĝi postulas tri parametrojn:
index
- La loko de la datumoj.valoro
- La enhavo de la novaj datumoj.rolo
- En ĉi tiu kunteksto, la rolo ĉi tie estas uzata por diri al vidoj kiel ili devas manipuli datumojn. La rolo ĉi tie devus estiQt::EditRole
.
La parametro role
ĉi-kaze estas uzata por certigi ke setData()
povas esti redaktita per uzanta enigo (Qt::EditRole). Uzante indekso', ni povas uzi tion por determini la lokon de kie la datumoj devas esti redaktitaj kun la enhavo de
valoro`.
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;
}
Noto
setData()
ne aŭtomate elsendas dataChanged()
kaj tio ankoraŭ devas esti farita permane.Ni ĝisdatigu la QML-kodon por ke ni povu malfermi promptilon, kiu ebligas al ni redakti la modelon per Controls.Button alkroĉita al la kartoj.
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();
}
}
}
}
}
}
}
}
}
Nun, kiam ajn la valoroj de la modelo ŝanĝiĝas en la fasado, la ŝanĝoj aŭtomate devas ĝisdatigi en la backend.
Aldonante Vicoj
Ni aldonis manieron modifi la datumojn en ekzistantaj ŝlosiloj de la QMap, kaj en la antaŭa fino, ĉi tio estas reflektita kiel modifado de la enhavo ene de la AbstractCards. Sed kio se ni bezonas aldoni novan ŝlosilan eniron en la QMap kaj tion reflektas sur la QML-flanko? Ni faru tion kreante novan metodon, kiu estas vokebla ĉe la QML-flanko por plenumi ĉi tiun taskon.
Por igi la metodon videbla en QML, ni devas uzi la Q_OBJECT-makroon en la klaso, kaj komenci la metododeklaron per la Q_INVOKABLE-makroo. Ĉi tiu metodo ankaŭ inkluzivos ĉenparametron, kiu celas esti la nova ŝlosilo en la QMap.
class Model : public QAbstractListModel {
Q_OBJECT;
...
public:
...
Q_INVOKABLE void addSpecies(const QString &species);
};
Ene de ĉi tiu metodo, ni devas diri al Qt, ke ni volas krei pli da vicoj en la modelo. Ĉi tio estas farita per vokado beginInsertRows()
por komenci nian vicon aldonante operacion, sekvita per enmeto kion ajn ni bezonas, tiam uzu endInsertRows()
por fini la operacion. Ni ankoraŭ bezonas elsendi dataChanged()
fine, tamen. Ĉi-foje, ni ĝisdatigos ĉiujn vicojn, de la unua vico ĝis la lasta ĉar la QMap povas alfabete reorganizi sin, kaj ni devas kapti tion tra ĉiuj vicoj.
Kiam vi vokas beginInsertRows()
, ni unue devas eniri QModelIndex-klason por specifi la lokon de kie la novaj vicoj devas esti aldonitaj, sekvitaj de kio la novaj unua kaj lasta vico-numeroj estos. En ĉi tiu lernilo, la unua argumento estos nur QModelIndex()
ĉar ne necesas uzi la parametron ĉi tie. Ni povas simple uzi la nunan vicon por la unua kaj lasta vico-numero, ĉar ni nur aldonos unu vicon ĉe la fino de la modelo.
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));
}
Noto
La funkciodataChanged()
uzas QModelIndex kiel la datumtipo por siaj parametroj. Tamen, ni povas konverti entjerojn en QModelIndex datumtipoj uzante la funkcion index()
.Ni ĝisdatigu la QML-kodon, por ke ni ricevu la eblon aldoni novan ŝlosilon al la 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 = ""; // Klarigi TextField ĉiufoje kiam ĝi estas farita
addPrompt.close();
}
}
}
pageStack.initialPage: Kirigami.ScrollablePage {
actions: [
Kirigami.Action {
icon.name: "add"
text: "Add New Species"
onTriggered: {
addPrompt.open();
}
}
]
...
}
}
Nun, ni devus ricevi novan agon ĉe la supro de la aplikaĵo, kiu aperigas prompton, kiu permesas aldoni novan elementon al la modelo, kun niaj propraj propraj datumoj.
Forigante Vicojn
La maniero forigi vicojn similas al aldoni vicojn. Ni kreu alian metodon, kiun ni nomos en QML. Ĉi-foje, ni uzos plian parametron, kaj tio estas entjero, kiu estas la vico-numero. La specionomo estas uzata por forigi la ŝlosilon de la QMap, dum la vico-numero estos uzata por forigi la vicon ĉe la antaŭa fino.
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));
}
Nun ni ĝisdatigu la aplikaĵon, tiel ke butono "Forigi" aperos apud la redaktbutono, kaj ligu ĝin al nia foriga metodo.
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);
}
}
}
}
}
}
}
}
Plena Kodo
Main.qml
|
|
model.h
|
|
model.cpp
|
|
Pliaj Informoj
Por pliaj informoj, vidu Uzante C++-Modelojn kun Qt Rapidaj Vidoj kaj Modelo/Vida Programado.