Qt Quick TableView Beispiele - Conway's Game of Life

Das Conway's Game of Life Beispiel zeigt, wie der QML TableView Typ verwendet werden kann, um ein C++ Modell anzuzeigen, das der Benutzer verschieben kann.

Ausführen des Beispiels

Zum Ausführen des Beispiels von Qt Creatorauszuführen, öffnen Sie den Modus Welcome und wählen Sie das Beispiel aus Examples aus. Weitere Informationen finden Sie unter Erstellen und Ausführen eines Beispiels.

Die QML-Benutzeroberfläche

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

Das Beispiel verwendet die Komponente TableView, um ein Gitter aus Zellen anzuzeigen. Jede dieser Zellen wird durch den Delegierten von TableViewauf dem Bildschirm gezeichnet, bei dem es sich um eine QML-Komponente Rectangle handelt. Wir lesen den Wert der Zelle und ändern ihn mit model.value, wenn der Benutzer auf die Zelle klickt.

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

Wenn die Anwendung startet, wird TableView in die Mitte gescrollt, indem die Eigenschaften contentX und contentY verwendet werden, um die Scroll-Position zu aktualisieren, und contentWidth und contentHeight, um zu berechnen, wohin die Ansicht gescrollt werden soll.

model: GameOfLifeModel {
    id: gameOfLifeModel
}

Das C++-Modell

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

Die Klasse GameOfLifeModel erweitert QAbstractTableModel, so dass sie als Modell für unsere Komponente TableView verwendet werden kann. Daher muss sie einige Funktionen implementieren, damit die Komponente TableView mit dem Modell interagieren kann. Wie Sie im private Teil der Klasse sehen können, verwendet das Modell ein Array fester Größe, um den aktuellen Zustand aller Zellen zu speichern. Wir verwenden auch das QML_ELEMENT Makro, um die Klasse für QML zu öffnen.

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

Hier werden die Methoden rowCount und columnCount implementiert, so dass die Komponente TableView die Größe der Tabelle kennen kann. Sie gibt einfach die Werte der Konstanten width und height zurück.

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

Diese Methode wird aufgerufen, wenn die Komponente TableView Daten aus dem Modell anfordert. In unserem Beispiel haben wir nur eine einzige Information pro Zelle: ob sie lebendig ist oder nicht. Diese Information wird durch den CellRole Wert des Roles enum in unserem C++ Code dargestellt; dies entspricht der value Eigenschaft im QML Code (die Verbindung zwischen diesen beiden wird durch die roleNames() Funktion unserer C++ Klasse hergestellt).

Die Klasse GameOfLifeModel kann die Zelle, aus der die Daten angefordert wurden, mit dem Parameter index identifizieren, der ein QModelIndex ist, das eine Zeile und eine Spalte enthält.

Aktualisieren der Daten

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

Die Methode setData wird aufgerufen, wenn der Wert einer Eigenschaft über die QML-Schnittstelle gesetzt wird: In unserem Beispiel wird der Zustand einer Zelle umgeschaltet, wenn sie angeklickt wird. Genauso wie die Funktion data() erhält diese Methode einen index und einen role Parameter. Zusätzlich wird der neue Wert als QVariant übergeben, den wir mit der Funktion toBool in einen booleschen Wert umwandeln.

Wenn wir den internen Zustand unseres Modellobjekts aktualisieren, müssen wir ein dataChanged Signal ausgeben, um der Komponente TableView mitzuteilen, dass sie die angezeigten Daten aktualisieren muss. In diesem Fall ist nur die Zelle betroffen, auf die geklickt wurde, d. h. der zu aktualisierende Bereich der Tabelle beginnt und endet mit dem Index der Zelle.

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

Diese Funktion kann direkt aus dem QML-Code aufgerufen werden, da sie das Makro Q_INVOKABLE in ihrer Definition enthält. Sie spielt eine Iteration des Spiels ab, entweder wenn der Benutzer auf die Schaltfläche Weiter klickt oder wenn der Timer ein Signal triggered() ausgibt.

Nach den Regeln von Conway's Game of Life wird für jede Zelle ein neuer Zustand berechnet, der vom aktuellen Zustand der Nachbarzellen abhängt. Wenn der neue Zustand für das gesamte Gitter berechnet wurde, ersetzt er den aktuellen Zustand und ein dataChanged-Signal wird für die gesamte Tabelle ausgegeben.

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

Wenn die Anwendung geöffnet wird, wird ein Muster geladen, um zu demonstrieren, wie Conway's Game of Life funktioniert. Diese beiden Funktionen laden die Datei, in der das Muster gespeichert ist, und parsen sie. Wie bei der Funktion nextStep wird ein dataChanged Signal für die gesamte Tabelle ausgegeben, sobald das Muster vollständig geladen wurde.

Beispielprojekt @ code.qt.io

© 2025 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.