PyQT QTableWidget extremely slow - python

this is the code I use to fill a table drawn in QT Designer.
Designed to be universal for any table, it works fine, but...
When I try to show a datasat containing 18 columns and ~12000 rows, it just freezes for 30 seconds or more.
So, what I am doing wrong and is there way to speed up, keeping the code still suitable for any table?
That's my code:
...blablabla...
self.connect(self, SIGNAL("set"), self.real_set)
...blablabla...
def set_table(self, table, data):
self.emit(SIGNAL('set'), table, data)
def real_set(self, table, data):
"""
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Assuming data is list of dict and table is a QTableWidget.
Get first key and get len of contents
"""
for key in data:
rows = len(data[key])
table.setRowCount(rows)
break
"""
Forbid resizing(speeds up)
"""
table.horizontalHeader().setResizeMode(QHeaderView.Fixed)
table.verticalHeader().setResizeMode(QHeaderView.Fixed)
table.horizontalHeader().setStretchLastSection(False)
table.verticalHeader().setStretchLastSection(False)
"""
Set number of columns too
"""
table.setColumnCount(len(data))
table.setHorizontalHeaderLabels(sorted(data.keys()))
"""
Now fill data
"""
for n, key in enumerate(sorted(data.keys())):
for m, item in enumerate(data[key]):
newitem = QTableWidgetItem(item)
table.setItem(m, n, newitem)

Here a test script which compares a few ways of populating a table.
The custom model is much faster, because it does not have to create all the items up front - but note that it is a very basic implementation, so does not implement sorting, editing, etc. (See Model/View Programming for more details).
from random import shuffle
from PyQt4 import QtCore, QtGui
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, data, parent=None):
super(TableModel, self).__init__(parent)
self._data = data
def rowCount(self, parent=None):
return len(self._data)
def columnCount(self, parent=None):
return len(self._data[0]) if self.rowCount() else 0
def data(self, index, role=QtCore.Qt.DisplayRole):
if role == QtCore.Qt.DisplayRole:
row = index.row()
if 0 <= row < self.rowCount():
column = index.column()
if 0 <= column < self.columnCount():
return self._data[row][column]
class Window(QtGui.QWidget):
def __init__(self):
super(Window, self).__init__()
self.table = QtGui.QTableView(self)
self.tablewidget = QtGui.QTableWidget(self)
self.tablewidget.setSortingEnabled(True)
self.button1 = QtGui.QPushButton('Custom Model', self)
self.button1.clicked.connect(
lambda: self.populateTable('custom'))
self.button2 = QtGui.QPushButton('StandardItem Model', self)
self.button2.clicked.connect(
lambda: self.populateTable('standard'))
self.button3 = QtGui.QPushButton('TableWidget', self)
self.button3.clicked.connect(
lambda: self.populateTable('widget'))
self.spinbox = QtGui.QSpinBox(self)
self.spinbox.setRange(15000, 1000000)
self.spinbox.setSingleStep(10000)
layout = QtGui.QGridLayout(self)
layout.addWidget(self.table, 0, 0, 1, 4)
layout.addWidget(self.tablewidget, 1, 0, 1, 4)
layout.addWidget(self.button1, 2, 0)
layout.addWidget(self.button2, 2, 1)
layout.addWidget(self.button3, 2, 2)
layout.addWidget(self.spinbox, 2, 3)
self._data = []
def populateTable(self, mode):
if mode == 'widget':
self.tablewidget.clear()
self.tablewidget.setRowCount(self.spinbox.value())
self.tablewidget.setColumnCount(20)
else:
model = self.table.model()
if model is not None:
self.table.setModel(None)
model.deleteLater()
if len(self._data) != self.spinbox.value():
del self._data[:]
rows = list(range(self.spinbox.value()))
shuffle(rows)
for row in rows:
items = []
for column in range(20):
items.append('(%d, %d)' % (row, column))
self._data.append(items)
timer = QtCore.QElapsedTimer()
timer.start()
if mode == 'widget':
self.tablewidget.setSortingEnabled(False)
for row, items in enumerate(self._data):
for column, text in enumerate(items):
item = QtGui.QTableWidgetItem(text)
self.tablewidget.setItem(row, column, item)
self.tablewidget.sortByColumn(0, QtCore.Qt.AscendingOrder)
else:
self.table.setSortingEnabled(False)
if mode == 'custom':
model = TableModel(self._data, self.table)
elif mode == 'standard':
model = QtGui.QStandardItemModel(self.table)
for row in self._data:
items = []
for column in row:
items.append(QtGui.QStandardItem(column))
model.appendRow(items)
self.table.setModel(model)
self.table.setSortingEnabled(True)
self.table.sortByColumn(0, QtCore.Qt.AscendingOrder)
print('%s: %.3g seconds' % (mode, timer.elapsed() / 1000))
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.setGeometry(600, 50, 1200, 800)
window.show()
sys.exit(app.exec_())

