En esta página

Qt Quick Ejemplos de TableView - Conway's Game of Life

El ejemplo Conway 's Game of Life muestra como el tipo QML TableView puede ser utilizado para mostrar un modelo C++ que el usuario puede desplazar.

Ejecutar el ejemplo

Para ejecutar el ejemplo desde Qt Creatorabra el modo Welcome y seleccione el ejemplo de Examples. Para más información, ver Qt Creator: Tutorial: Construir y ejecutar.

La interfaz de usuario 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
        }
    }

El ejemplo utiliza el componente TableView para mostrar una cuadrícula de celdas. Cada una de estas celdas es dibujada en la pantalla por el delegado de TableView, que es un componente QML Rectángulo. Leemos el valor de la celda y lo cambiamos utilizando model.value cuando el usuario hace clic en ella.

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

Cuando se inicia la aplicación, el TableView se desplaza hasta su centro utilizando sus propiedades contentX y contentY para actualizar la posición de desplazamiento, y las contentWidth y contentHeight para calcular hasta dónde debe desplazarse la vista.

model: GameOfLifeModel {
    id: gameOfLifeModel
}

El modelo 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 clase GameOfLifeModel extiende QAbstractTableModel para que pueda ser utilizada como modelo de nuestro componente TableView. Por lo tanto, necesita implementar algunas funciones para que el componente TableView pueda interactuar con el modelo. Como puedes ver en la parte private de la clase, el modelo utiliza un array de tamaño fijo para almacenar el estado actual de todas las celdas. También utilizamos la macro QML_ELEMENT para exponer la clase a 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;
}

Aquí se implementan los métodos rowCount y columnCount para que el componente TableView pueda conocer el tamaño de la tabla. Simplemente devuelve los valores de las constantes width y 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()})]);
}

Este método es llamado cuando el componente TableView solicita algún dato del modelo. En nuestro ejemplo, sólo tenemos un dato por celda: si está viva o no. Esta información está representada por el valor CellRole del enum Roles en nuestro código C++; éste corresponde a la propiedad value en el código QML (el enlace entre ambos se realiza mediante la función roleNames() de nuestra clase C++).

La clase GameOfLifeModel puede identificar de qué celda procedían los datos solicitados con el parámetro index, que es un QModelIndex que contiene una fila y una columna.

Actualización de los datos

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;
}

El método setData es llamado cuando se establece el valor de una propiedad desde la interfaz QML: en nuestro ejemplo, cambia el estado de una celda cuando se hace clic sobre ella. Al igual que la función data(), este método recibe un parámetro index y otro role. Además, el nuevo valor se pasa como QVariant, que convertimos en booleano utilizando la función toBool.

Cuando actualizamos el estado interno de nuestro objeto modelo, necesitamos emitir una señal dataChanged para decirle al componente TableView que necesita actualizar los datos mostrados. En este caso, sólo se ve afectada la celda sobre la que se ha hecho clic, por lo que el rango de la tabla que debe actualizarse comienza y termina en el índice de la celda.

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});
}

Esta función puede ser llamada directamente desde el código QML, ya que contiene la macro Q_INVOKABLE en su definición. Reproduce una iteración del juego, ya sea cuando el usuario pulsa el botón Siguiente o cuando el Temporizador emite una señal triggered().

Siguiendo las reglas del Juego de la Vida de Conway, se calcula un nuevo estado para cada celda dependiendo del estado actual de sus vecinas. Cuando el nuevo estado se ha calculado para toda la cuadrícula, sustituye al estado actual y se emite una señal dataChanged para toda la tabla.

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});
}

Cuando se abre la aplicación, se carga un patrón para demostrar cómo funciona el Juego de la Vida de Conway. Estas dos funciones cargan el fichero donde se almacena el patrón y lo analizan. Al igual que en la función nextStep, se emite una señal dataChanged para toda la tabla una vez que el patrón se ha cargado completamente.

Proyecto de ejemplo @ 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.