how to edit specific item in qtableview? - python

Is there a way to edit specific item in qtableview given the row and col value? For example, I want to increment its value every second. Here is my tablemodel. Thanks
class MyTableModel(QAbstractTableModel):
def __init__(self, datain, headerdata, parent=None, *args):
""" datain: a list of lists
headerdata: a list of strings
"""
QAbstractTableModel.__init__(self, parent, *args)
self.arraydata = datain
self.headerdata = headerdata
def rowCount(self, parent):
return len(self.arraydata)
def columnCount(self, parent):
try:
return len(self.arraydata[0])
except:
return 0
def data(self, index, role):
if not index.isValid():
return QVariant()
elif role != Qt.DisplayRole:
return QVariant()
return QVariant(self.arraydata[index.row()][index.column()])
def headerData(self, col, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return QVariant(self.headerdata[col])
return QVariant()
def sort(self, Ncol, order):
"""Sort table by given column number.
"""
self.emit(SIGNAL("layoutAboutToBeChanged()"))
self.arraydata = sorted(self.arraydata, key=operator.itemgetter(Ncol))
if order == Qt.DescendingOrder:
self.arraydata.reverse()
self.emit(SIGNAL("layoutChanged()"))

You could increment the value directly in your model, and emit a dataChanged signal from the model.
For example, add a method like this to the model class:
def incrementData(row, column):
self.arraydata[row][column] += 1
idx = self.index(row, column)
self.emit(SIGNAL("dataChanged(QModelIndex,QModelIndex)"), idx, idx)

Related

How to only update the data that has changed in QTableView (Python)

So basically this is my PandasModel:
class pandasModel(QAbstractTableModel):
def __init__(self, data, editable=False):
QAbstractTableModel.__init__(self)
self._data = data
self.editable = editable
def rowCount(self, parent=None):
return self._data.shape[0]
def columnCount(self, parnet=None):
return self._data.shape[1]
def data(self, index, role=Qt.DisplayRole):
if index.isValid():
if role == Qt.DisplayRole:
return str(self._data.iloc[index.row(), index.column()])
elif role == Qt.TextAlignmentRole:
return Qt.AlignCenter
def setData(self, index, value, role):
if role == Qt.EditRole:
self._data.iloc[index.row(), index.column()] = value
return True
return False
def headerData(self, col, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return self._data.columns[col]
def flags(self, index):
if self.editable:
return Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable
else:
return Qt.ItemIsEnabled | Qt.ItemIsSelectable
def sort(self, col, order):
"""sort table by given column number col"""
self.layoutAboutToBeChanged.emit()
self._data = self._data.sort_values(
self._data.columns[col], ascending=order == Qt.AscendingOrder)
self.layoutChanged.emit()
And this is the way I'm updating my Table when the data is changed and received from the thread using API.
Basically my other Thread runs in a while loop constantly and if the data is changed it emits the new array of data to main thread and then the data is received and that is when the data is changed.
self.Table.setModel(pandasModel(self.myDataFrame))
I only wanted to update the new data but this updates whole table, due to that the selection is also lost!
Is there other way we can do it so that the values are updated but the table doesn't reset completely and also the selections of rows to be not lost???
P.S. the data is huge so cannot just pick one and update manually I want something dynamic

How to alter dropEvent action in treeview without loosing basic drag-n-drop functionality in PyQt5?

I'm using my custom item model (subclassed from QAbstractItemModel) with custom QTreeView. I want to allow internal drag-n-drop movement (MoveAction) and, when modifier key or right mouse button is pressed, pass CopyAction to my model (to dropMimeData) to copy items. However, default implementation of dropEvent() in QTreeView seems (from C code) only capable of passing MoveAction but when I try to reimplement dropEvent() in my QTreeView subclass like this:
def dropEvent(self, e):
index = self.indexAt(e.pos())
parent = index.parent()
self.model().dropMimeData(e.mimeData(), e.dropAction(), index.row(), index.column(), parent)
e.accept()
... it works, but works horribly in terms of user interaction because there are tons of comlex code determining right index to drop item on in default implementation.
When i'm trying to modify action and call to superclass: super(Tree, self).dropEvent(e) dropAction() data is also lost.
What can I do in order to modify dropAction without loosing all fancy things that default dropEvent is doing for me?
Horrible mess of my current WIP code (i hope it's somewhere near minimal example)
from copy import deepcopy
import pickle
import config_editor
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt as Qt
from PyQt5.QtGui import QCursor, QStandardItemModel
from PyQt5.QtWidgets import QAbstractItemView, QTreeView, QMenu
class ConfigModelItem:
def __init__(self, label, value="", is_section=False, state='default', parent=None):
self.itemData = [label, value]
self.is_section = is_section
self.state = state
self.childItems = []
self.parentItem = parent
if self.parentItem is not None:
self.parentItem.appendChild(self)
def appendChild(self, item):
self.childItems.append(item)
item.parentItem = self
def addChildren(self, items, row):
if row == -1:
row = 0
self.childItems[row:row] = items
for item in items:
item.parentItem = self
def child(self, row):
return self.childItems[row]
def childCount(self):
return len(self.childItems)
def columnCount(self):
return 2
def data(self, column):
try:
return self.itemData[column]
except IndexError:
return None
def set_data(self, data, column):
try:
self.itemData[column] = data
except IndexError:
return False
return True
def parent(self):
return self.parentItem
def row(self):
if self.parentItem is not None:
return self.parentItem.childItems.index(self)
return 0
def removeChild(self, position):
if position < 0 or position > len(self.childItems):
return False
child = self.childItems.pop(position)
child.parentItem = None
return True
def __repr__(self):
return str(self.itemData)
class ConfigModel(QtCore.QAbstractItemModel):
def __init__(self, data, parent=None):
super(ConfigModel, self).__init__(parent)
self.rootItem = ConfigModelItem("Option", "Value")
self.setup(data)
def headerData(self, section, orientation, role):
if role == Qt.DisplayRole and orientation == Qt.Horizontal:
return self.rootItem.data(section)
def columnCount(self, parent):
return 2
def rowCount(self, parent):
if parent.column() > 0:
return 0
if not parent.isValid():
parentItem = self.rootItem
else:
parentItem = parent.internalPointer()
return parentItem.childCount()
def index(self, row, column, parent):
if not self.hasIndex(row, column, parent):
return QtCore.QModelIndex()
parentItem = self.nodeFromIndex(parent)
childItem = parentItem.child(row)
if childItem:
return self.createIndex(row, column, childItem)
else:
return QtCore.QModelIndex()
def parent(self, index):
if not index.isValid():
return QtCore.QModelIndex()
childItem = index.internalPointer()
parentItem = childItem.parent()
if parentItem == self.rootItem or parentItem is None:
return QtCore.QModelIndex()
return self.createIndex(parentItem.row(), 0, parentItem)
def nodeFromIndex(self, index):
if index.isValid():
return index.internalPointer()
return self.rootItem
def data(self, index, role):
if not index.isValid():
return None
item = index.internalPointer()
if role == Qt.DisplayRole or role == Qt.EditRole:
return item.data(index.column())
return None
def setData(self, index, value, role=Qt.EditRole):
if not index.isValid():
return False
item = index.internalPointer()
if role == Qt.EditRole:
item.set_data(value, index.column())
self.dataChanged.emit(index, index, (role,))
return True
def flags(self, index):
if not index.isValid():
return QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled # Qt.NoItemFlags
item = index.internalPointer()
flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
if index.column() == 0:
flags |= int(QtCore.Qt.ItemIsDragEnabled)
if item.is_section:
flags |= int(QtCore.Qt.ItemIsDropEnabled)
if index.column() == 1 and not item.is_section:
flags |= Qt.ItemIsEditable
return flags
def supportedDropActions(self):
return QtCore.Qt.CopyAction | QtCore.Qt.MoveAction
def mimeTypes(self):
return ['app/configitem', 'text/xml']
def mimeData(self, indexes):
mimedata = QtCore.QMimeData()
index = indexes[0]
mimedata.setData('app/configitem', pickle.dumps(self.nodeFromIndex(index)))
return mimedata
def dropMimeData(self, mimedata, action, row, column, parentIndex):
print('action', action)
if action == Qt.IgnoreAction:
return True
droppedNode = deepcopy(pickle.loads(mimedata.data('app/configitem')))
print('copy', action & Qt.CopyAction)
print(droppedNode.itemData, 'node')
self.insertItems(row, [droppedNode], parentIndex)
self.dataChanged.emit(parentIndex, parentIndex)
if action & Qt.CopyAction:
return False # to not delete original item
return True
def removeRows(self, row, count, parent):
print('rem', row, count)
self.beginRemoveRows(parent, row, row+count-1)
parentItem = self.nodeFromIndex(parent)
for x in range(count):
parentItem.removeChild(row)
self.endRemoveRows()
print('removed')
return True
#QtCore.pyqtSlot()
def removeRow(self, index):
parent = index.parent()
self.beginRemoveRows(parent, index.row(), index.row())
parentItem = self.nodeFromIndex(parent)
parentItem.removeChild(index.row())
self.endRemoveRows()
return True
def insertItems(self, row, items, parentIndex):
print('ins', row)
parent = self.nodeFromIndex(parentIndex)
self.beginInsertRows(parentIndex, row, row+len(items)-1)
parent.addChildren(items, row)
print(parent.childItems)
self.endInsertRows()
self.dataChanged.emit(parentIndex, parentIndex)
return True
def setup(self, data: dict, parent=None):
if parent is None:
parent = self.rootItem
for key, value in data.items():
if isinstance(value, dict):
item = ConfigModelItem(key, parent=parent, is_section=True)
self.setup(value, parent=item)
else:
parent.appendChild(ConfigModelItem(key, value))
def to_dict(self, parent=None) -> dict:
if parent is None:
parent = self.rootItem
data = {}
for item in parent.childItems:
item_name, item_data = item.itemData
if item.childItems:
data[item_name] = self.to_dict(item)
else:
data[item_name] = item_data
return data
#property
def dict(self):
return self.to_dict()
class ConfigDialog(config_editor.Ui_config_dialog):
def __init__(self, data):
super(ConfigDialog, self).__init__()
self.model = ConfigModel(data)
def setupUi(self, config_dialog):
super(ConfigDialog, self).setupUi(config_dialog)
self.config_view = Tree()
self.config_view.setObjectName("config_view")
self.config_view.setModel(self.model)
self.gridLayout.addWidget(self.config_view, 0, 0, 1, 1)
self.config_view.expandAll()
#self.config_view.setDragDropMode(True)
#self.setDragDropMode(QAbstractItemView.InternalMove)
#self.setDragEnabled(True)
#self.setAcceptDrops(True)
#self.setDropIndicatorShown(True)
self.delete_button.pressed.connect(self.remove_selected)
def remove_selected(self):
index = self.config_view.selectedIndexes()[0]
self.model.removeRow(index)\
class Tree(QTreeView):
def __init__(self):
QTreeView.__init__(self)
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.open_menu)
self.setSelectionMode(self.SingleSelection)
self.setDragDropMode(QAbstractItemView.InternalMove)
self.setDragEnabled(True)
self.setAcceptDrops(True)
self.setDropIndicatorShown(True)
self.setAnimated(True)
def dropEvent(self, e):
print(e.dropAction(), 'baseact', QtCore.Qt.CopyAction)
# if e.keyboardModifiers() & QtCore.Qt.AltModifier:
# #e.setDropAction(QtCore.Qt.CopyAction)
# print('copy')
# else:
# #e.setDropAction(QtCore.Qt.MoveAction)
# print("drop")
print(e.dropAction())
#super(Tree, self).dropEvent(e)
index = self.indexAt(e.pos())
parent = index.parent()
print('in', index.row())
self.model().dropMimeData(e.mimeData(), e.dropAction(), index.row(), index.column(), parent)
e.accept()
def open_menu(self):
menu = QMenu()
menu.addAction("Create new item")
menu.exec_(QCursor.pos())
if __name__ == '__main__':
import sys
def except_hook(cls, exception, traceback):
sys.__excepthook__(cls, exception, traceback)
sys.excepthook = except_hook
app = QtWidgets.QApplication(sys.argv)
Dialog = QtWidgets.QDialog()
data = {"section 1": {"opt1": "str", "opt2": 123, "opt3": 1.23, "opt4": False, "...": {'subopt': 'bal'}},
"section 2": {"opt1": "str", "opt2": [1.1, 2.3, 34], "opt3": 1.23, "opt4": False, "...": ""}}
ui = ConfigDialog(data)
ui.setupUi(Dialog)
print(Qt.DisplayRole)
Dialog.show()
print(app.exec_())
print(Dialog.result())
print(ui.model.to_dict())
sys.exit()
setDragDropMode(QAbstractItemView.InternalMove) only allows move operations (as the name would suggest, although the docs do leave some uncertainty in the way this is stated). You probably want to set it to QAbstractItemView.DragDrop mode. You can set the default action with setDefaultDropAction(). Other than that, it's up to the model to return the right item flags and supportedDropActions()/canDropMimeData(), which it looks like yours does. There's also a dragDropOverwriteMode property which may be interesting.
One thing that has surprised me before is that in the model's dropMimeData() method if you return True from a Qt.MoveAction, the QAbstractItemView will remove the dragged item from the model automatically (with a removeRows()/removeColumns() call to your model). This can cause some puzzling results if your model has already actually moved that row (and deleted the old one). I never quite understood that behavior. OTOH if you return False it doesn't matter to the item view, as long as the data is actually moved/updated properly.

Pyqt5 QAbstractTableModel dataChanged not updating data

I am trying to update my QTableView after I receive a notice via pydispatcher of a change in the system. I did create the following functions
def rowCount(self, parent=None):
return len(self.m_list)
def columnCount(self, parent=None):
return len(self.table_def)
def headerData(self, col, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return self.table_def[col]['Header']
return QVariant()
def data(self, index, role=Qt.DisplayRole):
if not index.isValid():
return None
if index.row() >= len(self.m_list) or index.row() < 0:
return None
row = index.row()
col = index.column()
print("Called for (%s, %s, %s)" % (row, col, role))
if role == Qt.DisplayRole:
return self.table_def[index.column()]['Function'](index.row())
elif role == Qt.BackgroundRole:
batch = (index.row() // 100) % 2
if batch == 0:
return QApplication.palette().base()
return QApplication.palette().alternateBase()
else:
return None
def flags(self, index):
if not index.isValid():
return None
return Qt.ItemIsEnabled
def update_model(self, data):
print('update_model')
index_1 = self.index(0, 0)
index_2 = self.index(0, 1)
self.dataChanged.emit(index_1, index_2, [Qt.DisplayRole])
The line self.dataChanged.emit(index_1, index_2, [Qt.DisplayRole]) does not seems to do anything; i.e. data(self, index, role=Qt.DisplayRole) is not called.
If I click on the table, then data(self, index, role=Qt.DisplayRole) is called and the table update.
The fix that I have right now is to call beginResetModel() and endResetModel(). That works, but it is not how it should work.
Any idea what could be happening?
I had the same problem and I fixed it just by calling self.headerDataChanged.emit instead. So, to do that, once you change something in the table, call the following:
self.headerDataChanged.emit(Qt.Horizontal, idx1, idx2)
where self._data is your data within the class. idx1 and idx2 are the first and last indexes of changed data, respectively. Qt.Horizontal is an example and it could be vertical depending on your table content.

QTableWidget becomes slow for large tables

I want to create a Qt table widget which adds about 20 new rows per second (maxed at 10000). Old rows are never changed.
Ar first I used QTableWidget, but I see the CPU% increases as the table size increases, and gets to 100% at around 1000 rows total.
So I tried to create my own model, which only does beginInsertRows and endInsertRows every 5 seconds. I hoped that the CPU% will become constant since I only emit the new rows, but I see that it still climbs up to 100%.
So I checked the number of data() calls I'm getting between emits, and I saw that is equal to about 240 * total_num_rows.
Why does the view query all of the rows and not just the new ones?
Why 240? I have 10 columns total per row. Are there 24 Roles?
Can I control the view somehow to do less queries (constant)?
Edit
Item view:
''' How to display each item '''
class BindedItemView(object):
def __init__(self, item):
self._data = item
#--------------------------------------------------------------------#
def _text(self, index):
return QVariant()
#--------------------------------------------------------------------#
def _font(self, index):
return QVariant()
#--------------------------------------------------------------------#
def _background(self, index):
return QVariant()
#--------------------------------------------------------------------#
def _foreground(self, index):
return QVariant()
#--------------------------------------------------------------------#
def get(self, role, col):
if role == Qt.DisplayRole:
return self._text(col)
if role == Qt.FontRole:
return self._font(col)
if role == Qt.ForegroundRole:
return self._foreground(col)
if role == Qt.BackgroundRole:
return self._background(col)
return QVariant()
Model:
class BindedTableModel(QAbstractTableModel):
end_of_process_signal = pyqtSignal()
#--------------------------------------------------------------------#
def __init__(self, headers, item_view):
super(BindedTableModel, self).__init__()
self._headers = headers
self._item_view = item_view
self._items = []
self._last_row_count = 0
self._num_data_requests = 0
#--------------------------------------------------------------------#
def _index(self, item):
return self._items.index(item)
#--------------------------------------------------------------------#
def _indexSorted(self, item):
return bisect.bisect(self._items, item)
#--------------------------------------------------------------------#
def _refreshView(self):
row_count = len(self._items)
print "DATA REQUESTS: %u" % self._num_data_requests
self._num_data_requests = 0
if self._last_row_count < row_count:
print "INSERT ROWS: %u %u" % (self._last_row_count, row_count - 1)
self.beginInsertRows(QModelIndex(), self._last_row_count, row_count - 1)
self.endInsertRows()
# elif self._last_row_count > row_count:
# self.beginRemoveRows(QModelIndex(), row_count, self._last_row_count - 1)
# self.endRemoveRows()
# else:
# top_left = self.createIndex(0, 0)
# bottom_right = self.createIndex(row_count - 1, self.columnCount() - 1)
# self.dataChanged.emit(top_left, bottom_right)
self._last_row_count = row_count
#--------------------------------------------------------------------#
def _onUpdate(self):
pass
#--------------------------------------------------------------------#
def _addItem(self, item, pos):
self._items.insert(pos, item)
self._onUpdate()
#--------------------------------------------------------------------#
def _removeItem(self, pos):
self._items.pop(pos)
self._onUpdate()
#--------------------------------------------------------------------#
def appendItem(self, item):
self._addItem(item, len(self._items))
#--------------------------------------------------------------------#
def addItemSorted(self, item):
self._addItem(item, self._indexSorted(item))
#--------------------------------------------------------------------#
def removeItem(self, item):
self._removeItem(self._index(item))
#--------------------------------------------------------------------#
def clear(self):
self._items = []
#--------------------------------------------------------------------#
def refreshView(self):
self._refreshView()
#--------------------------------------------------------------------#
'''
Override
'''
def rowCount(self, parent = None):
return len(self._items)
#--------------------------------------------------------------------#
'''
Override
'''
def columnCount(self, parent = None):
return len(self._headers)
#--------------------------------------------------------------------#
'''
Override
'''
def data(self, index, role):
self._num_data_requests += 1
item = self._items[index.row()]
view = self._item_view(item)
return view.get(role, index.column())
#--------------------------------------------------------------------#
'''
Override
'''
def headerData(self, section, orientation, role):
if role == Qt.DisplayRole:
if orientation == Qt.Horizontal:
return self._headers[section]
return QVariant()
#--------------------------------------------------------------------#
'''
Override
'''
def setData(self, index, value, role):
if role == Qt.DisplayRole:
pass
#--------------------------------------------------------------------#
def __iter__(self):
return self._items.__iter__()
#--------------------------------------------------------------------#
def __contains__(self, item):
return item in self._items
This is a workaround, but limiting the model to row count < 1000 gives a constant CPU%.
So an elegant solution for me will be to create a "paged" table view, which only shows N rows at a time (and the user can control the currently displayed window).
This yields excellent CPU as well as better usability, so I think the original question is not relevant.

Pandas df in editable QTableView: remove check boxes

I have a pandas dataframe that I would like to present in a QtableView and make it editable. I have create the below model, but for some reason the output has checkboxes in every field. How can I get rid of them?
The outout looks like this:
And this this is the model that is used to make the pandas dataframe shown in a qtavleview and make it editable (I'm using PySide)
class PandasModelEditable(QtCore.QAbstractTableModel):
def __init__(self, data, parent=None):
QtCore.QAbstractTableModel.__init__(self, parent)
self._data = data
def rowCount(self, parent=None):
return len(self._data.values)
def columnCount(self, parent=None):
return self._data.columns.size
def data(self, index, role=QtCore.Qt.DisplayRole):
if index.isValid():
if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
return unicode(self._data.iloc[index.row(), index.column()])
return unicode()
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if role != QtCore.Qt.DisplayRole:
return None
if orientation == QtCore.Qt.Horizontal:
try:
return '%s' % unicode(self._data.columns.tolist()[section])
except (IndexError,):
return unicode()
elif orientation == QtCore.Qt.Vertical:
try:
return '%s' % unicode(self._data.index.tolist()[section])
except (IndexError,):
return unicode()
def flags(self, index):
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | \
QtCore.Qt.ItemIsEditable
def setData(self, index, value, role=QtCore.Qt.EditRole):
if index.isValid():
self._data.iloc[index.row(), index.column()] = value
if self.data(index, QtCore.Qt.DisplayRole) == value:
self.dataChanged.emit(index, index)
return True
return unicode()
Removing QtCore.Qt.ItemIsSelectable does not solve the problem as it doesn't seem to have any effet.
You are returning the wrong default values from data and setaData. The former should return None (so you could just remove the last line), whilst the latter should return False.

Categories

Resources