Auf dieser Seite

Qt Quick Beispiele für TableView – Conways „Game of Life“

Das Beispiel „Conways Spiel des Lebens“ zeigt, wie der QML-Typ „ TableView “ verwendet werden kann, um ein C++-Modell darzustellen, in dem der Benutzer schwenken kann.

Conways „Game of Life“-Gitter, das Zellmuster anzeigt, mit „Weiter“-Schaltfläche und Wiedergabesteuerung

Ausführen des Beispiels

Sie können das Beispiel wie folgt ausführen:

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 Raster aus Zellen anzuzeigen. Jede dieser Zellen wird vom Delegaten der „ TableView“ – einer QML-Komponente vom Typ „Rectangle“ – auf dem Bildschirm gezeichnet. Wir lesen den Wert der Zelle aus und ändern ihn mithilfe von „ model.value “, wenn der Benutzer darauf klickt.

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

Beim Start der Anwendung wird der „ TableView “ in die Mitte gescrollt, indem die Eigenschaften „ contentX “ und „ contentY “ zur Aktualisierung der Scrollposition sowie „ contentWidth “ und „ contentHeight “ zur Berechnung der Zielposition verwendet werden.

model: GameOfLifeModel {
    id: gameOfLifeModel
}

Das C++-Modell

class GameOfLifeModel : public QAbstractTableModel
{
    Q_OBJECT
    QML_ELEMENT

public:
    enum Roles {
        CellRole
    };
    Q_ENUM(Roles)

    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 “ erbt von „ QAbstractTableModel “, sodass sie als Modell für unsere „ TableView “-Komponente verwendet werden kann. Daher muss sie einige Funktionen implementieren, damit die Komponente „ TableView “ mit dem Modell interagieren kann. Wie Sie im Abschnitt „ private “ der Klasse sehen können, verwendet das Modell ein Array fester Größe, um den aktuellen Zustand aller Zellen zu speichern. Wir verwenden außerdem das Makro „ QML_ELEMENT “, um die Klasse für QML verfügbar zu machen.

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, damit die Komponente „ TableView “ die Größe der Tabelle ermitteln kann. Sie geben 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 „ TableView “-Komponente Daten vom Modell anfordert. In unserem Beispiel gibt es pro Zelle nur einen Datenwert: ob sie lebt oder nicht. Diese Information wird durch den Wert „ CellRole “ der Enumeration „ Roles “ in unserem C++-Code dargestellt; dies entspricht der Eigenschaft „ value “ im QML-Code (die Verknüpfung zwischen diesen beiden erfolgt über die Funktion „ roleNames() “ unserer C++-Klasse).

Die Klasse „ GameOfLifeModel “ kann anhand des Parameters „ index “ – einem „ QModelIndex “, der eine Zeile und eine Spalte enthält – ermitteln, aus welcher Zelle die Daten angefordert wurden.

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-Oberfläche gesetzt wird: In unserem Beispiel schaltet sie den Zustand einer Zelle um, wenn diese angeklickt wird. Genau wie die Funktion ` data() ` erhält diese Methode die Parameter ` index ` und ` role `. Zusätzlich wird der neue Wert als ` QVariant` übergeben, den wir mithilfe der Funktion ` toBool ` in einen booleschen Wert umwandeln.

Wenn wir den internen Zustand unseres Modellobjekts aktualisieren, müssen wir ein Signal „ dataChanged “ auslösen, um der Komponente „ TableView “ mitzuteilen, dass sie die angezeigten Daten aktualisieren muss. In diesem Fall ist nur die Zelle betroffen, auf die geklickt wurde; daher beginnt und endet der zu aktualisierende Bereich der Tabelle am Index dieser 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 führt eine Spielrunde durch, entweder wenn der Benutzer auf die Schaltfläche „Weiter“ klickt oder wenn der Timer ein „ triggered() “-Signal aussendet.

Gemäß den Regeln von Conways „Game of Life“ wird für jede Zelle ein neuer Zustand berechnet, der vom aktuellen Zustand ihrer Nachbarn abhängt. Wenn der neue Zustand für das gesamte Raster berechnet wurde, ersetzt er den aktuellen Zustand und ein „dataChanged“-Signal wird für die gesamte Tabelle ausgesendet.

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

Beim Öffnen der Anwendung wird ein Muster geladen, um zu veranschaulichen, wie „Conways Spiel des Lebens“ funktioniert. Diese beiden Funktionen laden die Datei, in der das Muster gespeichert ist, und analysieren sie. Wie in der Funktion „ nextStep “ wird ein „ dataChanged “-Signal für die gesamte Tabelle ausgesendet, sobald das Muster vollständig geladen ist.

Beispielprojekt @ code.qt.io

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