Skip to main content
Passa al contenuto

Connetti i modelli C++ alla tua interfaccia utente QML

Dati dal backend C++ al frontend QML

As shown in the previous tutorial, you can connect C++ code to QML by creating a class that will be treated as just another component in QML. Tuttavia, potresti voler rappresentare dati più complicati, come i dati che devono agire come un ListModel personalizzato o che in qualche modo devono essere delegati da un Repeater.

Possiamo creare i nostri modelli dal lato C++ e dichiarare come i dati di quel modello dovrebbero essere rappresentati sul frontend QML.

Si consiglia vivamente di leggere il tutorial Visualizzazioni elenco prima di questo.

Il codice utilizzato per questo tutorial sarà basato sulla pagina precedente, Connetti la logica all'interfaccia utente QML.

Struttura del progetto

kirigami-tutorial/
├── CMakeLists.txt --------------------- # Modified for didactic purposes
├── org.kde.tutorial.desktop
└── src/
    ├── CMakeLists.txt
    ├── main.cpp
    ├── Main.qml ----------------------- # Modified
    └── components/
        ├── CMakeLists.txt ------------- # Modified
        ├── AddDialog.qml
        ├── KountdownDelegate.qml
        ├── ExposePage.qml
        ├── ModelsPage.qml ------------- # New
        ├── model.h -------------------- # New
        ├── model.cpp ------------------ # New
        ├── backend.h
        └── backend.cpp

Creazione di una nuova pagina per il tutorial sui modelli

Prima di fare qualsiasi cosa, aggiungiamo una nuova pagina al nostro codice QML.

Per prima cosa, in «src/Main.qml», aggiungi la seguente azione al cassetto globale:

15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
    globalDrawer: Kirigami.GlobalDrawer {
        isMenu: true
        actions: [
            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.Quit
                onTriggered: Qt.quit()
            }
        ]
    }

Quindi, crea un nuovo src/components/ModelsPage.qml con il seguente contenuto:

1
2
3
4
5
6
7
8
9
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as Controls
import org.kde.kirigami as Kirigami

Kirigami.ScrollablePage {
    title: "C++ models in QML"
    // ...
}

E infine aggiungilo a src/components/CMakeLists.txt:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
add_library(kirigami-hello-components)

ecm_add_qml_module(kirigami-hello-components
    URI "org.kde.tutorial.components"
    GENERATE_PLUGIN_SOURCE
)

ecm_target_qml_sources(kirigami-hello-components
    SOURCES
    AddDialog.qml
    KountdownDelegate.qml
    ExposePage.qml
    ModelsPage.qml
)

target_sources(kirigami-hello-components
    PRIVATE
    backend.cpp backend.h
)

ecm_finalize_qml_module(kirigami-hello-components)

install(TARGETS kirigami-hello-components ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})

Questo servirà come tela per questa pagina del tutorial.

Utilizzo di stringhe grezze

Per rendere questo tutorial più semplice per comprendere come viene popolato il modello, disabiliteremo una funzionalità utilizzata per impostazione predefinita dalle applicazioni KDE che utilizzano moduli extra-cmake (ECM) che ottimizza il codice stringa. Questo ci consente di evitare di dover scrivere QStringLiteral() ogni volta che viene introdotta una stringa nel nostro codice C++, il che sarà utile per il codice nel prossimo file di intestazione.

Nel file root «CMakeLists.txt», aggiungi quanto segue:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
cmake_minimum_required(VERSION 3.20)
project(kirigami-tutorial)

find_package(ECM 6.0.0 REQUIRED NO_MODULE)
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH})

include(KDEInstallDirs)
include(KDECMakeSettings)
include(KDECompilerSettings NO_POLICY_SCOPE)
include(ECMFindQmlModule)
include(ECMQmlModule)
remove_definitions(-DQT_NO_CAST_FROM_ASCII)

find_package(Qt6 REQUIRED COMPONENTS
    Core
    Quick
    Test
    Gui
    QuickControls2
    Widgets
)

find_package(KF6 REQUIRED COMPONENTS
    Kirigami
    I18n
    CoreAddons
    QQC2DesktopStyle
    IconThemes
)

ecm_find_qmlmodule(org.kde.kirigami REQUIRED)

add_subdirectory(src)

install(PROGRAMS org.kde.tutorial.desktop DESTINATION ${KDE_INSTALL_APPDIR})

feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)

Preparazione della classe

Creeremo una classe che contiene un QMap, dove un QString viene utilizzato come chiave e gli oggetti QStringList vengono utilizzati come valori. Il frontend sarà in grado di leggere e visualizzare chiavi e valori e sarà semplice da usare proprio come un array unidimensionale. Dovrebbe essere simile a un ListModel QML.

