Sur cette page

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

L'exemple du Jeu de la vie de Conway montre comment le type d'TableView s QML peut être utilisé pour afficher un modèle C++ que l'utilisateur peut faire défiler.

Grille du « Jeu de la vie » de Conway présentant des motifs cellulaires, avec un bouton « Suivant » et des commandes de lecture

Exécution de l'exemple

Vous pouvez exécuter l’exemple à partir de :

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 de type Rectangle. Nous lisons la valeur de la cellule et 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, la vue « TableView » est défilée vers son centre à l’aide de ses propriétés « contentX » et « contentY » pour mettre à jour la position de défilement, ainsi que des propriétés « contentWidth » et « contentHeight » pour calculer l’emplacement vers lequel 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 ` hérite de ` QAbstractTableModel ` afin de pouvoir servir de modèle à notre composant ` TableView `. Elle doit donc implémenter certaines fonctions afin 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 afin que le composant TableView puisse connaître la taille du tableau. Elles renvoient 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’énumération Roles dans notre code C++ ; cela correspond à la propriété value dans le code QML (le lien entre ces deux éléments est établi 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 lorsqu’une valeur de propriété est définie depuis l’interface QML : dans notre exemple, elle bascule l’état d’une cellule lorsqu’on clique dessus. Tout comme 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 ` 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 l’utilisateur a cliqué est concernée ; ainsi, la plage du tableau à mettre à jour commence et se termine à l’index de cette 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 depuis le code QML, car elle contient la macro ` Q_INVOKABLE ` dans sa définition. Elle lance une itération du jeu, soit lorsque l’utilisateur clique sur le bouton « Suivant », 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 voisines. Une fois le nouvel état 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 motif est chargé pour illustrer 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 le motif entièrement chargé.

Exemple de projet sur 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.