Python Forum
[PyQt] QTableView: scroll to top cell of the screen
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[PyQt] QTableView: scroll to top cell of the screen
#1
Hi everyone!

I have created a table in Qt using a QTableView widget. Cells are navigated using arrow keys. The current cell to which I have navigated using Down key has a border in red.

I try to do the following. If the current cell is not visible at all or is partially visible, pressing Down should act almost like PgDn - the table should be scrolled down one page, the cell that was not previously fully visible should appear at the very top of the page (currently visible area).

I achieve that by calculating Y position of the current page and scrolling to that position. However, I haven't managed yet to overcome the following problems:
1) I have to precalculate Y positions of cells because if the table scrollbar is not at the beginning, different Y values may be output - I don't know why.
2) With my algorithm, current pages are not calculated precisely.

In a common scenario, rows have the same height, but I may move to rows of a different height in the future.

Here's my code:
import sys
import PyQt5
import PyQt5.QtWidgets


class TableDelegate(PyQt5.QtWidgets.QStyledItemDelegate):
    # akej74, https://stackoverflow.com/questions/35397943/how-to-make-a-fast-qtableview-with-html-formatted-and-clickable-cells
    def __init__(self,*args,**kwargs):
        super().__init__(*args,**kwargs)
        self.rowno = 0
        self.colno = 0
    
    def paint(self,painter,option,index):
        options = PyQt5.QtWidgets.QStyleOptionViewItem(option)
        self.initStyleOption(options,index)
        style = options.widget.style()
        style.drawControl(PyQt5.QtWidgets.QStyle.CE_ItemViewItem,options,painter)
        
        if index.row() == self.rowno and index.column() == self.colno:
            color = PyQt5.QtGui.QColor('red')
            pen = PyQt5.QtGui.QPen(color,2)
            painter.setPen(pen)
            # Avoid intersecting cell borders and artifacts as the result
            x1, y1, x2, y2 = option.rect.getCoords()
            option.rect.setCoords(x1+1,y1+1,x2-1,y2-1)
            painter.drawRect(option.rect)
        
        painter.save()
        painter.restore()



class TableModel(PyQt5.QtCore.QAbstractTableModel):
    
    def __init__(self, data):
        super(TableModel, self).__init__()
        self._data = data

    def data(self,index,role):
        if role == PyQt5.QtCore.Qt.DisplayRole:
            return self._data[index.row()][index.column()]

    def rowCount(self,index):
        return len(self._data)

    def columnCount(self,index):
        return len(self._data[0])
    
    def update(self,rowno,colno):
        index_ = self.index(rowno,colno)
        self.dataChanged.emit(index_,index_)



class Table(PyQt5.QtWidgets.QTableView):
    
    def __init__(self):
        super().__init__()
        self.rownum = 200
        self.colnum = 10
        self.y = []
        self.delegate = TableDelegate()
        self.setItemDelegate(self.delegate)
        self.vscroll = self.verticalScrollBar()
        self.set_row_height()
    
    def set_row_height(self,height=42):
        for no in range(self.rownum):
            self.setRowHeight(no,height)
    
    def select(self,rowno,colno):
        if rowno == self.delegate.rowno and colno == self.delegate.colno:
            return
        self.mymodel.update(self.delegate.rowno,self.delegate.colno)
        self.mymodel.update(rowno,colno)
        self.delegate.rowno = rowno
        self.delegate.colno = colno
    
    def set_all_y(self):
        for rowno in range(self.rownum):
            self.y.append(self.rowViewportPosition(rowno))
        print(self.y)
    
    def get_page_y(self,y):
        height = self.height()
        print()
        print('Table height:',height)
        page_no = int(y/height)
        print('Page #{}'.format(page_no))
        y = page_no * height
        mes = 'Page Y: {}'.format(y)
        print(mes)
        return y
    
    def get_scroll(self,y):
        max_ = self.vscroll.maximum()
        y = self.get_page_y(y)
        scrolly = int((max_*y)/(self.y[-1]))
        mes = 'Scrolling percentage: {}'.format(scrolly)
        print(mes)
        return scrolly
    
    def print_cell(self):
        mes = '"' + self.matrix[self.delegate.rowno][self.delegate.colno] + '"'
        print(mes)
    
    def set_scroll(self,y):
        self.vscroll.setValue(0)
        percent = self.get_scroll(y)
        self.vscroll.setValue(percent)
        self.print_cell()
    
    def show_row(self,rowno):
        y = self.y[rowno]
        self.set_scroll(y)
    
    def go_down(self):
        rowno = self.delegate.rowno
        colno = self.delegate.colno
        rowno += 1
        self.select(rowno,colno)
        self.show_row(rowno)