Per fare ciò, dobbiamo creare una classe che erediti da QAbstractListModel. Aggiungiamo anche alcuni dati alla QMap. Queste dichiarazioni si troveranno in "model.h".

Crea due nuovi file, src/components/model.h e src/components/model.cpp.

Aggiungi questi due nuovi file a src/components/CMakeLists.txt:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
add_library(kirigami-hello-components)

ecm_add_qml_module(kirigami-hello-components
    URI "org.kde.tutorial.components"
    GENERATE_PLUGIN_SOURCE
)

ecm_target_qml_sources(kirigami-hello-components
    SOURCES
    AddDialog.qml
    KountdownDelegate.qml
    ExposePage.qml
    ModelsPage.qml
)

target_sources(kirigami-hello-components
    PRIVATE
    backend.cpp backend.h
    model.cpp model.h
)

ecm_finalize_qml_module(kirigami-hello-components)

install(TARGETS kirigami-hello-components ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})

Aggiungi quanto segue come contenuto iniziale a 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>

class Model : public QAbstractListModel {
    Q_OBJECT
    QML_ELEMENT
public:

private:
    QMap<QString, QStringList> m_list = {
            {"Feline", {"Tigress",   "Waai Fuu"}},
            {"Fox",    {"Carmelita", "Diane", "Krystal"}},
            {"Goat",   {"Sybil",     "Toriel"}}
    };
};

Ovviamente non possiamo semplicemente visualizzare questa classe così com'è. Dobbiamo anche dire a QML come rappresentare questi dati nella classe. Possiamo farlo sovrascrivendo tre funzioni virtuali essenziali:

  • rowCount() - Pensa a questa funzione come un modo per indicare a QML quanti elementi deve presentare il modello.
  • roleNames() - Puoi pensare ai nomi dei ruoli come nomi di proprietà allegati ai dati in QML. Questa funzione consente di creare tali ruoli.
  • data() - Questa funzione viene chiamata quando si desidera recuperare i dati che corrispondono ai nomi dei ruoli dal modello.

Sostituire e implementare rowCount()

Sovrascriviamo la funzione nel file header src/components/model.h. La funzione rowCount() viene fornita con il proprio parametro, ma non verrà utilizzata in questo esempio e quindi non è necessario nominarla.

 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>

class Model : public QAbstractListModel {
    Q_OBJECT
    QML_ELEMENT
public:
    int rowCount(const QModelIndex &) const override;

private:
    QMap<QString, QStringList> m_list = {
            {"Feline", {"Tigress",   "Waai Fuu"}},
            {"Fox",    {"Carmelita", "Diane", "Krystal"}},
            {"Goat",   {"Sybil",     "Toriel"}}
    };
};

Quindi, dichiariamo quante righe ci sono in questo modello in src/components/model.cpp:

1
2
3
4
5
#include "model.h"

int Model::rowCount(const QModelIndex &) const {
    return m_list.count();
}

Sostituire e implementare roleNames()

Prima di sovrascrivere roleNames(), dobbiamo dichiarare quali sono i ruoli nel lato C++ utilizzando un'enumerazione pubblica. La ragione di ciò è che questi valori enum vengono passati a data() ogni volta che QML accede a un ruolo corrispondente e come tale possiamo fare in modo che data() restituisca ciò che vogliamo.

Iniziamo con la creazione dell'enumerazione per i ruoli in src/components/model.h, dove ogni valore è un ruolo per il lato C++.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#pragma once

#include <QAbstractListModel>
#include <qqmlintegration.h>

class Model : public QAbstractListModel {
    Q_OBJECT
    QML_ELEMENT
public:
    enum Roles {
        SpeciesRole = Qt::UserRole,
        CharactersRole
    };

    QHash<int, QByteArray> roleNames() const override;
    int rowCount(const QModelIndex &) const override;

private:
    QMap<QString, QStringList> m_list = {
            {"Feline", {"Tigress",   "Waai Fuu"}},
            {"Fox",    {"Carmelita", "Diane", "Krystal"}},
            {"Goat",   {"Sybil",     "Toriel"}}
    };
};

Una volta risolto questo problema, possiamo finalmente creare quali siano questi ruoli nel lato QML utilizzando un QHash dove le chiavi sono i valori enumerati abbinati a QByteArrays. Questo dovrebbe andare su "src/components/model.cpp". Il testo nel QByteArray è ciò che viene utilizzato nel codice QML effettivo.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include "model.h"

int Model::rowCount(const QModelIndex &) const {
    return m_list.count();
}

QHash<int, QByteArray> Model::roleNames() const {
    return {
        {SpeciesRole,   "species"},
        {CharactersRole, "characters"}
    };
}

Nel nostro modello di esempio, il ruolo "specie" può essere utilizzato per recuperare la chiave QString "Felino", "Volpe", "Capra", ciascuna in un delegato separato. Lo stesso può essere fatto con i valori QStringList per l'elenco dei nomi dei caratteri.

