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.
					
					要运行范例从 Qt Creator ,打开 欢迎 模式,然后选择范例从 范例 。更多信息,拜访 构建和运行范例 .
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
						
						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.
					
contentX: (contentWidth - width) / 2; contentY: (contentHeight - height) / 2;
						当应用程序启动时,
						
							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.
					
model: GameOfLifeModel { id: gameOfLifeModel }
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.
					
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
						
						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.
					
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
						
值
						
						property in the QML code (the link between these two is made by the
						
roleNames()
						
						function of our C++ class).
					
						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.
						
					
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
						
						和
						
role
						
						parameter. Additionally, the new value is passed as a
						
							QVariant
						
						, that we convert to a boolean using the
						
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.
					
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
						
							下一
						
						button or when the Timer emits a
						
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.
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.