class App(PyQt5.QtWidgets.QMainWindow):
    
    def __init__(self):
        super().__init__()
        self.table = Table()
        self.setCentralWidget(self.table)
        self.setGeometry(300,200,800,600)
        PyQt5.QtWidgets.QShortcut(PyQt5.QtGui.QKeySequence('Down'),self).activated.connect(self.table.go_down)
    
    def fill(self):
        self.table.matrix = []
        for i in range(self.table.rownum):
            row = []
            for j in range(self.table.colnum):
                mes = 'Row {}. Column {}'.format(i+1,j+1)
                row.append(mes)
            self.table.matrix.append(row)
        self.table.mymodel = TableModel(self.table.matrix)
        self.table.setModel(self.table.mymodel)


if __name__ == '__main__':
    exe = PyQt5.QtWidgets.QApplication(sys.argv)
    app = App()
    app.fill()
    app.table.set_all_y()
    app.show()
    exe.exec_()
Any idea what's wrong with it?
Reply
#2
You can do something like that, but it woks only using Ctrl+Down Key

It scrolls selected row to top

class Table(PyQt5.QtWidgets.QTableView):
     
    def __init__(self):
        super().__init__()
        self.rownum = 200
        self.colnum = 10
        self.y = []
        self.delegate = TableDelegate()
        self.setItemDelegate(self.delegate)
        self.vscroll = self.verticalScrollBar()
        self.set_row_height()   

    def keyPressEvent(self, event):
        if event.key() == PyQt5.QtCore.Qt.Key_Down:
            print("down key")
            index = self.currentIndex()
            self.scrollTo(index, PyQt5.QtWidgets.QAbstractItemView.PositionAtTop)
        super().keyPressEvent(event)

....
random_nick likes this post
Reply
#3
(Oct-07-2022, 09:42 AM)Axel_Erfurt Wrote: You can do something like that
Thanks! Looks like I've got it.
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  [PyQt] PyQt5 QTableView SQLite : edit cell HeinKurz 2 2,284 Mar-27-2023, 10:41 AM
Last Post: HeinKurz
  [PyQt] QStyledItemDelegate and QTableView malonn 1 1,599 Feb-07-2023, 07:15 PM
Last Post: malonn
  [PyQt] QTableView set header labels HeinKurz 2 6,421 Jan-23-2023, 08:46 AM
Last Post: HeinKurz
  [PyQt] Determine whether text in QTableView cell is fully visible or not random_nick 0 956 Oct-27-2022, 09:29 PM
Last Post: random_nick
  [PyQt] [Solved]Add a Blank Row To QTableView Extra 3 5,368 Oct-02-2022, 04:53 PM
Last Post: Extra
  How to update the list of a combo box in a QTableView panoss 10 6,131 Feb-05-2022, 03:24 PM
Last Post: panoss
  [PyQt] How do I get a QScrollArea to scroll? LavaCreeperKing 9 7,599 Oct-29-2021, 08:33 AM
Last Post: Axel_Erfurt
  [PyQt] How to Copy-Paste a table from Office apps to QTableView? Vittorio 5 7,137 Aug-05-2021, 11:14 AM
Last Post: Axel_Erfurt
  Treeview scroll selected node to top rfresh737 1 2,668 Apr-14-2021, 03:27 AM
Last Post: deanhystad
  [Tkinter] canvas widget scroll issue chrisdb 2 3,778 Apr-07-2021, 05:48 AM
Last Post: chrisdb

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020