Sostituire e implementare data()

Ci sono due parametri che vengono passati a data(): index e role. L'indice è la posizione dei dati nel modello. Come affermato in precedenza, "role" viene utilizzato da QML per ottenere dati specifici restituiti quando accede a un ruolo.

In data(), possiamo utilizzare un'istruzione switch per restituire i dati e il tipo di dati appropriati a seconda del ruolo, il che è possibile poiché data() restituisce un QVariant. Dobbiamo comunque assicurarci di ottenere la posizione appropriata dei dati. Nell'esempio seguente, puoi vedere che viene dichiarata una nuova variabile iteratore, che viene impostata dall'inizio dell'elenco più la riga dell'indice, e i dati a cui punta l'iteratore sono ciò che viene restituito.

Tuttavia, non possiamo semplicemente restituire i dati che vogliamo. Potremmo provare ad associare i dati a una proprietà con un tipo di dati incompatibile, ad esempio una QStringList a una QString. Potrebbe essere necessario eseguire la conversione dei dati affinché i dati vengano visualizzati correttamente. Per questo creiamo una nuova funzione privata e statica denominata formatList().

Il risultato è il seguente codice in src/components/model.cpp:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include "model.h"

int Model::rowCount(const QModelIndex &) const {
    return m_list.count();
}

QHash<int, QByteArray> Model::roleNames() const {
    return {
        {SpeciesRole,   "species"},
        {CharactersRole, "characters"}
    };
}

QVariant Model::data(const QModelIndex &index, int role) const {
    const auto it = std::next(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;
}

E il seguente codice in src/components/model.h:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#pragma once

#include <QAbstractListModel>
#include <qqmlintegration.h>

class Model : public QAbstractListModel {
    Q_OBJECT
    QML_ELEMENT
public:
    enum Roles {
        SpeciesRole = Qt::UserRole,
        CharactersRole
    };
    int rowCount(const QModelIndex &) const override;
    QHash<int, QByteArray> roleNames() const override;
    QVariant data(const QModelIndex &index, int role) const override;

private:
    QMap<QString, QStringList> m_list = {
        {"Feline", {"Tigress",   "Waai Fuu"}},
        {"Fox",    {"Carmelita", "Diane", "Krystal"}},
        {"Goat",   {"Sybil",     "Toriel"}}
    };
    static QString formatList(const QStringList& list);
};

Utilizzo delle classi in QML

Il file QML utilizzato conterrà solo tre componenti Kirigami.AbstractCard, dove la chiave è l'intestazione e il valore è il contenuto. Queste schede vengono create delegando una AbstractCard utilizzando un Repeater, dove il modello personalizzato che abbiamo creato funge da modello. Si accede ai dati utilizzando la parola "model", seguita dai ruoli dichiarati in "roleNames()".

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as Controls
import org.kde.kirigami as Kirigami

Kirigami.ScrollablePage {
    title: "C++ models in QML"
    Model {
        id: customModel
    }
    ColumnLayout {
        anchors.left: parent.left
        anchors.right: parent.right
        Repeater {
            model: customModel
            delegate: Kirigami.AbstractCard {
                header: Kirigami.Heading {
                    text: model.species
                    level: 2
                }
                contentItem: Controls.Label {
                    text: model.characters
                }
            }
        }
    }
}
Una schermata dei modelli C++ nella pagina QML che mostra un elenco di personaggi organizzati per specie

Modifica dei dati

Modifica dei dati utilizzando dataChanged() e setData()

Potresti riscontrare una situazione in cui desideri modificare i dati nel modello e visualizzare le modifiche sul lato frontend. Ogni volta che modifichiamo i dati nel modello, dobbiamo emettere il segnale dataChanged() che applicherà tali modifiche sul lato frontend alle celle specifiche specificate nei suoi argomenti. In questo tutorial, possiamo semplicemente utilizzare l'argomento index di setData().

setData() è una funzione virtuale che puoi sovrascrivere in modo che la modifica dei dati dal lato frontend rifletta automaticamente tali modifiche sul lato backend. Richiede tre parametri:

  • index - la posizione dei dati.
  • value - Il contenuto dei nuovi dati.
  • "ruolo" - In questo contesto, il ruolo viene utilizzato per indicare alle visualizzazioni come dovrebbero gestire i dati. Il ruolo qui dovrebbe essere Qt::EditRole.

Il parametro role in questo caso viene utilizzato per garantire che setData() possa essere modificato tramite input dell'utente (Qt::EditRole). Utilizzando "index", possiamo utilizzarlo per determinare la posizione in cui i dati devono essere modificati con il contenuto di "value".

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include "model.h"

int Model::rowCount(const QModelIndex &) const {
    return m_list.count();
}

QHash<int, QByteArray> Model::roleNames() const {
    return {
        {SpeciesRole,   "species"},
        {CharactersRole, "characters"}
    };
}

QVariant Model::data(const QModelIndex &index, int role) const {
    const auto it = std::next(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;
}

bool Model::setData(const QModelIndex &index, const QVariant &value, int role) {
    if (!value.canConvert<QString>() && role != Qt::EditRole) {
        return false;
    }

    auto it = std::next(m_list.begin(), index.row());
    QString charactersUnformatted = value.toString();
    QStringList characters = charactersUnformatted.split(", ");

    m_list[it.key()] = characters;
    Q_EMIT dataChanged(index, index);

    return true;
}

Con la corrispondente aggiunta in src/components/model.h:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#pragma once

#include <QAbstractListModel>
#include <qqmlintegration.h>

class Model : public QAbstractListModel {
    Q_OBJECT
    QML_ELEMENT
public:
    enum Roles {
        SpeciesRole = Qt::UserRole,
        CharactersRole
    };
    int rowCount(const QModelIndex &) const override;
    QHash<int, QByteArray> roleNames() const override;
    QVariant data(const QModelIndex &index, int role) const override;
    bool setData(const QModelIndex &index, const QVariant &value, int role) override;
private:
    QMap<QString, QStringList> m_list = {
        {"Feline", {"Tigress",   "Waai Fuu"}},
        {"Fox",    {"Carmelita", "Diane", "Krystal"}},
        {"Goat",   {"Sybil",     "Toriel"}}
    };
    static QString formatList(const QStringList& list);
};

Aggiorniamo il codice QML in modo da poter aprire un prompt che ci consenta di modificare il modello utilizzando un Controls.Button allegato alle carte.

Aggiungi il seguente Kirigami.PromptDialog a src/components/ModelsPage.qml, insieme a un nuovo pulsante di modifica:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as Controls
import org.kde.kirigami as Kirigami
import org.kde.tutorial.components

Kirigami.ScrollablePage {
    title: "C++ models in QML"
    Model {
        id: customModel
    }
    ColumnLayout {
        anchors.left: parent.left
        anchors.right: parent.right
        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();
                            }
                        }
                    }
                }
            }
        }
    }
    Kirigami.PromptDialog {
        id: editPrompt
        property var model
        property alias text: editPromptText.text
        title: "Edit Characters"
        standardButtons: Kirigami.Dialog.Ok | Kirigami.Dialog.Cancel
        onAccepted: {
            const model = editPrompt.model;
            model.characters = editPromptText.text;
            editPrompt.close();
        }
        Controls.TextField {
            id: editPromptText
            onAccepted: editPrompt.accept()
        }
    }
}

