Sur cette page

Qt Quick Exemples de TableView - Le jeu de la vie de Conway

L'exemple Conway's Game of Life montre comment le type QML TableView peut être utilisé pour afficher un modèle C++ que l'utilisateur peut faire défiler.

Exécution de l'exemple

Pour exécuter l'exemple à partir de Qt Creatorouvrez le mode Welcome et sélectionnez l'exemple à partir de Examples. Pour plus d'informations, voir Qt Creator: Tutoriel : Construire et exécuter.

L'interface utilisateur QML

TableView {
    id: tableView
    anchors.fill: parent

    rowSpacing: 1
    columnSpacing: 1

    ScrollBar.horizontal: ScrollBar {}
    ScrollBar.vertical: ScrollBar {}

    delegate: Rectangle {
        id: cell
        implicitWidth: 15
        implicitHeight: 15

        required property var model
        required property bool value

        color: value ? "#f3f3f4" : "#b5b7bf"

        MouseArea {
            anchors.fill: parent
            onClicked: parent.model.value = !parent.value
        }
    }

L'exemple utilise le composant TableView pour afficher une grille de cellules. Chacune de ces cellules est dessinée à l'écran par le délégué de TableView, qui est un composant QML Rectangle. Nous lisons la valeur de la cellule et nous la modifions à l'aide de model.value lorsque l'utilisateur clique dessus.

contentX: (contentWidth - width) / 2;
contentY: (contentHeight - height) / 2;

Au démarrage de l'application, le site TableView est déplacé vers son centre en utilisant ses propriétés contentX et contentY pour mettre à jour la position de défilement, et les propriétés contentWidth et contentHeight pour calculer l'endroit où la vue doit être déplacée.

model: GameOfLifeModel {
    id: gameOfLifeModel
}

Le modèle C++

class GameOfLifeModel : public QAbstractTableModel
{
    Q_OBJECT
    QML_ELEMENT

public:
    enum Roles {
        CellRole
    };
    Q_ENUM(Roles)

    QHash<int, QByteArray> roleNames() const override {
        return {
            { CellRole, "value" }
        };
    }

    explicit GameOfLifeModel(QObject *parent = nullptr);

    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
    int columnCount(const QModelIndex &parent = QModelIndex()) const override;

    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
    bool setData(const QModelIndex &index, const QVariant &value,
                 int role = Qt::EditRole) override;

    Qt::ItemFlags flags(const QModelIndex &index) const override;

    Q_INVOKABLE void nextStep();
    Q_INVOKABLE bool loadFile(const QString &fileName);
    Q_INVOKABLE void loadPattern(const QString &plainText);
    Q_INVOKABLE void clear();

private:
    static constexpr int width = 256;
    static constexpr int height = 256;
    static constexpr int size = width * height;

    using StateContainer = std::array<bool, size>;
    StateContainer m_currentState;

    int cellNeighborsCount(const QPoint &cellCoordinates) const;
    static bool areCellCoordinatesValid(const QPoint &coordinates);
    static QPoint cellCoordinatesFromIndex(int cellIndex);
    static std::size_t cellIndex(const QPoint &coordinates);
};

La classe GameOfLifeModel étend QAbstractTableModel afin de pouvoir être utilisée comme modèle de notre composant TableView. Elle doit donc implémenter certaines fonctions pour que le composant TableView puisse interagir avec le modèle. Comme vous pouvez le voir dans la partie private de la classe, le modèle utilise un tableau de taille fixe pour stocker l'état actuel de toutes les cellules. Nous utilisons également la macro QML_ELEMENT afin d'exposer la classe à QML.

int GameOfLifeModel::rowCount(const QModelIndex &parent) const
{
    if (parent.isValid())
        return 0;

    return height;
}

int GameOfLifeModel::columnCount(const QModelIndex &parent) const
{
    if (parent.isValid())
        return 0;

    return width;
}

Ici, les méthodes rowCount et columnCount sont implémentées pour que le composant TableView puisse connaître la taille du tableau. Elle renvoie simplement les valeurs des constantes width et height.

QVariant GameOfLifeModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid() || role != CellRole)
        return QVariant();

    return QVariant(m_currentState[cellIndex({index.column(), index.row()})]);
}

