Qt Quick TableView examples - Conway’s Game of Life
Qt Quick TableView示例-Conway的生存游戏
The Conway’s Game of Life example shows how the QML TableView type can be used to display a C++ model that the user can pan around.
Conway的Game of Life示例展示了如何使用QML TableView类型显示用户可以平移的C++模型。
Running the Example
运行示例
To run the example from Qt Creator, open the Welcome mode and select the example from Examples. For more information, visit Building and Running an Example.
要从Qt Creator运行示例,请打开欢迎模式并从示例中选择示例。有关更多信息,请访问构建和运行示例。
The QML User Interface
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
}
}
The example uses the TableView component to display a grid of cells. Each of these cells is drawn on the screen by the TableView’s delegate, which is a Rectangle QML component. We read the cell’s value and we change it using model.value
when the user clicks it.
该示例使用TableView组件显示单元格网格。每个单元格都由TableView的委托绘制在屏幕上,该委托是矩形QML组件。我们读取单元格的值,然后使用model.value
更改它的值。
contentX: (contentWidth - width) / 2;
contentY: (contentHeight - height) / 2;
When the application starts, the TableView is scrolled to its center by using its contentX
and contentY
properties to update the scroll position, and the contentWidth
and contentHeight
to compute where the view should be scrolled to.
当应用程序启动时,TableView将滚动到其中心,方法是使用其contentX和contentY属性更新滚动位置,并使用contentWidth和contentHeight计算视图应该滚动到的位置。
model: GameOfLifeModel {
id: gameOfLifeModel
}
The C++ Model
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);
};
The GameOfLifeModel
class extends QAbstractTableModel so it can be used as the model of our TableView component. Therefore, it needs to implement some functions so the TableView component can interact with the model. As you can see in the private
part of the class, the model uses a fixed-size array to store the current state of all the cells. We also use the QML_ELEMENT macro in order to expose the class to QML.
GameOfLifeModel类扩展了QAbstractTableModel,因此它可以用作TableView组件的模型。因此,它需要实现一些函数,以便TableView组件可以与模型交互。正如您在类的私有部分中看到的,模型使用固定大小的数组来存储所有单元格的当前状态。我们还使用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;
}
Here, the rowCount
and columnCount
methods are implemented so the TableView component can know the size of the table. It simply returns the values of the width
and height
constants.
这里实现了rowCount和columnCount方法,以便TableView组件可以知道表的大小。它只返回宽度和高度常量的值。
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()})]);
}
This method is called when the TableView component requests some data from the model. In our example, we only have one piece of data by cell: whether it is alive or not. This information is represented by the CellRole
value of the Roles
enum in our C++ code; this corresponds to the value
property in the QML code (the link between these two is made by the roleNames()
function of our C++ class).
当TableView组件从模型请求一些数据时,将调用此方法。在我们的示例中,每个单元格只有一段数据:不管它是否活动。此信息由C++代码中Roles枚举的CellRole值表示;这对应于QML代码中的value属性(这两者之间的链接由C++类的roleNames()函数建立)。
The GameOfLifeModel
class can identify which cell was the data requested from with the index
parameter, which is a QModelIndex that contains a row and a column.
GameOfLifeModel类可以使用index参数来标识从哪个单元格请求数据,该参数是一个包含行和列的QModelIndex。
Updating the Data
更新数据
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;
}
The setData
method is called when a property’s value is set from the QML interface: in our example, it toggles a cell’s state when it is clicked. In the same way as the data()
function does, this method receives an index
and a role
parameter. Additionally, the new value is passed as a QVariant, that we convert to a boolean using the toBool
function.
当从QML接口设置属性值时,会调用setData方法:在我们的示例中,单击单元格时,它会切换单元格的状态。与data()函数相同,此方法接收index
和role
参数。此外,新值作为QVariant传递,我们使用toBool函数将其转换为布尔值。
When we update the internal state of our model object, we need to emit a dataChanged
signal to tell the TableView component that it needs to update the displayed data. In this case, only the cell that was clicked is affected, thus the range of the table that has to be updated begins and ends at the cell’s index.
当我们更新模型对象的内部状态时,我们需要发出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});
}
This function can be called directly from the QML code, because it contains the Q_INVOKABLE macro in its definition. It plays an iteration of the game, either when the user clicks the Next button or when the Timer emits a triggered()
signal.
可以直接从QML代码调用此函数,因为它在定义中包含Q_INVOKABLE宏。当用户单击“下一步”按钮或计时器发出triggered()信号时,它会反复播放游戏。
Following the Conway’s Game of Life rules, a new state is computed for each cell depending on the current state of its neighbors. When the new state has been computed for the whole grid, it replaces the current state and a dataChanged signal is emitted for the whole table.
按照康威的生存游戏规则,每个细胞都会根据其邻居的当前状态计算出一个新状态。计算完整个网格的新状态后,它将替换当前状态,并为整个表发出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});
}
When the application opens, a pattern is loaded to demonstrate how Conway’s Game of Life works. These two functions load the file where the pattern is stored and parse it. As in the nextStep
function, a dataChanged
signal is emitted for the whole table once the pattern has been fully loaded.
当应用程序打开时,将加载一个模式来演示Conway的“生存游戏”是如何工作的。这两个函数加载存储模式的文件并对其进行解析。与nextStep函数一样,一旦模式被完全加载,就会为整个表发出dataChanged信号。
© 2022 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.