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_())

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

вызывающе неверная информация. разузнай про делегаты.

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

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

baobab ()

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

ox55ff ★★★★★ ()

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

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

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

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

У меня QTextDocument. На QTableWidget, кстати, скорость более-менее приличная (но он не поддерживает html). На QTableView гляну, спасибо всем отписавшимся.

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

Ещё как посылают. Как и на все не кастомные интерфейсы.

peregrine ★★★★★ ()
Ответ на: комментарий от 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 )
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.