QTableView.verticalHeader().setOffset() doesn't work

Maurizio Berti maurizio.berti at gmail.com
Sun Mar 20 22:29:53 GMT 2022


Il giorno dom 20 mar 2022 alle ore 20:23 Matej Brezović <
matey.brezovic at gmail.com> ha scritto:

> Yes, seems like you're right. If I call setOffset() after calling show()
> on my window, the offset is visible but disappears as soon as I try to
> resize the window.
> Calling it after resizeEvent or paintEvent doesn't seems to work either,
> and if it did it would probably be inefficient.
>

Calling it in the resizeEvent is not inefficient, and it's what many
widgets actually do (but you *must* remember to call the base
implementation first).


> I don't really know why does that function even exist if it doesn't seems
> to have an effect if the view is resizable.
>

I believe you misinterpreted its purpose: that function is required by
views, which set the offsets whenever their relative scroll bars change
values.
Headers are not part of the items, nor the scrollarea viewport: they are
direct child widgets of the view, their position is always fixed and based
on the top left (or right, for RTL languages) corner, translated by the
width or height of the opposite header; setting the offset allows the
header to "scroll" its contents.


> And yes, my intention was to simply add some padding, just a few pixels,
> on the top of the first row, so that the table view would be slightly
> separated from the header view, but it seems I will have to give up on that
> idea.
>

I did some testing, but, as I was afraid, it's almost impossible or, at
least, extremely difficult.

Even overriding the updateGeometries and calling setViewportMargins() won't
be enough, as it would create further issues:
- the viewport margins are used to compute the span of scroll bars, and
their value is used to know how much the viewport is scrolled (so the last
row or column could become partially or even completely invisible);
- painting is always restricted to the widget rectangle, and adding a
margin would only *translate* the origin point of the viewport: while the
view would show a proper margin above the first row, scrolling would result
in keeping that margin (so you will *always* have a blank margin on top);

The only way I could think of would be to completely override *lots* of
functions, with the most difficult ones being those related to the scroll
bars.

A *dirty* hack could be to use a model with a "ghost" row and do something
similar to the frozen column example (
https://doc.qt.io/qt-5/qtwidgets-itemviews-frozencolumn-example.html ):
create a basic QWidget that is a child of the table view, ensure that it
uses the same palette Base role as backgroundRole and that
autoFillBackground is set, then setSectionResizeMode(0, QHeaderView.Fixed)
anytime the view calls updateGeometries. Obviously, this has a lot of
drawbacks, especially if the model is expected to support sorting.

Here is a basic example of the above:

class Model(QtCore.QAbstractTableModel):
    def __init__(self, parent: Optional[QWidget] = None) -> None:
        super(Model, self).__init__(parent)
        self.__data = []
        for i in range(10):
            row = [i, 1, 2, 3, 4, 5, 6, 7]
            self.__data.append(row)

    def flags(self, index):
        if index.row() == 0:
            return QtCore.Qt.NoItemFlags
        return super().flags(index)

    def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
        if orientation == QtCore.Qt.Vertical:
            section -= 1
            if section < 0 and role == QtCore.Qt.DisplayRole:
                return
        return super().headerData(section, orientation, role)

    def rowCount(self, index: Optional[QtCore.QModelIndex] = None) -> int:
        return len(self.__data) + 1

    def columnCount(self, index: Optional[QtCore.QModelIndex] = None) ->
int:
        return len(self.__data[0])

    def data(self, index: QtCore.QModelIndex,
             role: QtCore.Qt.ItemDataRole = Qt.DisplayRole) -> Any:
        if index.row() and role in [Qt.DisplayRole, Qt.EditRole]:
            return self.__data[index.row() - 1][index.column()]
        return None


class MarginTableView(QTableView):
    _margin = 0
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.marginWidget = QWidget(self)
        self.marginWidget.setBackgroundRole(QPalette.Base)
        self.marginWidget.setAutoFillBackground(True)
        self.verticalScrollBar().valueChanged.connect(self.updateMargin)

    @pyqtProperty(int)
    def margin(self):
        return self._margin

    @margin.setter
    def margin(self, margin):
        margin = max(0, margin)
        if self._margin != margin:
            self._margin = margin
            if self.isVisible():
                self.updateGeometries()

    def updateGeometries(self):
        super().updateGeometries()
        self.verticalHeader().setSectionResizeMode(0, QHeaderView.Fixed)
        self.verticalHeader().resizeSection(0, self.margin)
        self.updateMargin()

    def updateMargin(self):
        self.marginWidget.setGeometry(
            self.horizontalHeader().x(),
            self.verticalHeader().y(),
            self.viewport().width(),
            max(0, self.margin - 1 - self.verticalHeader().offset())
        )

    def resizeEvent(self, event):
        super().resizeEvent(event)
        self.updateMargin()


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.resize(700, 700)

        self.central_widget = QWidget()
        self.central_widget_layout = QHBoxLayout(self.central_widget)

        view = MarginTableView(margin=50)
        model = Model()
        view.setModel(model)

        self.central_widget_layout.addWidget(view)
        self.setCentralWidget(self.central_widget)


It "works", but it's hacky and *ugly*. While I can understand your request,
I'd just give up. As far as I know, solving this problem *easily* is
impossible, and while technically doable, it would require an incredible
amount of efforts that are just not worth something that would "just be
nice to have".

Regards,
Maurizio

-- 
È difficile avere una convinzione precisa quando si parla delle ragioni del
cuore. - "Sostiene Pereira", Antonio Tabucchi
http://www.jidesk.net
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://www.riverbankcomputing.com/pipermail/pyqt/attachments/20220320/9aee475d/attachment-0001.htm>


More information about the PyQt mailing list