In GUI applications one comes across a situation where there is a need
to display a lot of items in a tabular or list format (for example
displaying large number of rows in a table). One way to increase the
GUI responsiveness is to load a few items when the screen is displayed
and defer loading of rest of the items based on user action. Qt
provides a solution to address this requirement of loading the data on
demand.
You can find the implementation of this technique called pagination in this link

Related

Applying QStyledItemDelegate to a cell in QTreeView

I'm developping an application that allows to read and edit json files on pyqt5; the
aplication lets the user make changes to the data, by default it lets the user edit by
hand the fields, however I prefer them to select the information from the a set of options to avoid wrong editions.
To achieve this I am creating an multiple delegates, as an exaple in the code below Delegate_1 and delgate_2 that inherit from QStyledItemDelegate and rewrites the createEditor method. Searching in internet I found three methods from the class QTreeView that can be used to apply the delegates to different situations: setItemDelegateForColumn, setItemDelegateForRow and setItemDelegate, the fist two are works for full columns or rows and the third works for the whole tree, however my intention is to use the delegate_1 for cell with the index (0, 1), and the delegate_2 for the index (1, 1).
is there a method of QTreeView or a way to achieve that ?
import sys
from PySide2.QtWidgets import *
from PySide2.QtGui import *
from PySide2.QtCore import *
class Delegate_1(QStyledItemDelegate):
def createEditor(self, parent, option, index):
combo = QComboBox()
combo.addItem('BINARY')
combo.addItem('ASCII')
return combo
def setModelData(self, editor, model, index):
txt = editor.currentText()
model.setData(index, txt)
class Delegate_2(QStyledItemDelegate):
def createEditor(self, parent, option, index):
combo = QComboBox()
combo.addItem('True')
combo.addItem('False')
return combo
def setModelData(self, editor, model, index):
txt = editor.currentText()
model.setData(index, txt)
if __name__ == '__main__':
app = QApplication(sys.argv)
model = QStandardItemModel(2, 2)
it = QStandardItem('File_mode')
model.setItem(0, 0, it)
it = QStandardItem('ASCII') # apply delegate_1 to the cell
model.setItem(0, 1, it)
it = QStandardItem('Opened')
model.setItem(1, 0, it)
it = QStandardItem('True') # apply delegate_2 to the cell
model.setItem(1, 1, it)
t = QTreeView() # <- it's json data
t.setModel(model)
t.setItemDelegateForColumn(1, Delegate_1()) # <- column 1
#t.setItemDelegate(Delegate()) # <- all cells
t.show()
sys.exit(app.exec_())
Thanks for your help
In the createEditor method, you can call the index.row() and index.column() methods, and depending on the row and column values, create the necessary editor.
def createEditor(self, parent, option, index):
if index.row() == 0 and index.column() == 1:
combo = QComboBox()
combo.addItem('BINARY')
combo.addItem('ASCII')
return combo
elif index.row() == 1 and index.column() == 1:
combo = QComboBox()
combo.addItem('True')
combo.addItem('False')
return combo

How to select rows on QTableWidget and return the value to the QLineEdit