Ora, ogni volta che i valori del modello cambiano nel frontend, le modifiche dovrebbero aggiornarsi automaticamente nel backend.

app_screenshot_1.png
app_screenshot_2.png

Aggiungere righe

Abbiamo aggiunto un modo per modificare i dati nelle chiavi esistenti di QMap e, nel front-end, ciò si riflette nella modifica dei contenuti all'interno di AbstractCards. Ma cosa succede se dobbiamo aggiungere una nuova voce chiave nella QMap e rifletterla sul lato QML? Facciamolo creando un nuovo metodo richiamabile dal lato QML per eseguire questa attività.

Per rendere il metodo visibile in QML, dobbiamo iniziare la dichiarazione del metodo con la macro Q_INVOKABLE. Questo metodo includerà anche un parametro stringa, destinato a essere la nuova chiave nella QMap.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#pragma once

#include <QAbstractListModel>
#include <qqmlintegration.h>

class Model : public QAbstractListModel {
    Q_OBJECT
    QML_ELEMENT
public:
    enum Roles {
        SpeciesRole = Qt::UserRole,
        CharactersRole
    };
    int rowCount(const QModelIndex &) const override;
    QHash<int, QByteArray> roleNames() const override;
    QVariant data(const QModelIndex &index, int role) const override;
    Q_INVOKABLE void addSpecies(const QString &species);

private:
    QMap<QString, QStringList> m_list = {
        {"Feline", {"Tigress",   "Waai Fuu"}},
        {"Fox",    {"Carmelita", "Diane", "Krystal"}},
        {"Goat",   {"Sybil",     "Toriel"}}
    };
    static QString formatList(const QStringList& list);
};

All'interno di questo metodo, dobbiamo dire a Qt che vogliamo creare più righe nel modello. Questo viene fatto chiamando beginInsertRows() per iniziare l'operazione di aggiunta di righe, seguito dall'inserimento di tutto ciò di cui abbiamo bisogno, quindi utilizzare endInsertRows() per terminare l'operazione. Tuttavia, dobbiamo ancora emettere dataChanged() alla fine. Questa volta aggiorneremo tutte le righe, dalla prima all'ultima poiché la QMap potrebbe riorganizzarsi in ordine alfabetico e dobbiamo catturarlo su tutte le righe.

