IU have a Qt window with a button and a QTableView which loads some data from a pandas dataframe using the QAbstractTableModel.
I would like to set the backgroundcolor of a cel (or a row) when i click on the button.
This is my code so far:
import timeit
import pandas as pd
from PyQt5 import QtWidgets
from PyQt5.QtCore import *
from PyQt5.QtCore import QAbstractTableModel, Qt
from PyQt5.QtGui import *
from PyQt5.uic import loadUi
class PandasModel(QAbstractTableModel):
def __init__(self, data, parent=None):
QAbstractItemModel.__init__(self, parent)
self._data = data
self.colors = dict()
def rowCount(self, parent=None):
return self._data.index.size
def columnCount(self, parent=None):
return self._data.columns.size
def data(self, index, role=Qt.DisplayRole):
if index.isValid():
if role == Qt.DisplayRole:
return str(self._data.iloc[index.row(), index.column()])
if role == Qt.EditRole:
return str(self._data.iloc[index.row(), index.column()])
if role == Qt.BackgroundRole:
color = self.colors.get((index.row(), index.column()))
if color is not None:
return color
return None
def headerData(self, rowcol, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return self._data.columns[rowcol]
if orientation == Qt.Vertical and role == Qt.DisplayRole:
return self._data.index[rowcol]
return None
def flags(self, index):
flags = super(self.__class__, self).flags(index)
flags |= Qt.ItemIsEditable
flags |= Qt.ItemIsSelectable
flags |= Qt.ItemIsEnabled
flags |= Qt.ItemIsDragEnabled
flags |= Qt.ItemIsDropEnabled
return flags
def sort(self, Ncol, order):
"""Sort table by given column number.
"""
try:
self.layoutAboutToBeChanged.emit()
self._data = self._data.sort_values(self._data.columns[Ncol], ascending=not order)
self.layoutChanged.emit()
except Exception as e:
print(e)
def change_color(self, row, column, color):
ix = self.index(row, column)
self.colors[(row, column)] = color
self.dataChanged.emit(ix, ix, (Qt.BackgroundRole,))
class TableViewer(QtWidgets.QMainWindow):
def __init__(self):
super(TableViewer, self).__init__()
self.ui = loadUi("QTableViewForm.ui", self)
self.ui.cmdRun1.clicked.connect(self.RunFunction1)
self.showdata()
def showdata(self):
data = pd.read_pickle("productdata.pkl")
self.model = PandasModel(data)
self.ui.tableData.setModel(self.model)
def set_cell_color(self, row, column):
self.model.change_color(row, column, QBrush(Qt.red))
def RunFunction1(self):
start = timeit.default_timer()
print("Start RunFunction1")
print("Stop RunFunction1")
end = timeit.default_timer()
print("Process Time: ", (end - start))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
win = TableViewer()
win.set_cell_color(row=7,column=7)
win.show()
sys.exit(app.exec_())
When i run this i get cell (7,7) with a red background.
Now i would like to do this by clicking the button and running the function Run Function1 but i can't get this to work.
Got it working , i just go over the row to get it in color. I'm working a lot with dataframes so if annyone has any suggestions to complete the PandasModel(QAbstractTableModel) class feel free ;-)
Updated Code:
import timeit
import pandas as pd
from PyQt5 import QtWidgets
from PyQt5.QtCore import *
from PyQt5.QtCore import QAbstractTableModel, Qt
from PyQt5.QtGui import *
from PyQt5.uic import loadUi
class PandasModel(QAbstractTableModel):
def __init__(self, data, parent=None):
QAbstractItemModel.__init__(self, parent)
self._data = data
self.colors = dict()
def rowCount(self, parent=None):
return self._data.index.size
def columnCount(self, parent=None):
return self._data.columns.size
def setData(self, index, value, role):
if role == Qt.EditRole:
# Set the value into the frame.
self._data.iloc[index.row(), index.column()] = value
return True
def data(self, index, role=Qt.DisplayRole):
if index.isValid():
if role == Qt.DisplayRole:
return str(self._data.iloc[index.row(), index.column()])
if role == Qt.EditRole:
return str(self._data.iloc[index.row(), index.column()])
if role == Qt.BackgroundRole:
color = self.colors.get((index.row(), index.column()))
if color is not None:
return color
return None
def headerData(self, rowcol, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return self._data.columns[rowcol]
if orientation == Qt.Vertical and role == Qt.DisplayRole:
return self._data.index[rowcol]
return None
def change_color(self, row, column, color):
ix = self.index(row, column)
self.colors[(row, column)] = color
self.dataChanged.emit(ix, ix, (Qt.BackgroundRole,))
class TableViewer(QtWidgets.QMainWindow):
def __init__(self):
super(TableViewer, self).__init__()
self.ui = loadUi("QTableViewForm.ui", self)
self.ui.cmdRun1.clicked.connect(self.RunFunction1)
self.showdata()
def showdata(self):
data = pd.read_pickle("productdata.pkl")
self.model = PandasModel(data)
# self.ui.tableData.setModel(self.model)
self.proxy_model = QSortFilterProxyModel()
self.proxy_model.setFilterKeyColumn(-1) # Search all columns.
self.proxy_model.setSourceModel(self.model)
self.proxy_model.sort(0, Qt.AscendingOrder)
self.tableData.setModel(self.proxy_model)
def set_cell_color(self, row, column):
self.model.change_color(row, column, QBrush(Qt.red))
def RunFunction1(self):
start = timeit.default_timer()
print("Start RunFunction1")
#Gans de rij in 't rood
colums = self.proxy_model.columnCount()
for c in range(colums):
self.set_cell_color(3, c)
print("Stop RunFunction1")
end = timeit.default_timer()
print("Process Time: ", (end - start))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
win = TableViewer()
win.show()
sys.exit(app.exec_())
Related
I tried something out with QTableview and want that the mousepointer changed to "hand" and font color changed to blue if I enter a specific cell in QTableview. Is that possible? So the cell should be use like a button without being a button.
I provide you a demo, maybe you want this.
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import numpy as np
class Model(QAbstractTableModel):
def __init__(self):
super().__init__()
self._data = np.random.randint(0, 255, (5, 10))
self._color = dict()
def rowCount(self, parent: QModelIndex = ...) -> int:
return self._data.shape[0]
def columnCount(self, parent: QModelIndex = ...) -> int:
return self._data.shape[1]
def data(self, index: QModelIndex, role: int = ...) :
if index.isValid() and role == Qt.DisplayRole:
return str(self._data[index.row(), index.column()])
if index.isValid() and role == Qt.ForegroundRole:
if f"{index.row(),index.column()}" in self._color:
return self._color[f"{index.row(),index.column()}"]
def setData(self, index: QModelIndex, value, role: int = ...) -> bool:
if index.isValid() and role == Qt.ForegroundRole:
self._color = dict()
self._color[f"{index.row(),index.column()}"] = value
self.dataChanged.emit(self.index(0,0), self.index(self.rowCount()-1, self.columnCount() -1))
return True
return super().setData(index, value, role)
class View(QTableView):
def mouseMoveEvent(self, e: QMouseEvent) -> None:
index = self.indexAt(e.pos())
if index.isValid():
self.model().setData(index, QBrush(Qt.blue), Qt.ForegroundRole)
self.setCursor(Qt.PointingHandCursor)
else:
self.setCursor(Qt.ArrowCursor)
return super().mouseMoveEvent(e)
app = QApplication([])
v = View()
v.setMouseTracking(True)
v.setModel(Model())
v.show()
app.exec()
Such "visual" hints should never modify the underlying model, and should always be managed by the item delegate instead.
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class ItemDelegate(QStyledItemDelegate):
def __init__(self, parent=None):
super().__init__(parent)
self.highlightedCells = []
def setHighlightedCells(self, cells):
self.highlightedCells.clear()
for row, column in cells:
self.highlightedCells.append((row, column))
def initStyleOption(self, option, index):
super().initStyleOption(option, index)
if not (index.row(), index.column()) in self.highlightedCells:
return
if option.state & QStyle.State_MouseOver:
option.palette.setColor(QPalette.Text, Qt.blue)
def editorEvent(self, event, model, option, index):
if event.type() == event.MouseMove:
if (index.row(), index.column()) in self.highlightedCells:
option.widget.viewport().setCursor(Qt.PointingHandCursor)
else:
option.widget.viewport().unsetCursor()
return super().editorEvent(event, model, option, index)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
table = QTableWidget(5, 5)
for r in range(5):
for c in range(5):
table.setItem(r, c,
QTableWidgetItem('{} {}'.format(r + 1, c + 1)))
# required for updates on mouse movements (especially the cursor)
table.setMouseTracking(True)
delegate = ItemDelegate(table)
table.setItemDelegate(delegate)
# the following is for the fourth column of the first 3 rows
delegate.setHighlightedCells([(0, 3), (1, 3), (2, 3)])
table.show()
sys.exit(app.exec_())
I am to learn model/view and write a demo, but my table has no data and couldn't set data.
And checkIndex method of QAbstractItemMode couldn't work ???
The code:
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtPrintSupport import *
from PyQt5.QtChart import *
import numpy as np
class TableModel(QAbstractTableModel):
def __init__(self, data: np.ndarray):
super().__init__()
self.dataArray = data
def rowCount(self, parent):
return self.dataArray.shape[0]
def columnCount(self, parent):
return self.dataArray.shape[1]
def data(self, index: QModelIndex, role=None):
# checkIndex method not working ???
# self.checkIndex(index, QAbstractItemModel::IndexIsValid)
if not index.isValid():
return None
if index.row() >= self.dataArray.shape[0] or index.column() >= self.dataArray.shape[1]:
return None
if role in [Qt.DisplayRole, Qt.EditRole]:
return self.dataArray[index.row()][index.column()]
else:
return None
def headerData(self, section, orientation, role=None):
if role != Qt.DisplayRole:
return None
if orientation == Qt.Horizontal:
return f'Column {section}'
else:
return f'Row {section}'
def flags(self, index: QModelIndex):
if not index.isValid():
return Qt.ItemIsEnabled
return super().flags(index) | Qt.ItemIsEditable
def setData(self, index: QModelIndex, value, role=None):
if index.isValid() and role == Qt.EditRole:
self.dataArray[index.row()][index.column()] = value
self.dataChanged.emit(index, index, [role])
return True
return False
class DemoA(QMainWindow):
def __init__(self):
super().__init__()
self.init_ui()
def init_ui(self):
data = np.random.randint(1, 100, (4, 6))
model = TableModel(data)
# delegate = ComboBoxDelegate()
table = QTableView()
table.setModel(model)
# table.setItemDelegate(delegate)
self.setCentralWidget(table)
app = QApplication([])
demo = DemoA()
demo.show()
app.exec()
The result:
PyQt5 does not handle numpy data types so it will not display them. In your case, the data stored in the numpy array is numpy.int64, so the solution is to convert it to an integer or float:
if role in [Qt.DisplayRole, Qt.EditRole]:
return int(self.dataArray[index.row()][index.column()])
I'm starting be more an more confused. I cannot make a QTableView emit its signal like I would like. I reduced my case to something less messy, and even in that case I cannot get any signals to be fired when I click.
For example in that code the slot "onClickedRow" is called once when starting the app (I don't know why), but then I can click as much as I want anywhere and the slot is never called :
import sys
from PySide2 import QtWidgets, QtCore, QtGui
class Message(QtCore.QAbstractItemModel):
def __init__(self):
super().__init__()
self.messageList = []
def addMessage(self, typeName, data):
self.messageList.append({"type": typeName,
"data": data})
def data(self, index, role):
if not index.isValid():
return None
if role != QtCore.Qt.DisplayRole:
return None
item = self.messageList[index.row()]
if index.column() == 0:
return str(item["type"])
else:
return str(item["data"])
def headerData(self, section, orientation, role):
if orientation == QtCore.Qt.Horizontal:
if role == QtCore.Qt.DisplayRole:
if section == 0:
return "type"
else:
return "data"
return None
def parent(self, index):
if not index.isValid():
return QtCore.QModelIndex()
return QtCore.QModelIndex()
def index(self, row, column, parent):
if not self.hasIndex(row, column, parent):
return QtCore.QModelIndex()
else:
return self.createIndex(row, column)
def flags(self, index):
if not index.isValid():
return QtCore.Qt.NoItemFlags
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
def columnCount(self, parent):
return 2
def rowCount(self, parent):
return len(self.messageList)
class FormMessageJournal(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.layout = QtWidgets.QVBoxLayout()
self.messageTable = QtWidgets.QTableView(self)
self.messageTable.clicked.connect(self.onClickedRow())
self.messageList = Message()
self.messageList.addMessage("Send", "Hello")
self.messageList.addMessage("Send", "Hello")
self.messageList.addMessage("Send", "Hello")
self.messageList.addMessage("Send", "Hello")
self.messageTable.setModel(self.messageList)
self.layout.addWidget(self.messageTable)
self.setLayout(self.layout)
def onClickedRow(self, index=None):
print("Click !")
if __name__ == "__main__":
app = QtWidgets.QApplication([])
widget = FormMessageJournal()
widget.show()
sys.exit(app.exec_())
Am I the only one having that type of issues?
self.messageTable.clicked.connect(self.onClickedRow())
Change to:
self.messageTable.clicked.connect(self.onClickedRow)
I want to sort a QTableView in PyQT5.
I found an example that uses PyQT4, but in PyQT5 SIGNALs are not existing anymore.
This is my example code
class MainWindow(QWidget):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
# create table
self.get_table_data()
table = self.createTable()
# layout
layout = QVBoxLayout()
layout.addWidget(table)
self.setLayout(layout)
def get_table_data(self):
stdouterr = os.popen("dir c:\\").read()
lines = stdouterr.splitlines()
lines = lines[5:]
lines = lines[:-2]
self.tabledata = [re.split(r"\s+", line, 4)
for line in lines]
def createTable(self):
# create the view
tv = QTableView()
# set the table model
header = ['date', 'time', '', 'size', 'filename']
tm = MyTableModel(self.tabledata, header, self)
tv.setModel(tm)
# set the minimum size
tv.setMinimumSize(400, 300)
# hide grid
tv.setShowGrid(False)
tv.setSelectionBehavior(QAbstractItemView.SelectRows)
# set the font
# hide vertical header
vh = tv.verticalHeader()
vh.setVisible(False)
# set horizontal header properties
hh = tv.horizontalHeader()
hh.setStretchLastSection(True)
# set column width to fit contents
tv.resizeColumnsToContents()
# set row height
nrows = len(self.tabledata)
for row in range(nrows):
tv.setRowHeight(row, 18)
# enable sorting
tv.setSortingEnabled(True)
return tv
self.setWindowTitle("Finance")
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):
return len(self.arraydata[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()"))
if __name__ == '__main__':
from PyQt5.QtWidgets import QApplication
app = QApplication(sys.argv)
helloPythonWidget = MainWindow()
helloPythonWidget.show()
sys.exit(app.exec_())
I tried many different ways to use self.layoutAboutToBeChanged() and pyqtSignal, but to be honest, I don't understand it, since I'm new to python and PyQT in general yet.
I tried to get infos from the Documentation, but i didn't get a clue from documentation and didn't found an good example on the web.
UPDATE:
I solved the puzzle:
self.layoutAboutToBeChanged.emit() emits the signal (codecompletion in eclipse is a bit misleading)
def sort(self, Ncol, order):
"""Sort table by given column number.
"""
self.layoutAboutToBeChanged.emit()
self.arraydata = sorted(self.arraydata, key=operator.itemgetter(Ncol))
if order == Qt.DescendingOrder:
self.arraydata.reverse()
self.layoutChanged.emit()
This is the solution
for those of you guys who wants to import pandas dataframe into qt model try this:
class PandasModel(QtCore.QAbstractTableModel):
def __init__(self, data, parent=None):
"""
:param data: a pandas dataframe
:param parent:
"""
QtCore.QAbstractTableModel.__init__(self, parent)
self._data = data
# self.headerdata = data.columns
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:
return str(self._data.values[index.row()][index.column()])
return None
def headerData(self, rowcol, orientation, role):
# print(self._data.columns[rowcol])
# print(self._data.index[rowcol])
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
return self._data.columns[rowcol]
if orientation == QtCore.Qt.Vertical and role == QtCore.Qt.DisplayRole:
return self._data.index[rowcol]
return None
def flags(self, index):
flags = super(self.__class__, self).flags(index)
flags |= QtCore.Qt.ItemIsEditable
flags |= QtCore.Qt.ItemIsSelectable
flags |= QtCore.Qt.ItemIsEnabled
flags |= QtCore.Qt.ItemIsDragEnabled
flags |= QtCore.Qt.ItemIsDropEnabled
return flags
def sort(self, Ncol, order):
"""Sort table by given column number.
"""
try:
self.layoutAboutToBeChanged.emit()
self._data = self._data.sort_values(self._data.columns[Ncol], ascending=not order)
self.layoutChanged.emit()
except Exception as e:
print(e)
I have forgotten where I got the base pandasmodel, probably from here
I have a QTableView with three columns
The second column is about numbers, there are only three types: 1, -1 and 0.
I want to have different colors for this three "types" of numbers (1,-1,0), coloring their rows with different colors. How can i do it?
self.tableView = QTableView(self.tabSentimento)
self.tableView.setGeometry(QRect(550,10,510,700))
self.tableView.setObjectName(_fromUtf8("TabelaSentimento"))
self.tableView.setModel(self.model)
self.tableView.horizontalHeader().setStretchLastSection(True)
obs: I used horizontalheader().setStrechLastSection(True) because I opened an existing csv file (using a button) into my tableview.
You have to define the color in the model, not in the view:
def data(self, index, role):
...
if role == Qt.BackgroundRole:
return QBrush(Qt.yellow)
Edit:
Here's a working example, except for the color part completely stolen from http://www.saltycrane.com/blog/2007/06/pyqt-42-qabstracttablemodelqtableview/
from PyQt4.QtCore import *
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys
my_array = [['00','01','02'],
['10','11','12'],
['20','21','22']]
def main():
app = QApplication(sys.argv)
w = MyWindow()
w.show()
sys.exit(app.exec_())
class MyWindow(QTableView):
def __init__(self, *args):
QTableView.__init__(self, *args)
tablemodel = MyTableModel(my_array, self)
self.setModel(tablemodel)
class MyTableModel(QAbstractTableModel):
def __init__(self, datain, parent=None, *args):
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 QVariant()
# vvvv this is the magic part
elif role == Qt.BackgroundRole:
if index.row() % 2 == 0:
return QBrush(Qt.yellow)
else:
return QBrush(Qt.red)
# ^^^^ this is the magic part
elif role != Qt.DisplayRole:
return QVariant()
return QVariant(self.arraydata[index.row()][index.column()])
if __name__ == "__main__":
main()