I want to make that when i click on particular cell on the QTableWidget it will block the corresponding rows and I want to return the value of each row selected into the QLineEdit.
I couldnt seem to find the solution my code only return when i click it will block the rows but not getting the value.
def click_inventorytable(self):
self.tableInventory.setSelectionBehavior(QtWidgets.QTableView.SelectRows)
index = (self.tableInventory.selectionModel().currentIndex())
value = index.row()
list = [value]
if(len(list)==6):
self.lineproductnameinv.setText((list[1]))
self.linedescinv.setText((list[2]))
self.combocateinv.setText((list[3]))
self.linepriceinv.setText((list[4]))
self.linecurrentstock.setText((list[5]))
self.addstock.setText('')
Since the OP does not provide an MRE then I will create a simple demo of how you can implement the functionality of mapping the elements of the selected row of a table in various widgets.
The appropriate widgets must be chosen so that the user does not enter incorrect values, for example in the case of stock if a QLineEdit is used the user could enter a word which does not make sense since a number is expected so it is better to use a QSpinBox.
Also when the data is saved in the table it is not good to convert it to a string since it loses the way to differentiate them, it is better to save the value through setData() associated with the Qt::DisplayRole role.
Finally, the key to the solution is to use a QDataWidgetMapper that allows mapping parts of a model in widgets, so each time a row is selected the currentIndex of the mapper is updated and it sends the information to the editors.
from functools import cached_property
import random
import sys
from PyQt5 import QtCore, QtWidgets
class MainWindow(QtWidgets.QMainWindow):
LABELS = (
"Product ID",
"Product Name",
"Description",
"Category",
"Price",
"Stock",
)
CATEGORY_OPTIONS = (
"OPTION1",
"OPTION2",
"OPTION3",
"OPTION4",
)
def __init__(self, parent=None):
super().__init__(parent)
self.mapper.setModel(self.tableWidget.model())
self.tableWidget.selectionModel().currentChanged.connect(
self.mapper.setCurrentModelIndex
)
central_widget = QtWidgets.QWidget()
self.setCentralWidget(central_widget)
lay = QtWidgets.QHBoxLayout(central_widget)
flay = QtWidgets.QFormLayout()
lay.addLayout(flay)
lay.addWidget(self.tableWidget, stretch=1)
editors = (
self.name_edit,
self.description_edit,
self.category_combo,
self.price_edit,
self.stock_edit,
)
for i, (label, widget) in enumerate(zip(self.LABELS[1:], editors)):
flay.addRow(label, widget)
self.mapper.addMapping(widget, i)
self.fillTable()
self.resize(960, 480)
#cached_property
def tableWidget(self):
table = QtWidgets.QTableWidget(
0,
len(self.LABELS),
selectionBehavior=QtWidgets.QAbstractItemView.SelectRows,
selectionMode=QtWidgets.QAbstractItemView.SingleSelection,
)
table.setHorizontalHeaderLabels(self.LABELS)
return table
#cached_property
def name_edit(self):
return QtWidgets.QLineEdit()
#cached_property
def description_edit(self):
return QtWidgets.QLineEdit()
#cached_property
def category_combo(self):
combo = QtWidgets.QComboBox()
combo.addItems(["--Null--"] + list(self.CATEGORY_OPTIONS))
combo.setCurrentIndex(0)
return combo
#cached_property
def price_edit(self):
return QtWidgets.QDoubleSpinBox(maximum=2147483647)
#cached_property
def stock_edit(self):
return QtWidgets.QSpinBox(maximum=2147483647)
#cached_property
def mapper(self):
return QtWidgets.QDataWidgetMapper()
def fillTable(self):
self.tableWidget.setRowCount(0)
for i in range(30):
self.tableWidget.insertRow(self.tableWidget.rowCount())
values = (
i,
f"name-{i}",
f"Description-{i}",
random.choice(self.CATEGORY_OPTIONS),
random.uniform(100, 2000),
random.randint(0, 100),
)
for j, value in enumerate(values):
it = QtWidgets.QTableWidgetItem()
it.setData(QtCore.Qt.DisplayRole, value)
it.setFlags(it.flags() & ~QtCore.Qt.ItemIsEditable)
self.tableWidget.setItem(i, j, it)
def main():
app = QtWidgets.QApplication(sys.argv)
win = MainWindow()
win.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()

QTableWidget loads very slow after clearing it with QLineEdits inside

