LINUX.ORG.RU

Ускорить выделение ячейки таблицы мышью

 ,


0

1

Всем привет!

Изучаю pyqt, перевожу на него свой pet-проект. Нужно создать таблицу и выделять ту ячейку, внутри которой находится указатель мыши.

QTableWidget не подходит, поскольку не поддерживает разное форматирование в одной и той же ячейке. Реализовал через QTextDocument.

Проблема в том, что, когда таблица достигает нескольких сот ячеек, выделение занимает несколько секунд. Как такое реализовать на pyqt5/pyqt6?

Это основная проблема, но есть и другие, которые не смог пока решить:

1) Есть вариант не закрашивать ячейку целиком, а просто менять цвет ее границ. Делается ли такое только для одной ячейки?

2) Как ограничить максимальную высоту ячейки? Есть метод setColumnWidthConstraints, а вот метода setColumnHeightConstraints нет.

Вот примерный код:

#!/usr/bin/python3
# -*- coding: UTF-8 -*-

import sys
import PyQt5
import PyQt5.QtWidgets


class App(PyQt5.QtWidgets.QWidget):
    
    def __init__(self,*args,**kwargs):
        super().__init__(*args,**kwargs)
        self.set_gui()
    
    def set_gui(self):
        self.table = Table()
        self.layout = PyQt5.QtWidgets.QVBoxLayout()
        self.layout.addWidget(self.table)
        self.setLayout(self.layout)
    
    def show(self):
        self.showMaximized()



class Table(PyQt5.QtWidgets.QTextEdit):

    def __init__(self):
        super().__init__()
        self.rowno = 0
        self.colno = 0
        self.set_gui()

    def set_gui(self):
        self.doc = PyQt5.QtGui.QTextDocument()
        self.cursor = PyQt5.QtGui.QTextCursor(self.textCursor())
        self.fmt = PyQt5.QtGui.QTextTableFormat()
        self.table = self.cursor.insertTable(10,10,self.fmt)
        self.fill()
    
    def fill(self):
        for rowno in range(10):
            for colno in range(10):
                cell = self.table.cellAt(rowno,colno)
                self.cursor = cell.lastCursorPosition()
                self.setTextCursor(self.cursor)
                code = '<b>Row</b>: {}. <b>Column</b>: {}'.format(rowno,colno)
                self.cursor.insertHtml(code)
    
    def eventFilter(self,source,event):
        if event.type() == PyQt5.QtCore.QEvent.MouseMove:
            pos = event.pos()
            cursor = self.cursorForPosition(pos)
            cell = self.table.cellAt(cursor)
            if not cell.isValid() or cell.column() == self.colno and cell.row() == self.rowno:
                return super().eventFilter(source,event)
            self.rowno = cell.row()
            self.colno = cell.column()
            self.set_cell_bg(cell,'cyan')
            cell = self.table.cellAt(self.cursor)
            self.set_cell_bg(cell,'white')
            self.cursor = cursor
        return super().eventFilter(source,event)
    
    def set_cell_bg(self,cell,color):
        cell_fmt = cell.format()
        cell_fmt.setBackground(PyQt5.QtGui.QColor(color))
        cell.setFormat(cell_fmt)


if __name__ == '__main__':
    exe = PyQt5.QtWidgets.QApplication(sys.argv)
    app = App()
    exe.installEventFilter(app.table)
    app.show()
    sys.exit(exe.exec_())


Последнее исправление: baobab (всего исправлений: 1)

Ответ на: комментарий от aol

Я уже давно про них знаю, гуглил и пытался делать. Приведи, пожалуйста, минимальный запускаемый код, где QTableWidget поддерживает разное форматирование для одной ячейки.

baobab
() автор топика

Тормозит потому что не предназначено для такого использования. Нужно делать кастомный делегат в QTableView. Qtableview оптимизирован, чтобы не тормозить на большом количестве элементов.