Quando chiamiamo beginInsertRows(), dobbiamo prima passare una classe QModelIndex per specificare la posizione in cui dovrebbero essere aggiunte le nuove righe, seguita da quali saranno i numeri della prima e dell'ultima riga. In questo tutorial, il primo argomento sarà semplicemente QModelIndex() poiché non è necessario utilizzare il parametro qui. Possiamo semplicemente utilizzare la dimensione della riga corrente per il numero della prima e dell'ultima riga, poiché aggiungeremo semplicemente una riga alla fine del modello.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include "model.h"

int Model::rowCount(const QModelIndex &) const {
    return m_list.count();
}

QHash<int, QByteArray> Model::roleNames() const {
    return {
        {SpeciesRole,   "species"},
        {CharactersRole, "characters"}
    };
}

QVariant Model::data(const QModelIndex &index, int role) const {
    const auto it = std::next(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;
}

bool Model::setData(const QModelIndex &index, const QVariant &value, int role) {
    if (!value.canConvert<QString>() && role != Qt::EditRole) {
        return false;
    }

    auto it = std::next(m_list.begin(), index.row());
    QString charactersUnformatted = value.toString();
    QStringList characters = charactersUnformatted.split(", ");

    m_list[it.key()] = characters;
    Q_EMIT dataChanged(index, index);

    return true;
}

void Model::addSpecies(const QString& species) {
    beginInsertRows(QModelIndex(), m_list.size() - 1, m_list.size() - 1);
    m_list.insert(species, {});
    endInsertRows();
    Q_EMIT dataChanged(index(0), index(m_list.size() - 1));
}

Aggiorniamo il codice QML in modo da avere la possibilità di aggiungere una nuova chiave alla QMap.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as Controls
import org.kde.kirigami as Kirigami
import org.kde.tutorial.components

Kirigami.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: addPrompt
        title: "Add New Species"
        standardButtons: Kirigami.Dialog.Ok
        onAccepted: {
            customModel.addSpecies(addPromptText.text);
            addPromptText.text = ""; // Clear TextField every time it's done
            addPrompt.close();
        }
        Controls.TextField {
            id: addPromptText
            Layout.fillWidth: true
            onAccepted: addPrompt.accept()
        }
    }
    Kirigami.PromptDialog {
        id: editPrompt
        // ...
    }
}

Ora, dovremmo ricevere una nuova azione nella parte superiore dell'applicazione che fa apparire un prompt che consente di aggiungere un nuovo elemento al modello, con i nostri dati personalizzati.

app_screenshot_add_1.png
app_screenshot_add_2.png

Rimozione di righe

Il modo in cui rimuovi le righe è simile all'aggiunta di righe. Creiamo un altro metodo che chiameremo in QML. Questa volta utilizzeremo un parametro aggiuntivo, ovvero un numero intero che rappresenta il numero di riga. Il nome della specie viene utilizzato per eliminare la chiave dalla QMap, mentre il numero di riga verrà utilizzato per eliminare la riga sul front-end.

Aggiungi una nuova funzione Q_INVOKABLE denominata deleteSpecies() in src/components/model.h:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#pragma once

#include <QAbstractListModel>
#include <qqmlintegration.h>

class Model : public QAbstractListModel {
    Q_OBJECT
    QML_ELEMENT
public:
    enum Roles {
        SpeciesRole = Qt::UserRole,
        CharactersRole
    };
    int rowCount(const QModelIndex &) const override;
    QHash<int, QByteArray> roleNames() const override;
    QVariant data(const QModelIndex &index, int role) const override;
    Q_INVOKABLE void addSpecies(const QString &species);
    Q_INVOKABLE void deleteSpecies(const QString &speciesName, const int &rowIndex);

private:
    QMap<QString, QStringList> m_list = {
        {"Feline", {"Tigress",   "Waai Fuu"}},
        {"Fox",    {"Carmelita", "Diane", "Krystal"}},
        {"Goat",   {"Sybil",     "Toriel"}}
    };
    static QString formatList(const QStringList& list);
};

Con un'implementazione corrispondente in src/components/model.cpp:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include "model.h"

int Model::rowCount(const QModelIndex &) const {
    return m_list.count();
}

QHash<int, QByteArray> Model::roleNames() const {
    return {
        {SpeciesRole,   "species"},
        {CharactersRole, "characters"}
    };
}

