이 페이지에서

Qt Quick TableView 예제 - 콘웨이의 생명 게임

'콘웨이의 생명의 게임' 예제는 QML의 ` TableView ` 타입을 사용하여 사용자가 화면을 이동하며 볼 수 있는 C++ 모델을 표시하는 방법을 보여줍니다.

‘넥스트(Next)’ 버튼과 재생 제어 기능이 포함된, 세포 패턴을 보여주는 콘웨이의 ‘생명의 게임’ 격자

예제 실행

다음 경로에서 예제를 실행할 수 있습니다.

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

GameOfLifeModel 클래스는 QAbstractTableModel 을 상속받으므로, TableView 컴포넌트의 모델로 사용할 수 있습니다. 따라서 TableView 컴포넌트가 모델과 상호작용할 수 있도록 몇 가지 함수를 구현해야 합니다. 클래스의 private 부분에서 볼 수 있듯이, 모델은 고정 크기의 배열을 사용하여 모든 셀의 현재 상태를 저장합니다. 또한 QML에서 이 클래스를 사용할 수 있도록 QML_ELEMENT 매크로를 사용합니다.

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

여기서는 TableView 컴포넌트가 테이블의 크기를 알 수 있도록 rowCount 및 columnCount 메서드가 구현되어 있습니다. 이 메서드는 단순히 width 및 height 상수의 값을 반환합니다.

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

이 메서드는 TableView 컴포넌트가 모델로부터 데이터를 요청할 때 호출됩니다. 이 예제에서는 셀당 ‘살아 있는지 여부’라는 단 하나의 데이터만 있습니다. 이 정보는 C++ 코드 내의 Roles 열거형에 있는 CellRole 값으로 표현되며, 이는 QML 코드의 value 속성과 대응됩니다(이 둘 간의 연결은 C++ 클래스의 roleNames() 함수를 통해 이루어집니다).

GameOfLifeModel 클래스는 index 매개변수를 통해 요청된 데이터가 어느 셀에서 온 것인지 식별할 수 있으며, 이 매개변수는 행과 열을 포함하는 QModelIndex 입니다.

데이터 업데이트

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

setData 메서드는 QML 인터페이스에서 속성의 값이 설정될 때 호출됩니다. 이 예제에서는 셀을 클릭했을 때 해당 셀의 상태를 전환합니다. data() 함수와 마찬가지로, 이 메서드도 index 및 role 매개변수를 받습니다. 또한, 새로운 값은 QVariant 로 전달되며, 이를 toBool 함수를 사용하여 부울 값으로 변환합니다.

모델 객체의 내부 상태를 업데이트할 때는, TableView 컴포넌트에 표시된 데이터를 업데이트해야 함을 알리기 위해 dataChanged 신호를 발산해야 합니다. 이 경우 클릭된 셀에만 영향을 미치므로, 업데이트해야 할 테이블의 범위는 해당 셀의 인덱스에서 시작하여 인덱스에서 끝납니다.

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

이 함수는 정의 내에 Q_INVOKABLE 매크로가 포함되어 있으므로 QML 코드에서 직접 호출할 수 있습니다. 이 함수는 사용자가 ‘다음(Next )’ 버튼을 클릭하거나 타이머(Timer)가 triggered() 신호를 발산할 때 게임의 한 판을 진행합니다.

콘웨이의 생명 게임 규칙에 따라, 각 셀의 새로운 상태는 인접 셀들의 현재 상태에 따라 계산됩니다. 전체 그리드에 대한 새로운 상태가 계산되면, 현재 상태를 대체하고 테이블 전체에 대해 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});
}

애플리케이션이 실행되면 콘웨이의 생명 게임이 어떻게 작동하는지 보여주기 위해 패턴이 로드됩니다. 다음 두 함수는 패턴이 저장된 파일을 불러와 파싱합니다. ` nextStep ` 함수와 마찬가지로, 패턴이 완전히 로드되면 전체 테이블에 대해 ` dataChanged ` 신호가 발생합니다.

예제 프로젝트 @ 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.