Cette méthode est appelée lorsque le composant TableView demande des données au modèle. Dans notre exemple, nous n'avons qu'une seule donnée par cellule : si elle est vivante ou non. Cette information est représentée par la valeur CellRole de l'enum Roles dans notre code C++ ; cela correspond à la propriété value dans le code QML (le lien entre les deux est fait par la fonction roleNames() de notre classe C++).

La classe GameOfLifeModel peut identifier la cellule à partir de laquelle les données ont été demandées grâce au paramètre index, qui est un QModelIndex contenant une ligne et une colonne.

Mise à jour des données

bool GameOfLifeModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if (role != CellRole || data(index, role) == value)
        return false;

    m_currentState[cellIndex({index.column(), index.row()})] = value.toBool();
    emit dataChanged(index, index, {role});

    return true;
}

La méthode setData est appelée lorsque la valeur d'une propriété est définie à partir de l'interface QML : dans notre exemple, elle fait basculer l'état d'une cellule lorsqu'elle est cliquée. De la même manière que la fonction data(), cette méthode reçoit un paramètre index et un paramètre role. De plus, la nouvelle valeur est transmise sous la forme d'un paramètre QVariant, que nous convertissons en booléen à l'aide de la fonction toBool.

Lorsque nous mettons à jour l'état interne de notre objet modèle, nous devons émettre un signal dataChanged pour indiquer au composant TableView qu'il doit mettre à jour les données affichées. Dans ce cas, seule la cellule sur laquelle on a cliqué est affectée, de sorte que la plage du tableau qui doit être mise à jour commence et se termine à l'index de la cellule.

void GameOfLifeModel::nextStep()
{
    StateContainer newValues;

    for (std::size_t i = 0; i < size; ++i) {
        bool currentState = m_currentState[i];

        int cellNeighborsCount = this->cellNeighborsCount(cellCoordinatesFromIndex(static_cast<int>(i)));

        newValues[i] = currentState == true
                ? cellNeighborsCount == 2 || cellNeighborsCount == 3
                : cellNeighborsCount == 3;
    }

    m_currentState = std::move(newValues);

    emit dataChanged(index(0, 0), index(height - 1, width - 1), {CellRole});
}

Cette fonction peut être appelée directement à partir du code QML, car elle contient la macro Q_INVOKABLE dans sa définition. Elle joue une itération du jeu, soit lorsque l'utilisateur clique sur le bouton Next, soit lorsque le Timer émet un signal triggered().

Conformément aux règles du jeu de la vie de Conway, un nouvel état est calculé pour chaque cellule en fonction de l'état actuel de ses voisins. Lorsque le nouvel état a été calculé pour l'ensemble de la grille, il remplace l'état actuel et un signal dataChanged est émis pour l'ensemble du tableau.

bool GameOfLifeModel::loadFile(const QString &fileName)
{
    QFile file(fileName);
    if (!file.open(QIODevice::ReadOnly))
        return false;

    QTextStream in(&file);
    loadPattern(in.readAll());

    return true;
}

void GameOfLifeModel::loadPattern(const QString &plainText)
{
    clear();

    QStringList rows = plainText.split("\n");
    QSize patternSize(0, rows.count());
    for (QString row : rows) {
        if (row.size() > patternSize.width())
            patternSize.setWidth(row.size());
    }

    QPoint patternLocation((width - patternSize.width()) / 2, (height - patternSize.height()) / 2);

    for (int y = 0; y < patternSize.height(); ++y) {
        const QString line = rows[y];

        for (int x = 0; x < line.length(); ++x) {
            QPoint cellPosition(x + patternLocation.x(), y + patternLocation.y());
            m_currentState[cellIndex(cellPosition)] = line[x] == 'O';
        }
    }

    emit dataChanged(index(0, 0), index(height - 1, width - 1), {CellRole});
}

À l'ouverture de l'application, un modèle est chargé pour démontrer le fonctionnement du jeu de la vie de Conway. Ces deux fonctions chargent le fichier dans lequel le motif est stocké et l'analysent. Comme dans la fonction nextStep, un signal dataChanged est émis pour l'ensemble du tableau une fois que le motif a été entièrement chargé.

Exemple de projet @ code.qt.io

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.