QVariant Model::data(const QModelIndex &index, int role) const {
    const auto it = std::next(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;
}

bool Model::setData(const QModelIndex &index, const QVariant &value, int role) {
    if (!value.canConvert<QString>() && role != Qt::EditRole) {
        return false;
    }

    auto it = std::next(m_list.begin(), index.row());
    QString charactersUnformatted = value.toString();
    QStringList characters = charactersUnformatted.split(", ");

    m_list[it.key()] = characters;
    Q_EMIT dataChanged(index, index);

    return true;
}

void Model::addSpecies(const QString& species) {
    beginInsertRows(QModelIndex(), m_list.size() - 1, m_list.size() - 1);
    m_list.insert(species, {});
    endInsertRows();
    Q_EMIT dataChanged(index(0), index(m_list.size() - 1));
}

void Model::deleteSpecies(const QString &speciesName, const int& rowIndex) {
    beginRemoveRows(QModelIndex(), rowIndex, rowIndex);
    m_list.remove(speciesName);
    endRemoveRows();
    Q_EMIT dataChanged(index(0), index(m_list.size() - 1));
}

Ora aggiorniamo l'applicazione in modo che venga visualizzato un pulsante "Elimina" in un RowLayout accanto al pulsante di modifica all'interno della nostra AbstractCard e colleghiamolo al nostro metodo di eliminazione.

24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
        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
                        }
                        RowLayout {
                            Layout.fillWidth: true
                            Controls.Button {
                                text: "Edit"
                                onClicked: {
                                    editPrompt.text = model.characters;
                                    editPrompt.model = model;
                                    editPrompt.open();
                                }
                            }
                            Controls.Button {
                                text: "Delete"
                                onClicked: {
                                    customModel.deleteSpecies(model.species, index);
                                }
                            }
                        }
                    }
                }
            }
        }
app_screenshot_del_1.png
app_screenshot_del_2.png

Il nostro programma finora

Codice d'uscita

src/CMakeLists.txt
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
add_executable(kirigami-hello)

ecm_add_qml_module(kirigami-hello
    URI
    org.kde.tutorial
)

target_sources(kirigami-hello
    PRIVATE
    main.cpp
)

ecm_target_qml_sources(kirigami-hello
    SOURCES
    Main.qml
)

target_link_libraries(kirigami-hello
    PRIVATE
    Qt6::Quick
    Qt6::Qml
    Qt6::Gui
    Qt6::QuickControls2
    Qt6::Widgets
    KF6::I18n
    KF6::CoreAddons
    KF6::IconThemes
    kirigami-hello-components
)

install(TARGETS kirigami-hello ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})

add_subdirectory(components)
src/main.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <QApplication>
#include <QQmlApplicationEngine>
#include <QtQml>
#include <QUrl>
#include <QQuickStyle>
#include <KLocalizedContext>
#include <KLocalizedString>
#include <KIconTheme>

int main(int argc, char *argv[])
{
    KIconTheme::initTheme();
    QApplication app(argc, argv);
    KLocalizedString::setApplicationDomain("tutorial");
    QApplication::setOrganizationName(QStringLiteral("KDE"));
    QApplication::setOrganizationDomain(QStringLiteral("kde.org"));
    QApplication::setApplicationName(QStringLiteral("Kirigami Tutorial"));
    QApplication::setDesktopFileName(QStringLiteral("org.kde.tutorial"));

    QApplication::setStyle(QStringLiteral("breeze"));
    if (qEnvironmentVariableIsEmpty("QT_QUICK_CONTROLS_STYLE")) {
        QQuickStyle::setStyle(QStringLiteral("org.kde.desktop"));
    }

    QQmlApplicationEngine engine;

    engine.rootContext()->setContextObject(new KLocalizedContext(&engine));
    engine.loadFromModule("org.kde.tutorial", "Main");

    if (engine.rootObjects().isEmpty()) {
        return -1;
    }

    return app.exec();
}
src/components/KountdownDelegate.qml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as Controls
import org.kde.kirigami as Kirigami

Kirigami.AbstractCard {
    contentItem: Item {
        implicitWidth: delegateLayout.implicitWidth
        implicitHeight: delegateLayout.implicitHeight
        GridLayout {
            id: delegateLayout
            anchors {
                left: parent.left
                top: parent.top
                right: parent.right
            }
            rowSpacing: Kirigami.Units.largeSpacing
            columnSpacing: Kirigami.Units.largeSpacing
            columns: root.wideScreen ? 4 : 2

            Kirigami.Heading {
                level: 1
                text: i18n("%1 days", Math.round((date-Date.now())/86400000))
            }

            ColumnLayout {
                Kirigami.Heading {
                    Layout.fillWidth: true
                    level: 2
                    text: name
                }
                Kirigami.Separator {
                    Layout.fillWidth: true
                    visible: description.length > 0
                }
                Controls.Label {
                    Layout.fillWidth: true
                    wrapMode: Text.WordWrap
                    text: description
                    visible: description.length > 0
                }
            }
            Controls.Button {
                Layout.alignment: Qt.AlignRight
                Layout.columnSpan: 2
                text: i18n("Edit")
            }
        }
    }
}
src/components/AddDialog.qml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as Controls
import org.kde.kirigami as Kirigami

