Qt Quick 表格视图示例 - 康威生命游戏
《康威生命游戏》示例显示了如何使用 QML TableView 类型来显示用户可以浏览的 C++ 模型。
运行示例
要从 Qt Creator 运行示例,请打开 欢迎 模式并从 示例 中选择示例。如需更多信息,请访问 构建和运行示例。
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
属性将 TableView 居中,以更新滚动位置,并使用 contentWidth
和 contentHeight
计算视图应滚动到何处。
model: GameOfLifeModel { id: gameOfLifeModel }
C++ 模型
class GameOfLifeModel : public QAbstractTableModel { Q_OBJECT QML_ELEMENT Q_ENUMS(Roles) public: enum Roles { CellRole }; 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}); }
此函数可以直接从 QML 代码中调用,因为它在其定义中包含 Q_INVOKABLE 宏。它会在用户点击 下一步 按钮或计时器发出 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
信号。
© 2024 Qt 公司有限公司。本文档中的文档贡献的版权属于其各自的所有者。提供的文档是根据由自由软件基金会发布并于 GNU 自由文档许可版本 1.3 的条款授予的。Qt 和相应的徽标是芬兰和/或世界上其他国家的 Qt 公司有限公司的商标。所有其他商标都是其各自所有者的财产。