Search code examples
python-3.9pyside6qt6

Qt6: how to disable selection for empty cells in QTableView?


I'm trying to display some data from a database in a grid view, similar to how a file manager works. I thought of using a QTableView as the grid view since it did what I wanted out of the box. However, as shown with the below given MRE, even if just a single cell has value, you can still select the other empty cells, how can I prevent this? Basically, I want to make it so that only cells with a value can be selected.

MRE:

from PySide6 import QtWidgets as qtw
from PySide6 import QtGui as qtg
from PySide6 import QtCore as qtc


ROW_COUNT = 5
COL_COUNT = 5

class Model(qtc.QAbstractTableModel):
    def __init__(self, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)
        self._data = [[None for _ in range(COL_COUNT)] for _ in range(ROW_COUNT)]

    def data(self, index: qtc.QModelIndex, role: qtc.Qt.ItemDataRole):
        if not index.isValid():
            return None
        
        if role == qtc.Qt.ItemDataRole.DisplayRole:
            return self._data[index.row()][index.column()]

        return None

    def setData(self, index: qtc.QModelIndex, value, role: qtc.Qt.ItemDataRole=qtc.Qt.ItemDataRole.DisplayRole):
        if  not index.isValid():
            return False

        if role == qtc.Qt.ItemDataRole.DisplayRole:
            self._data[index.row()][index.column()] = value

        return False

    def rowCount(self, _):
        return ROW_COUNT
    
    def columnCount(self, _):
        return COL_COUNT

app = qtw.QApplication()
view = qtw.QTableView()
view.setModel(Model())
view.setShowGrid(False)
view.verticalHeader().setVisible(False)
view.horizontalHeader().setVisible(False)
view.model().setData(view.model().createIndex(0, 0), "this is a test")
view.show()
app.exec()

enter image description here


Solution

  • You need to override the flags() and ensure that it doesn't return the ItemIsSelectable flag.

    class Model(qtc.QAbstractTableModel):
        # ...
        def flags(self, index):
            flags = super().flags(index)
            if index.data() is None:
                flags &= ~qtc.Qt.ItemIsSelectable
            return flags
    

    In your case, you also probably want to avoid the ItemIsEnabled, and since these two flags are the default one, you can just return NoItemFlags

        def flags(self, index):
            if index.data() is None:
                return qtc.Qt.NoItemFlags
            return super().flags(index)
    

    If you also need to clear the selection, then you could subclass the view and do it in the mousePressEvent():

    class TableView(qtw.QTableView):
        def mousePressEvent(self, event):
            index = self.indexAt(event.pos())
            if index.isValid() and not index.flags() & qtc.Qt.ItemIsSelectable:
                self.clearSelection()
            else:
                super().mousePressEvent(event)