Kirigami.Dialog {
    id: addDialog
    title: i18nc("@title:window", "Add kountdown")
    standardButtons: Kirigami.Dialog.Ok | Kirigami.Dialog.Cancel
    padding: Kirigami.Units.largeSpacing
    preferredWidth: Kirigami.Units.gridUnit * 20

    Kirigami.FormLayout {
        Controls.TextField {
            id: nameField
            Kirigami.FormData.label: i18nc("@label:textbox", "Name*:")
            onAccepted: descriptionField.forceActiveFocus()
        }
        Controls.TextField {
            id: descriptionField
            Kirigami.FormData.label: i18nc("@label:textbox", "Description:")
            onAccepted: dateField.forceActiveFocus()
        }
        Controls.TextField {
            id: dateField
            Kirigami.FormData.label: i18nc("@label:textbox", "ISO Date*:")
            inputMask: "D999-99-99"
            onAccepted: addDialog.accepted()
        }
        Controls.Label {
            text: "* = required fields"
        }
    }
    Component.onCompleted: {
        const button = standardButton(Kirigami.Dialog.Ok);
        button.enabled = Qt.binding( () => requiredFieldsFilled() );
    }
    onAccepted: {
        if (!addDialog.requiredFieldsFilled()) return;
        appendDataToModel();
        clearFieldsAndClose();
    }
    function requiredFieldsFilled() {
        return (nameField.text !== "" && dateField.acceptableInput);
    }
    function appendDataToModel() {
        kountdownModel.append({
            name: nameField.text,
            description: descriptionField.text,
            date: new Date(dateField.text)
        });
    }
    function clearFieldsAndClose() {
        nameField.text = ""
        descriptionField.text = ""
        dateField.text = ""
        addDialog.close();
    }
}
src/components/ExposePage.qml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import org.kde.kirigami as Kirigami
import org.kde.tutorial.components

Kirigami.Page {
    title: "Exposing to QML Tutorial"
    Kirigami.Heading {
        anchors.centerIn: parent
        text: 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>

class Backend : public QObject
{
    Q_OBJECT
    QML_ELEMENT
    QML_SINGLETON
    Q_PROPERTY(QString introductionText READ introductionText WRITE setIntroductionText NOTIFY introductionTextChanged)
public:
    explicit Backend(QObject *parent = nullptr);
    QString introductionText() const;
    void setIntroductionText(const QString &introductionText);
    Q_SIGNAL void introductionTextChanged();
private:
    QString m_introductionText = QStringLiteral("Hello World!");
};
src/components/backend.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#include "backend.h"

Backend::Backend(QObject *parent)
    : QObject(parent)
{}

QString Backend::introductionText() const
{
    return m_introductionText;
}

void Backend::setIntroductionText(const QString &introductionText)
{
    m_introductionText = introductionText;
    Q_EMIT introductionTextChanged();
}

Codice scritto/modificato in questa pagina:

CMakeLists.txt
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
cmake_minimum_required(VERSION 3.20)
project(kirigami-tutorial)

find_package(ECM 6.0.0 REQUIRED NO_MODULE)
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH})

include(KDEInstallDirs)
include(KDECMakeSettings)
include(KDECompilerSettings NO_POLICY_SCOPE)
include(ECMFindQmlModule)
include(ECMQmlModule)
remove_definitions(-DQT_NO_CAST_FROM_ASCII)

find_package(Qt6 REQUIRED COMPONENTS
    Core
    Quick
    Test
    Gui
    QuickControls2
    Widgets
)

find_package(KF6 REQUIRED COMPONENTS
    Kirigami
    I18n
    CoreAddons
    QQC2DesktopStyle
    IconThemes
)

ecm_find_qmlmodule(org.kde.kirigami REQUIRED)

add_subdirectory(src)

install(PROGRAMS org.kde.tutorial.desktop DESTINATION ${KDE_INSTALL_APPDIR})

feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)
src/Main.qml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as Controls
import org.kde.kirigami as Kirigami
import org.kde.tutorial.components

Kirigami.ApplicationWindow {
    id: root

    width: 600
    height: 400

    title: i18nc("@title:window", "Day Kountdown")

    globalDrawer: Kirigami.GlobalDrawer {
        isMenu: true
        actions: [
            Kirigami.Action {
                text: i18n("Exposing to QML Tutorial")
                icon.name: "kde"
                onTriggered: pageStack.push(Qt.createComponent("org.kde.tutorial.components", "ExposePage"))
            },
            Kirigami.Action {
                text: i18n("C++ models in QML tutorial")
                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.Quit
                onTriggered: Qt.quit()
            }
        ]
    }

    ListModel {
        id: kountdownModel
    }

    AddDialog {
        id: addDialog
    }

    pageStack.initialPage: Kirigami.ScrollablePage {
        title: i18nc("@title", "Kountdown")

        actions: [
            Kirigami.Action {
                id: addAction
                icon.name: "list-add-symbolic"
                text: i18nc("@action:button", "Add kountdown")
                onTriggered: addDialog.open()
            }
        ]

        Kirigami.CardsListView {
            id: cardsView
            model: kountdownModel
            delegate: KountdownDelegate {}
        }
    }
}
src/components/CMakeLists.txt
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
add_library(kirigami-hello-components)