ox55ff ★★★★★
()

Когда-то этот pyqt, лет 15 назад, на него смотрели как на детские забавы, мол че можно на нем сделОть, если у тебя 512 мб оперативы, а мб 60 только один питон сразу сжирает… если это не лаба, то доведи проект до конца и выложи на гитхаб чтобы все эти статические петухи, любители обмазаться сишкой и структурами данных еще побугуртили от того, что не удел остались

qanon
()
Ответ на: комментарий от qanon

а если по делу, то поковыряй QML на нем интерфейсы выглядят в разы лучше чем закос под натив .потому что там хтмл дрисня какая-то в вебвиеве отоборажается, но хейтеры электронов этого не знают, потому на кеды лучи поноса не посылают.

qanon
()
Ответ на: комментарий от ox55ff

OK. Переделал в QTableView, но код все равно тормозит :(

#!/usr/bin/python3
# -*- coding: UTF-8 -*-

import PyQt5
import PyQt5.QtWidgets


class MyTableModel(PyQt5.QtCore.QAbstractTableModel):
    
    def __init__(self,datain,parent=None,*args):
        PyQt5.QtCore.QAbstractTableModel.__init__(self,parent,*args)
        self.arraydata = datain

    def rowCount(self,parent):
        return len(self.arraydata)

    def columnCount(self,parent):
        return len(self.arraydata[0])
    
    def data(self,index,role):
        if not index.isValid():
            return PyQt5.QtCore.QVariant()
        if role == PyQt5.QtCore.Qt.DisplayRole:
            return PyQt5.QtCore.QVariant(self.arraydata[index.row()][index.column()])
    
    def update(self):
        self.layoutChanged.emit()



class CustomDelegate(PyQt5.QtWidgets.QStyledItemDelegate):
    
    def __init__(self,*args,**kwargs):
        super().__init__(*args,**kwargs)
        self.rowno = 0
        self.colno = 0
        color = PyQt5.QtGui.QColor('red')
        self.pen = PyQt5.QtGui.QPen(color,2)
    
    def paint(self,painter,option,index):
        options = PyQt5.QtWidgets.QStyleOptionViewItem(option)
        self.initStyleOption(options,index)
    
        if options.widget:
            style = options.widget.style()
        else:
            style = PyQt5.QtWidgets.QApplication.style()
    
        style.drawControl(PyQt5.QtWidgets.QStyle.CE_ItemViewItem,options,painter)
    
        if index.row() == self.rowno and index.column() == self.colno:
            painter.setPen(self.pen)
            painter.drawRect(option.rect)



class Table(PyQt5.QtWidgets.QTableView):
    
    def __init__(self,*args,**kwargs):
        super().__init__(*args,**kwargs)
        self.set_gui()
    
    def eventFilter(self,widget,event):
        # Qt accepts boolean at output, but not NoneType
        if event.type() != PyQt5.QtCore.QEvent.MouseMove:
            return False
        pos = event.pos()
        colno = self.columnAt(pos.x())
        rowno = self.rowAt(pos.y())
        if rowno == self.delegate.rowno and colno == self.delegate.colno:
            return False
        self.delegate.rowno = rowno
        self.delegate.colno = colno
        #NOTE: The model should be assigned from the controller
        self.model.update()
        return True
    
    def set_gui(self):
        self.delegate = CustomDelegate()
        self.setItemDelegate(self.delegate)



class App(PyQt5.QtWidgets.QMainWindow):
    
    def __init__(self,*args,**kwargs):
        super().__init__(*args,**kwargs)
    
    def show(self):
        self.showMaximized()
    
    def create_layout(self):
        self.parent = PyQt5.QtWidgets.QWidget()
        self.layout = PyQt5.QtWidgets.QVBoxLayout()
    
    def set_layout(self):
        self.layout.addWidget(self.table)
        self.parent.setLayout(self.layout)
    
    def set_gui(self):
        self.create_layout()
        self.table = Table()
        self.set_layout()
        self.setCentralWidget(self.parent)



if __name__ == '__main__':
    import sys
    exe = PyQt5.QtWidgets.QApplication(sys.argv)
    app = App()
    app.set_gui()
    
    data = []
    for i in range(20):
        row = []
        for j in range(20):
            mes = 'Row: {}. Column: {}'.format(i,j)
            row.append(mes)
        data.append(row)
    
    tablemodel = MyTableModel(data)
    app.table.setModel(tablemodel)
    app.table.model = tablemodel
    exe.installEventFilter(app.table)
    app.show()
    sys.exit(exe.exec_())

baobab
() автор топика
Последнее исправление: baobab (всего исправлений: 2)
Ответ на: комментарий от baobab

self.model.update()

self.layoutChanged.emit()

Ты этим триггеришь полное обновление таблицы. Как часто у тебя это вызывается? При любом движении мыши? Не удивительно, что тормозит. Модель должна обновляться только при изменении данных. При чём там есть сигналы обновления конкретной строки, а не всей модели.

Ты должен посылать сигнал только если изменилось содержимое self.arraydata из класса MyTableModel.

ox55ff ★★★★★
()
Ответ на: комментарий от ox55ff

Спасибо! Если обновлять только пару ячеек таблицы (предыдущую и текущую), то меняет выделение моментально.

def update(self,rowno,colno):
    index_ = self.index(rowno,colno)
    self.dataChanged.emit(index_,index_)

baobab
() автор топика
Ответ на: комментарий от baobab

Разобрался сам (правда, для QTableView). Если кому-нибудь понадобится:

import sys
import PyQt5
import PyQt5.QtWidgets


class CustomDelegate(PyQt5.QtWidgets.QStyledItemDelegate):
    # akej74, https://stackoverflow.com/questions/35397943/how-to-make-a-fast-qtableview-with-html-formatted-and-clickable-cells
    def paint(self,painter,option,index):
        options = PyQt5.QtWidgets.QStyleOptionViewItem(option)
        self.initStyleOption(options,index)
    
        if options.widget:
            style = options.widget.style()
        else:
            style = PyQt5.QtWidgets.QApplication.style()
    
        doc = PyQt5.QtGui.QTextDocument()
        doc.setHtml(options.text)
        options.text = ''
    
        style.drawControl(PyQt5.QtWidgets.QStyle.CE_ItemViewItem,options,painter)
        ctx = PyQt5.QtGui.QAbstractTextDocumentLayout.PaintContext()
    
        textRect = style.subElementRect(PyQt5.QtWidgets.QStyle.SE_ItemViewItemText,options)
    
        painter.save()
    
        painter.translate(textRect.topLeft())
        painter.setClipRect(textRect.translated(-textRect.topLeft()))
        painter.translate(0,0.5*(options.rect.height() - doc.size().height()))
        doc.documentLayout().draw(painter,ctx)
    
        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):
        ''' The following takes the first sub-list, and returns the length
            (only works if all rows are an equal length).
        '''
        return len(self._data[0])



class MainWindow(PyQt5.QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()

        self.table = PyQt5.QtWidgets.QTableView()

        lst = [['<b>4</b>', '<b>9</b> <i>no</i> <u>excuses</u>, fairy tale creatures<b>!</b>', '2']
               ,['1', '0', '0']
               ,['3', '5', '0']
               ,['3', '3', '2']
               ,['7', '8', '9']
               ,
               ]
        
        model = TableModel(lst)
        self.table.setModel(model)
        self.table.setItemDelegate(CustomDelegate())

        self.setCentralWidget(self.table)


if __name__ == '__main__':
    app = PyQt5.QtWidgets.QApplication(sys.argv)
    window = MainWindow()
    window.show()
    app.exec_()

baobab
() автор топика
Последнее исправление: baobab (всего исправлений: 1)