Edit cells and lock them in QML TableView

57 views Asked by At

I have the following TableView and was passed to QML via a C++ QAbstractTableModel and I want to make the cells editable pressing the enter key and after that the cells cannot be edited anymore. How can I achieve that? I have tried but nothing seem to work if I press enter.

Grid.cpp

#include "Grid.h"

Grid::Grid(QObject *parent) : QAbstractTableModel(parent)
{
    int initialValues[9][9] = {
        {7, 0, 2, 0, 5, 0, 6, 0, 0},
        {0, 0, 0, 0, 0, 3, 0, 0, 0},
        {1, 0, 0, 0, 0, 9, 5, 0, 0},
        {8, 0, 0, 0, 0, 0, 0, 9, 0},
        {0, 4, 3, 0, 0, 0, 7, 5, 0},
        {0, 9, 0, 0, 0, 0, 0, 0, 8},
        {0, 0, 9, 7, 0, 0, 0, 0, 5},
        {0, 0, 0, 2, 0, 0, 0, 0, 0},
        {0, 0, 7, 0, 4, 0, 2, 0, 3}
    };

    for (int row = 0; row < 9; ++row) {
        for (int col = 0; col < 9; ++col) {
            gridData[row][col] = initialValues[row][col];
        }
    }
}

int Grid::rowCount(const QModelIndex &) const
{
    return 9;
}

int Grid::columnCount(const QModelIndex &) const
{
    return 9;
}

QVariant Grid::data(const QModelIndex &index, int role) const
{
    int row = index.row();
    int col = index.column();

    switch (role) {
    case Qt::DisplayRole:
        return gridData[row][col];
    }

    return QVariant();
}

bool Grid::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if (role == Qt::EditRole) {
        if (!checkIndex(index))
            return false;

        gridData[index.row()][index.column()] = value.toInt();
        return true;

    }
    return false;
}

Qt::ItemFlags Grid::flags(const QModelIndex &index) const
{
    return Qt::ItemIsEditable | QAbstractTableModel::flags(index);
}

QML file

TableView {
    id: tableView
    anchors.fill: parent
    clip: true

    model: SudokuGrid {}

    delegate: Rectangle {
        implicitWidth: 50
        implicitHeight: 50
        
        border {
            color: "white"
            width: 1
        }

        Rectangle {
            width: 1
            height: parent.height
            color: model.column % 3 == 0 ? "black" : "transparent"
        }

        Rectangle {
           width: parent.width
           height: 1
           color: model.row % 3 == 0 ? "black" : "transparent"
        }

        color: model.row % 2 ? "lightpink" : "lightblue"

        Text {
            anchors.centerIn: parent
            text: display
            font.pointSize: 12
        }

        TableView.editDelegate: TextField {
            anchors.fill: parent
            text: display
            horizontalAlignment: TextInput.AlignHCenter
            verticalAlignment: TextInput.AlignVCenter

            Component.onCompleted: selectAll()

            onAccepted: {
                tableView.commit()
            }

            TableView.onCommit: {
                display = text
            }
        }
    }
}

I tried to follow the documentation for TableView and add the same data function and the flags, but nothing seem to work

1

There are 1 answers

0
Stephen Quan On

Visually, your Sudoku game is 2D, hence, why you think to use TableView and a 2D 9x9 array backend, however, with a simpler 1D model (with 81 values), you can solve it using a regular Repeater because the x,y coordinates of each Sudoku game cell can be present in your model (or derivable from your index). Your delegate can use those coordinates to place your delegate in a 2D arrangement.

The following snippet demonstrates how you may do this in QML:

import QtQuick
import QtQuick.Controls
Page {
    title: "Sudoku"
    ListModel {
        id: sudokuGrid
        Component.onCompleted: {
            for (let v of [
               7, 0, 2, 0, 5, 0, 6, 0, 0,
               0, 0, 0, 0, 0, 3, 0, 0, 0,
               1, 0, 0, 0, 0, 9, 5, 0, 0,
               8, 0, 0, 0, 0, 0, 0, 9, 0,
               0, 4, 3, 0, 0, 0, 7, 5, 0,
               0, 9, 0, 0, 0, 0, 0, 0, 8,
               0, 0, 9, 7, 0, 0, 0, 0, 5,
               0, 0, 0, 2, 0, 0, 0, 0, 0,
               0, 0, 7, 0, 4, 0, 2, 0, 3])
                 append( {v} );
        }
    }
    Repeater {
        model: sudokuGrid
        delegate: Frame {
            property int gx: index % 9
            property int gy: Math.floor(index / 9)
            x: gx * 40
            y: gy * 40
            width: 40
            height: 40
            TextInput {
                text: v
                onAccepted: v = parseInt(text)
            }
        }
    }
}

You can Try it Online!