ecm_add_qml_module(kirigami-hello-components
    URI "org.kde.tutorial.components"
    GENERATE_PLUGIN_SOURCE
)

ecm_target_qml_sources(kirigami-hello-components
    SOURCES
    AddDialog.qml
    KountdownDelegate.qml
    ExposePage.qml
    ModelsPage.qml
)

target_sources(kirigami-hello-components
    PRIVATE
    backend.cpp backend.h
    model.cpp model.h
)

ecm_finalize_qml_module(kirigami-hello-components)

install(TARGETS kirigami-hello-components ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
src/components/ModelsPage.qml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as Controls
import org.kde.kirigami as Kirigami
import org.kde.tutorial.components

Kirigami.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.left
        anchors.right: parent.right
        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
                        }
                        RowLayout {
                            Layout.fillWidth: true
                            Controls.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: addPrompt
        title: "Add New Species"
        standardButtons: Kirigami.Dialog.Ok
        onAccepted: {
            customModel.addSpecies(addPromptText.text);
            addPromptText.text = ""; // Clear TextField every time it's done
            addPrompt.close();
        }
        Controls.TextField {
            id: addPromptText
            Layout.fillWidth: true
            onAccepted: addPrompt.accept()
        }
    }

    Kirigami.PromptDialog {
        id: editPrompt
        property var model
        property alias text: editPromptText.text
        title: "Edit Characters"
        standardButtons: Kirigami.Dialog.Ok | Kirigami.Dialog.Cancel
        onAccepted: {
            const model = editPrompt.model;
            model.characters = editPromptText.text;
            editPrompt.close();
        }
        Controls.TextField {
            id: editPromptText
            onAccepted: editPrompt.accept()
        }
    }
}
src/components/model.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#pragma once

#include <QAbstractListModel>
#include <qqmlintegration.h>

class Model : public QAbstractListModel {
    Q_OBJECT
    QML_ELEMENT
public:
    enum Roles {
        SpeciesRole = Qt::UserRole,
        CharactersRole
    };
    int rowCount(const QModelIndex &) const override;
    QHash<int, QByteArray> roleNames() const override;
    QVariant data(const QModelIndex &index, int role) const override;
    bool setData(const QModelIndex &index, const QVariant &value, int role) override;
    Q_INVOKABLE void addSpecies(const QString &species);
    Q_INVOKABLE void deleteSpecies(const QString &speciesName, const int &rowIndex);
private:
    QMap<QString, QStringList> m_list = {
        {"Feline", {"Tigress",   "Waai Fuu"}},
        {"Fox",    {"Carmelita", "Diane", "Krystal"}},
        {"Goat",   {"Sybil",     "Toriel"}}
    };
    static QString formatList(const QStringList& list);
};
src/components/model.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include "model.h"

int Model::rowCount(const QModelIndex &) const {
    return m_list.count();
}

QHash<int, QByteArray> Model::roleNames() const {
    return {
        {SpeciesRole,   "species"},
        {CharactersRole, "characters"}
    };
}

QVariant Model::data(const QModelIndex &index, int role) const {
    const auto it = std::next(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;
}

bool Model::setData(const QModelIndex &index, const QVariant &value, int role) {
    if (!value.canConvert<QString>() && role != Qt::EditRole) {
        return false;
    }

    auto it = std::next(m_list.begin(), index.row());
    QString charactersUnformatted = value.toString();
    QStringList characters = charactersUnformatted.split(", ");

    m_list[it.key()] = characters;
    Q_EMIT dataChanged(index, index);

    return true;
}

void Model::addSpecies(const QString& species) {
    beginInsertRows(QModelIndex(), m_list.size() - 1, m_list.size() - 1);
    m_list.insert(species, {});
    endInsertRows();
    Q_EMIT dataChanged(index(0), index(m_list.size() - 1));
}

void Model::deleteSpecies(const QString &speciesName, const int& rowIndex) {
    beginRemoveRows(QModelIndex(), rowIndex, rowIndex);
    m_list.remove(speciesName);
    endRemoveRows();
    Q_EMIT dataChanged(index(0), index(m_list.size() - 1));
}

Ulteriori informazioni

Per ulteriori informazioni, vedere Utilizzo di modelli C++ con Qt Quick Views e Programmazione modello/vista.