警告
本节包含从 C++ 自动翻译到 Python 的代码片段,可能包含错误。
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
属性来更新滚动位置,并且使用 contentWidth
和 contentHeight
来计算视图应该滚动到的地方。
model: GameOfLifeModel { id: gameOfLifeModel }
C++ 模型#
class GameOfLifeModel(QAbstractTableModel): Q_OBJECT QML_ELEMENT Q_ENUMS(Roles) # public Roles = { CellRole QHash<int, QByteArray> roleNames() override { return { { CellRole, "value" } GameOfLifeModel = explicit(QObject parent = None) int rowCount(QModelIndex parent = QModelIndex()) override int columnCount(QModelIndex parent = QModelIndex()) override QVariant data(QModelIndex index, int role = Qt.DisplayRole) override bool setData(QModelIndex index, QVariant value, role = Qt.EditRole) override() Qt.ItemFlags flags(QModelIndex index) override Q_INVOKABLE void nextStep() Q_INVOKABLE bool loadFile(QString fileName) Q_INVOKABLE void loadPattern(QString plainText) Q_INVOKABLE void clear() # private staticexpr int width = 256 staticexpr int height = 256 staticexpr int size = width * height StateContainer = std::array<bool, size> m_currentState = StateContainer() cellNeighborsCount = int(QPoint cellCoordinates) areCellCoordinatesValid = bool(QPoint coordinates) cellCoordinatesFromIndex = QPoint(int cellIndex) std.size_t cellIndex(QPoint coordinates)
该 GameOfLifeModel
类扩展了 QAbstractTableModel,因此它可以作为我们的 TableView 组件的模型使用。因此,它需要实现一些函数,使 TableView 组件能够与模型交互。如您在类的 private
部分所见,模型使用一个固定大小的数组来存储所有单元格的当前状态。我们还将 QML_ELEMENT 宏用于将类公开给 QML。
def rowCount(self, QModelIndex parent): if parent.isValid(): return 0 return height def columnCount(self, QModelIndex parent): if parent.isValid(): return 0 return width
在这里,实现了 rowCount
和 columnCount
方法,这样 TableView 组件就可以知道表格的大小。它只是返回 width
和 height
常量的值。
def data(self, QModelIndex index, int role): if not index.isValid() or role not = CellRole: return QVariant() return QVariant(m_currentState[cellIndex({index.column(), index.row()})])
该方法在TableView组件从模型请求一些数据时被调用。在我们的示例中,每个单元格只有一条数据:是否存活。这个信息由我们在C++代码中Role枚举的CellRole值来表示;这对应于QML代码中的value属性(这两者之间的链接是由我们的C++类中的roleNames()函数创建的)。
GameOfLifeModel类可以通过index参数识别请求数据的单元格是哪一个,index是一个QModelIndex类型,它包含行和列。
数据更新
def setData(self, QModelIndex index, QVariant value, int role): if role != CellRole or data(index, role) == value: return False m_currentState[cellIndex({index.column(), index.row()})] = value.toBool() dataChanged.emit(index, index, {role}) return True
当通过QML界面设置属性值时,会调用setData方法:在我们的示例中,当点击单元格时,它会切换单元格的状态。与data()函数一样,这个方法接收一个index和一个role参数。此外,新值作为QVariant传递,我们使用toBool函数将其转换为布尔值。
当我们更新模型对象的内部状态时,我们需要发出dataChanged信号来告知TableView组件需要更新显示的数据。在这种情况下,只有被点击的单元格受到影响,因此需要更新的表格范围从单元格的索引开始和结束。
def nextStep(self): newValues = StateContainer() for i in range(0, size): currentState = m_currentState[i] cellNeighborsCount = self.cellNeighborsCount(cellCoordinatesFromIndex(int(i))) newValues[i] = currentState == True ? cellNeighborsCount == 2 or cellNeighborsCount == 3 m_currentState = std.move(newValues) dataChanged.emit(index(0, 0), index(height - 1, width - 1), {CellRole})
由于它在定义中含有Q_INVOKABLE宏,该函数可以直接从QML代码中调用。它执行游戏的迭代,无论是用户点击Next按钮还是计时器发出triggered()信号。
根据康威生命游戏规则,根据每个单元格的邻居当前状态计算其新状态。当整个网格的新状态计算完成后,它将替换当前状态,并且为整个表格发出一个dataChanged信号。
def loadFile(self, QString fileName): file = QFile(fileName) if not file.open(QIODevice.ReadOnly): return False in = QTextStream(file) loadPattern(in.readAll()) return True def loadPattern(self, plainText): clear() rows = plainText.split("\n") patternSize = QSize(0, rows.count()) for row in rows: if row.size() > patternSize.width(): patternSize.setWidth(row.size()) patternLocation = QPoint((width - patternSize.width()) / 2, (height - patternSize.height()) / 2) for y in range(0, patternSize.height()): line = rows[y] for x in range(0, line.length()): cellPosition = QPoint(x + patternLocation.x(), y + patternLocation.y()) m_currentState[cellIndex(cellPosition)] = line[x] == 'O' dataChanged.emit(index(0, 0), index(height - 1, width - 1), {CellRole})
当应用程序打开时,加载一个模式以演示Life游戏如何工作。这两个函数加载存储模式的文件并解析它。与nextStep函数一样,一旦模式完全加载,就会发出一个dataChanged信号为整个表格。