I have a QTableWidget with QLineEdits as CellWidgets inside of it.
If I clear the table and refill it with the same function it needs much longer to finish.
In this example the difference is between 0.1 and 3.9 seconds, but in my real code the difference is between 0.1 seconds and 10 minutes.
So here is the example code:
class Test_Layout(QWidget):
def __init__(self, parent=None):
super(Test_Layout, self).__init__(parent=None)
self.left = 0
self.top = 0
self.width = 0
self.height = 0
self.initUI()
self.isMaximized()
def initUI(self):
self.setGeometry(self.left, self.top, self.width, self.height)
self.createTable()
start_time = time.time()
self.fillTable()
print(time.time() - start_time)
self.combo = QComboBox(self)
self.combo.addItem("Reset")
for i in range(0, 5):
self.combo.addItem(str(i))
self.combo.currentTextChanged.connect(self.on_combo_changed)
self.vbox = QVBoxLayout()
self.vbox.addWidget(self.combo)
self.vbox.addWidget(self.table)
self.setLayout(self.vbox)
def fill_row(self, row):
self.table.insertRow(row)
placeholder = QLineEdit()
self.table.setCellWidget(row, 0, placeholder)
placeholder = QLineEdit()
self.table.setCellWidget(row, 1, placeholder)
placeholder = QLineEdit()
self.table.setCellWidget(row, 2, placeholder)
def on_combo_changed(self, currentText):
self.table.setRowCount(0)
if currentText == "Reset":
start_time = time.time()
self.fillTable()
print(time.time() - start_time)
else:
for row in range(0, int(currentText)):
self.fill_row(row)
def createTable(self):
self.table = QTableWidget()
self.table.setColumnCount(3)
self.table.setHorizontalHeaderLabels([
"LineEdit0",
"LineEdit1",
"LineEdit2",
])
header = self.table.horizontalHeader()
for i in range(0, 3):
header.setSectionResizeMode(i, QHeaderView.ResizeToContents)
def fillTable(self):
for row in range(0, 1000):
self.fill_row(row)
Output:
0.14005303382873535
And after using the QCombobox and setting it back to "Reset":
3.9842889308929443
And before somebody asks, I fill the QTableWidget with QLineEdits, because I want to use placeholders.
The difference is not only due to the fact that you're using cell widgets for each cell (and, let me say that 3000 widgets are a lot), but because you're calling setRowCount() each time.
You can also see that the problem happens not after "clearing", but just when creating the new cells: just remove the first call to fillTable in the __init__ and the same delay occurs.
Each time the model layout changes (by adding/removing rows or columns), lots of things happen not only for the model, but for the view that shows its contents, and since you are adding rows individually, this results in longer time required for the view to process its contents, even if you cannot see it instantly (and that's because the repainting is queued and only happens as soon as the event queue is cleared).
To improve performance, in your case, you should call setRowCount() only once with the final number of rows that are going to be shown:
def fill_row(self, row):
# comment or remove the following line
# self.table.insertRow(row)
placeholder = QLineEdit()
self.table.setCellWidget(row, 0, placeholder)
placeholder = QLineEdit()
self.table.setCellWidget(row, 1, placeholder)
placeholder = QLineEdit()
self.table.setCellWidget(row, 2, placeholder)
def on_combo_changed(self, currentText):
self.table.setRowCount(0)
if currentText == "Reset":
start_time = time.time()
self.fillTable()
print(time.time() - start_time)
else:
count = int(currentText)
self.table.setRowCount(count)
for row in range(0, int(currentText)):
self.fill_row(row)
def fillTable(self):
self.table.setRowCount(1000)
for row in range(0, 1000):
self.fill_row(row)
Finally, if you're really going to show that many rows, I strongly suggest to find an alternative, as the documentation explains for setIndexWidget() (which is internally called by setCellWidget()):
This function should only be used to display static content within the visible area corresponding to an item of data. If you want to display custom dynamic content or implement a custom editor widget, subclass QStyledItemDelegate instead.
This is because large amounts of widgets will cause drastic performances issues (exactly like yours).
If what you need is a placeholder, using a QLineEdit for each cell is a bad choice, not only for performance reasons, but also because in that way you don't have direct access to the model data, but you would always need to find the cell widget before that.
A more elegant and preferable solution is to use a custom delegate, which will show the placeholder text when there is no data for the cell, and add CurrentChanged to the table edit triggers:
self.table.setEditTriggers(self.table.editTriggers() | self.table.CurrentChanged)
A simple delegate implementation could be like this:
class PlaceholderDelegate(QStyledItemDelegate):
def __init__(self, placeholder='', parent=None):
super().__init__(parent)
self.placeholder = placeholder
def createEditor(self, parent, option, index):
editor = super().createEditor(parent, option, index)
if isinstance(editor, QLineEdit):
editor.setPlaceholderText(self.placeholder)
return editor
def paint(self, painter, option, index):
super().paint(painter, option, index)
if not index.data():
try:
# placeholderText palette role was introduced on Qt 5.12
color = option.palette.placeholderText().color()
except:
color = option.palette.text().color()
color.setAlpha(128)
painter.setPen(color)
style = option.widget.style()
margin = style.pixelMetric(style.PM_FocusFrameHMargin, option, option.widget)
rect = option.rect.adjusted(margin, 0, -margin, 0)
text = option.fontMetrics.elidedText(self.placeholder, option.textElideMode, rect.width())
painter.drawText(rect, option.displayAlignment, text)
class Test_Layout(QWidget):
# ...
def createTable(self):
# ...
for i in range(0, 3):
header.setSectionResizeMode(i, QHeaderView.ResizeToContents)
self.table.setItemDelegateForColumn(
i, PlaceholderDelegate('empty {}'.format(i + 1), self.table))
Note: you should not use setGeometry() with 0 width and height, and always provide a default position can be very annoying for users with large screens or more than one screen; also, width and height are default properties for all QWidget subclasses, and should never be overwritten with custom attributes.

pyqt5 I want to know how to select the row value in qtableview

I have two tables. I want to get the selected value in table1 and put it in table2.
For example, if you select 1, table2
I want the whole value of row 1 to be entered and the next row 5 to be added to the row 5.
In conclusion, I would like to make table1 show the selected row value in table2.
I do not know exactly how to load the selected table1 value, but I think it would be better to append one value to QStandardItemModel in def table1_DoubleClicked (self): using self.serch.table.setModel in table2. How can I do it?
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.resize(500, 500)
self.Table1()
self.Table2()
self.Layout()
def Table1(self):
self.select_guorpbox = QGroupBox()
self.select_guorpbox.setTitle("Article 1")
self.columncount = 10
self.rowcount = 10
self.select_table_model = QStandardItemModel(self.rowcount,self.columncount)
for i in range(self.rowcount):
for j in range(self.columncount):
table = QStandardItem('test [{},{}]'.format(i,j))
self.select_table_model.setItem(i,j,table)
table.setTextAlignment(Qt.AlignCenter)
self.TextFilter = QSortFilterProxyModel()
self.TextFilter.setSourceModel(self.select_table_model)
self.TextFilter.setFilterKeyColumn(2)
self.SerchLineEdit = QLineEdit()
self.SerchLineEdit.textChanged.connect(self.TextFilter.setFilterRegExp)
self.select_table = QTableView()
self.select_table.setModel(self.TextFilter)
self.select_table.setColumnWidth(1, 150)
self.select_table.setColumnWidth(2, 300)
self.select_table.setEditTriggers(QTableView.NoEditTriggers)
self.select_table.setSelectionBehavior(QTableView.SelectRows)
self.select_table.setContextMenuPolicy(Qt.CustomContextMenu)
self.select_table.doubleClicked.connect(self.table1_DoubleClicked)
self.select_table.customContextMenuRequested.connect(self.table1_CustomContextMenu)
# column auto sort
# self.select_table.setSizeAdjustPolicy(QAbstractScrollArea.AdjustToContents)
# self.select_table.resizeColumnsToContents()
v = QVBoxLayout()
v.addWidget(self.select_table)
self.select_guorpbox.setLayout(v)
def Table2(self):
self.serch_groupbox = QGroupBox()
self.serch_groupbox.setTitle("Article 2")
lable = QLabel("~")
lable.setFixedWidth(10)
lable.setAlignment(Qt.AlignCenter)
insertbutton = QPushButton("insert")
self.startdate = QDateEdit()
self.startdate.setDate(QDate.currentDate())
self.startdate.setFixedWidth(150)
self.startdate.setCalendarPopup(True)
self.enddate = QDateEdit()
self.enddate.setDate(QDate.currentDate())
self.enddate.setFixedWidth(150)
self.enddate.setCalendarPopup(True)
self.article_serch_button = QPushButton("ARTICL SERTCH")
self.article_serch_button.setFixedWidth(250)
self.serch_table = QTableView()
h1 = QHBoxLayout()
h1.addWidget(insertbutton)
h1.addWidget(self.startdate)
h1.addWidget(lable)
h1.addWidget(self.enddate)
h1.addWidget(self.article_serch_button)
h2 = QHBoxLayout()
h2.addWidget(self.serch_table)
v = QVBoxLayout()
v.addLayout(h1)
v.addLayout(h2)
self.serch_groupbox.setLayout(v)
def table1_DoubleClicked(self):
self.k =QItemSelectionModel().Select
def table1_CustomContextMenu(self, position):
menu = QMenu()
menu.addAction("Add")
menu.exec_(self.select_table.mapToGlobal(position))
print("?")
def Layout(self):
self.vbox = QVBoxLayout()
self.vbox.addWidget(self.SerchLineEdit)
self.vbox.addWidget(self.select_guorpbox)
self.vbox.addWidget(self.serch_groupbox)
self.setLayout(self.vbox)
if __name__ == "__main__":
app = QApplication(sys.argv)
fream = MainWindow()
fream.show()
app.exec_()
You could try the following, it copies the selected row from one table to the other:
def table1_DoubleClicked(self, index):
rows = []
row = []
for column_index in range(self.columncount):
cell_idx = self.select_table.model().index(index.row(), column_index)
row.append(self.select_table.model().data(cell_idx))
rows.append(row)
search_table_model = QStandardItemModel(len(rows), self.columncount)
for i in range(len(rows)):
for j in range(self.columncount):
search_table_model.setItem(i, j, QStandardItem(rows[i][j]))
self.serch_table.setModel(search_table_model)

Set selection from a list of indexes in a QTreeView

In PySide, is there a way to selected a few hundred treeview items without having to manually go row by row and select them? The problem with this method is the UI updates each time a new row is selected causing the application to freeze up while it finishes the method. Is there a way I can just pass the selection-model a list of all the rows I want selected?
My treeview has hundres of rows and four columns, but the treeview is set to select entire rows, not cells.
model = self.uiFilesList.model()
rows = self.selectedFileItems.selectedRows()
self.uiFilesList.selectionModel().clear()
I would expect this to work, but it doesn't.
selection = self.uiFilesList.selectionModel().selection()
self.uiFilesList.selectionModel().clear()
mode = QtGui.QItemSelectionModel.Select | QtGui.QItemSelectionModel.Rows
self.uiFilesList.selectionModel().select(selection, mode)
Here is my sample project where the selection is not updating after i update the data in the mode. You'll see in my sample below, when you right-click and change the age or the jersey number the list must be repopulated in order to update the displayed data. However i attempt to store the selection before updating the list. Then i try to restore it after the list is repopulated but it doesn't appear to work.
import sys, os, random
from PySide import QtGui, QtCore
class Person(object):
def __init__(self, name, age, hair, jersey):
self.name = name
self.age = age
self.hair = hair
self.jersey = jersey
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.resize(500, 320)
self.people = [
Person('Kevin', 10, 'Brown', 20),
Person('Marsha', 32, 'Blonde', 00),
Person('Leslie', 27, 'Black', 15),
Person('Tim', 53, 'Red', 37),
Person('Marie', 65, 'Brown', 101),
Person('Bella', 8, 'Blonde', 1)
]
self.treeview = QtGui.QTreeView()
self.treeview.setAlternatingRowColors(True)
self.treeview.setModel(QtGui.QStandardItemModel())
self.treeview.setSortingEnabled(True)
self.treeview.setRootIsDecorated(False)
self.treeview.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
self.treeview.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
self.treeview.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
self.treeview.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.treeview.customContextMenuRequested.connect(self.open_menu)
self.selectedItems = self.treeview.selectionModel()
self.setCentralWidget(self.treeview)
self.populate_list()
# actions
self.actRandomizeAges = QtGui.QAction('Randomize Ages', self)
self.actRandomizeJerseys = QtGui.QAction('Randomize Jersey Numbers', self)
# menu
self.cmenu = QtGui.QMenu()
self.cmenu.addAction(self.actRandomizeAges)
self.cmenu.addAction(self.actRandomizeJerseys)
# connections
self.actRandomizeAges.triggered.connect(self.randomize_ages)
self.actRandomizeJerseys.triggered.connect(self.randomize_jerseys)
def open_menu(self, position):
self.cmenu.exec_(self.treeview.viewport().mapToGlobal(position))
def randomize_ages(self):
rows = self.selectedItems.selectedRows()
for i, item in enumerate(rows):
obj = item.data(role=QtCore.Qt.UserRole)
obj.age = random.randint(0, 70)
self.populate_list()
def randomize_jerseys(self):
rows = self.selectedItems.selectedRows()
for i, item in enumerate(rows):
obj = item.data(role=QtCore.Qt.UserRole)
obj.jersey = random.randint(1, 100)
self.populate_list()
def populate_list(self):
selection = self.treeview.selectionModel().selection()
flags = QtGui.QItemSelectionModel.Select
self.treeview.selectionModel().clear()
model = self.treeview.model()
model.clear()
model.setHorizontalHeaderLabels(['Name','Age','Hair', 'Jersey'])
for p in self.people:
# column 1
col1 = QtGui.QStandardItem()
col1.setData(p.name, role=QtCore.Qt.DisplayRole)
col1.setData(p, role=QtCore.Qt.UserRole)
# column 2
col2 = QtGui.QStandardItem()
col2.setData(p.age, role=QtCore.Qt.DisplayRole)
if p.age > 30:
col2.setData(QtGui.QBrush(QtGui.QColor(255,0,0,255)), role=QtCore.Qt.ForegroundRole)
# column 3
col3 = QtGui.QStandardItem()
col3.setData(p.hair, role=QtCore.Qt.DisplayRole)
# column 4
col4 = QtGui.QStandardItem()
col4.setData(p.jersey, role=QtCore.Qt.DisplayRole)
if p.jersey > 30:
col4.setData(QtGui.QBrush(QtGui.QColor(0,0,255,255)), role=QtCore.Qt.ForegroundRole)
model.appendRow([col1, col2, col3, col4])
self.treeview.selectionModel().select(selection, flags)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
ex = MainWindow()
ex.show()
sys.exit(app.exec_())
Your example doesn't work because it uses the wrong selection-flags. If you use QItemSelectionModel.Select alone, it will work correctly (and will select whole rows).
To set the selection from a list of indexes, you can create a series of QItemSelection objects which cover contiguous ranges, and merge them all into one selection.
Here is a demo script which shows how to do that:
import sys
from PySide import QtCore, QtGui
class Window(QtGui.QWidget):
def __init__(self):
super(Window, self).__init__()
self.button = QtGui.QPushButton('Select')
self.button.clicked.connect(self.handleButton)
self.tree = QtGui.QTreeView()
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.tree)
layout.addWidget(self.button)
columns = 'One Two Three Four'.split()
mod = QtGui.QStandardItemModel(self)
mod.setHorizontalHeaderLabels(columns)
for row in range(1000):
mod.appendRow((
QtGui.QStandardItem('A%s' % row),
QtGui.QStandardItem('B%s' % row),
QtGui.QStandardItem('C%s' % row),
QtGui.QStandardItem('D%s' % row),
))
self.tree.setModel(mod)
self.tree.setSelectionMode(
QtGui.QAbstractItemView.ExtendedSelection)
def handleButton(self):
mod = self.tree.model()
columns = mod.columnCount() - 1
flags = QtGui.QItemSelectionModel.Select
selection = QtGui.QItemSelection()
for start, end in ((2, 15), (25, 260), (500, 996)):
start, end = mod.index(start, 0), mod.index(end, columns)
if selection.indexes():
selection.merge(QtGui.QItemSelection(start, end), flags)
else:
selection.select(start, end)
self.tree.selectionModel().clear()
self.tree.selectionModel().select(selection, flags)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = Window()
window.setGeometry(700, 50, 500, 600)
window.show()
sys.exit(app.exec_())
UPDATE:
The example in your question doesn't work because your populate_list method clears the model, which will invalidate all the indexes in the selection. So you need a method to save the current selection as a list of row-numbers (rather than model-indexes). This can then be fed to the method I gave above to re-create the selection.
If you update your example as follows, it should work as expected:
def save_selection(self):
selection = self.treeview.selectionModel().selectedRows()
blocks = []
for count, index in enumerate(sorted(selection)):
row = index.row()
if count > 0 and row == block[1] + 1:
block[1] = row
else:
block = [row, row]
blocks.append(block)
return blocks
def create_selection(self, blocks):
mod = self.treeview.model()
columns = mod.columnCount() - 1
flags = QtGui.QItemSelectionModel.Select
selection = QtGui.QItemSelection()
for start, end in blocks:
start, end = mod.index(start, 0), mod.index(end, columns)
if selection.indexes():
selection.merge(QtGui.QItemSelection(start, end), flags)
else:
selection.select(start, end)
self.treeview.selectionModel().clear()
self.treeview.selectionModel().select(selection, flags)
def populate_list(self):
selection = self.save_selection()
... # re-populate model
self.create_selection(selection)

Categories

Resources