本页内容

Qt Quick TableView 示例 - 康威的生命游戏

“康威的生命游戏”示例展示了如何利用 QML 的 `TableView ` 类型来显示一个 C++ 模型,用户可以对其进行平移操作。

康威的生命游戏网格,显示细胞图案,并带有“下一步”按钮和回放控制功能

运行示例

您可以通过以下方式运行该示例:

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

该示例使用TableView 组件来显示一个单元格网格。每个单元格都由TableView的委托(即一个Rectangle QML组件)绘制在屏幕上。当用户点击单元格时,我们会读取其值,并使用model.value 对其进行修改。

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

应用程序启动时,通过使用TableView 的contentX 和contentY 属性来更新滚动位置,并利用contentWidth 和contentHeight 来计算视图应滚动到的位置,从而将 滚动到中心位置。

model: GameOfLifeModel {
    id: gameOfLifeModel
}

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

GameOfLifeModel 类继承自QAbstractTableModel ,因此可作为TableView 组件的模型。 因此,它需要实现一些函数,以便TableView 组件能够与模型进行交互。如该类的private 部分所示,该模型使用一个固定大小的数组来存储所有单元格的当前状态。我们还使用了QML_ELEMENT 宏,以便将该类暴露给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;
}

在此,实现了rowCount 和columnCount 方法,以便TableView 组件能够获知表格的大小。这些方法仅返回width 和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()})]);
}

当TableView 组件向模型请求数据时,会调用此方法。 在我们的示例中,每个单元格仅包含一项数据:是否存活。在 C++ 代码中,该信息由Roles 枚举中的CellRole 值表示;这对应于 QML 代码中的value 属性(二者之间的关联由我们 C++ 类的roleNames() 函数建立)。

GameOfLifeModel 类可以通过index 参数识别请求数据来自哪个单元格,该参数是一个QModelIndex 类型,其中包含行和列信息。

更新数据

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

当从 QML 界面设置属性值时,会调用 `setData ` 方法:在本例中,该方法在单元格被点击时切换其状态。与 `data() ` 函数类似,该方法接收 `index ` 和 `role ` 参数。此外,新值作为 `QVariant` 传递,我们使用 `toBool ` 函数将其转换为布尔值。

当我们更新模型对象的内部状态时,需要发出一个dataChanged 信号,以告知TableView 组件需要更新显示的数据。在此情况下,仅被点击的单元格会受到影响,因此需要更新的表格范围以该单元格的索引为起始和结束点。

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

由于该函数的定义中包含了Q_INVOKABLE 宏,因此可以直接从QML代码中调用它。它会在用户点击“下一步”按钮或定时器(Timer)发出triggered() 信号时,执行一次游戏迭代。

遵循康威生命游戏的规则,每个单元格的新状态将根据其邻居的当前状态进行计算。当整个网格的新状态计算完成后,它将替换当前状态,并为整个表格发出dataChanged信号。

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

应用程序启动时,会加载一个图案以演示康威生命游戏的运行原理。以下两个函数负责加载存储图案的文件并进行解析。与nextStep 函数类似,当图案完全加载完毕后,会为整个表格发出dataChanged 信号。

示例项目 @ 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.