1
votes

I have a QTableView that shows some data from my custom subclass of QAbstractTableModel. The problem is that the text is pushed up against the left side of the cell. I found this question: How to set the padding of QTableView cells through CSS? It led me to try this:

self.tableView.setStyleSheet('QTableView::item {border: 0px; padding: 5px;} ')

That indented the text but introduced a new problem. Now when I click on a cell, the dotted focus rectangle is inset into the cell.

How can I have both cell spacing and a dotted focus rectangle that surrounds the entire cell (not inset)?

2
When using this stylesheet and clicking on different cells I get even more crazy behavior, the text in selected cells vanishes. So maybe it's just impossible with CSS. - Trilarion

2 Answers

2
votes

Using CSS is not the best way to do this, take 5 minutes of your time and write delegate, it's easy. But you can try:

QTableView {
   outline: 0; /* Disable focus rect*/
}  
QTableView:item {
  border: 0px;
  padding: 0px 10px;
}
QTableView::item:focus { /*Emulate focus*/
    color: black;
    background-color: yellow;
    border: 1px dashed black;
}
1
votes

Here is what I was finally able to come up with. It puts the focus rectangle on the border of each cell, regardless of "padding" or "margin". It also preserves the stylesheet. At least it preserves background color and padding. I didn't test all stylesheet options. However, it does not preserve the text color in the cell with focus. (P.S. This is working with PySide 1.1.1)

class CellDelegate(QtGui.QStyledItemDelegate):
    def __init__(self, parent):
        super(CellDelegate, self).__init__(parent)
        self._parent = parent

    def paint(self, qPainter, option, qModelIndex):
        v4Option = QtGui.QStyleOptionViewItemV4(option)
        v4Option.index = qModelIndex
        value = qModelIndex.data()
        v4Option.text = str(value)

        style = self._parent.style()

        if (v4Option.state & QtGui.QStyle.State_HasFocus):
            # --- The table cell with focus
            # Draw the background
            style.drawPrimitive(style.PE_PanelItemViewItem, v4Option, qPainter, self._parent)

            # Draw the text
            subRect = style.subElementRect(style.SE_ItemViewItemText, v4Option, self._parent)
            alignment = qModelIndex.data(QtCore.Qt.TextAlignmentRole)
            if not alignment:
                alignment = int(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
            if (v4Option.state & QtGui.QStyle.State_Enabled):
                itemEnabled = True
            else:
                itemEnabled = False
            textRect = style.itemTextRect(v4Option.fontMetrics, subRect, alignment, itemEnabled, value)
            style.drawItemText(qPainter, textRect, alignment, v4Option.palette, v4Option.state, value)

            # Draw the focus rectangle
            focusOption = QtGui.QStyleOptionFocusRect()
            focusOption.rect = v4Option.rect
            style.drawPrimitive(style.PE_FrameFocusRect, focusOption, qPainter, self._parent)
        else:
            # --- All other table cells
            style.drawControl(style.CE_ItemViewItem, v4Option, qPainter, self._parent)

Here is some example code showing how to use it. The goal is that the stylesheet would be set in a .ui file. This is just a self-contained example:

class TestTableModel(QtCore.QAbstractTableModel):
    headerNames = ('Column 1', 'Column 2')
    def __init__(self):
        super(TestTableModel, self).__init__()
        self._data = [['test', 'text'], ['yyy', 'zzz']]

    #----- Overridden Functions ------------------------------------------------
    def columnCount(self, parentIndex):
        return len(self.headerNames)

    def data(self, qModelIndex, role=QtCore.Qt.DisplayRole):
        if qModelIndex.isValid():
            if role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole):
                dataItem = self._data[qModelIndex.row()][qModelIndex.column()]
                return dataItem
        return None

    def headerData(self, colNum, orientation, role):
        if (orientation == QtCore.Qt.Horizontal) and (role == QtCore.Qt.DisplayRole):
            return self.headerNames[colNum]
        return None

    def rowCount(self, parentIndex=QtCore.QModelIndex()):
        return len(self._data)

#------------------------------------------------------------------------------
class TestTableViewSpacing(QtGui.QMainWindow):
    def __init__(self, parent=None):
        super(TestTableViewSpacing, self).__init__(parent)

        self.tableView = QtGui.QTableView()
        self.setCentralWidget(self.tableView)

        tableModel = TestTableModel()
        self.tableView.setModel(tableModel)

        self.tableView.setStyleSheet('QTableView::item {border: 0px; padding: 5px; margin: 5px; color: yellow; '
                                     'background-color: QLinearGradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #330055, stop: 1 #000000);} '
                                     'QTableView::item:focus {border: 0px; background-color: darkred; color: yellow;}')

        # VERY IMPORTANT!! Must pass the table view to the delegate, or it will not work!
        newDelegate = CellDelegate(self.tableView)
        self.tableView.setItemDelegate(newDelegate)