I'm working on data analysis software, which takes data from remote database and puts it into QTableWidget. How could I effectively get these data from table and put them into QChart?
I've seen that if I had been using QTableView, it could have been done with models, but as I understand it, using QTableView would be far more complicated for my scenario.
from PySide2.QtWidgets import *
from PySide2.QtGui import *
from PySide2.QtCore import *
from PySide2.QtCharts import *
import sys
import random
class DateTimeDelegate(QStyledItemDelegate):
def initStyleOption(self, option, index):
super(DateTimeDelegate, self).initStyleOption(option, index)
value = index.data()
option.text =
QDateTime.fromMSecsSinceEpoch(value).toString("dd.MM.yyyy")
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setGeometry(0, 0, 1280, 400)
self.chart_table()
self.populate()
def chart_table(self):
self.table = QTableWidget(0, 2)
delegate = DateTimeDelegate(self.table)
self.table.setItemDelegateForColumn(0, delegate)
chart = QtCharts.QChart()
self.chartView = QtCharts.QChartView(chart)
self.chartView.setFixedSize(600, 430)
splitter = QSplitter(self)
splitter.addWidget(self.table)
splitter.addWidget(self.chartView)
self.setCentralWidget(splitter)
series = QtCharts.QLineSeries(name='Odoslané')
mapper = QtCharts.QVXYModelMapper(xColumn=0, yColumn=2)
mapper.setModel(self.table.model())
mapper.setSeries(series)
chart.addSeries(mapper.series())
self.axis_X = QtCharts.QDateTimeAxis()
self.axis_X.setFormat("MMM yyyy")
self.axis_Y = QtCharts.QValueAxis()
chart.setAxisX(self.axis_X, series)
chart.setAxisY(self.axis_Y, series)
self.axis_Y.setRange(0, 0)
self.axis_Y.setLabelFormat('%.0f')
self.axis_X.setRange(QDate(2017, 10, 1), QDate.currentDate())
chart.setTitle('Chart')
def addRow(self, dt, value):
self.table.insertRow(0)
for col, v in enumerate((dt.toMSecsSinceEpoch(), value)):
it = QTableWidgetItem()
it.setData(Qt.DisplayRole, dt.toMSecsSinceEpoch())
self.table.setItem(0, 0, it)
t_m, t_M = self.axis_X.min(), self.axis_X.max()
t_m = min(t_m, dt)
t_M = max(t_M, dt)
m, M = self.axis_Y.min(), self.axis_Y.max()
m = min(m, value)
M = max(M, value)
In this method I simulate filling table with data as I get them from database.
def populate(self):
for i in range(4):
count=random.randint(1,40)
value_str = QDate.currentDate().addDays(count).toString('dd.MM.yyyy')
dt = QDateTime.fromString(value_str, "dd.MM.yyyy")
sent = QTableWidgetItem(str(count))
value = int(sent.text())
self.addRow(dt, value)
self.table.setItem(0, 1, sent)
And App running function -
def main():
app = QApplication(sys.argv)
gui = MainWindow()
gui.show()
sys.exit(app.exec_())
main()
The easiest way to show the data of a QTableWidget in a QChartView is to use a QVXYModelMapper that relates the model of the QTableWidget with a QLineSerie. But for this the data stored in the QTableWidget should not be a string but an integer so you should not convert the QDateTime to string using toString(), but to an integer using toMSecsSinceEpoch(), and to show it as datetime in the QTableWidget a delegate should be used.
In the following example the addRow method allows to add a (QDateTime, value) to a row, this recalculates the ranges of each axis.
import random
from functools import partial
from PySide2 import QtCore, QtGui, QtWidgets
from PySide2.QtCharts import QtCharts
class DateTimeDelegate(QtWidgets.QStyledItemDelegate):
def initStyleOption(self, option, index):
super(DateTimeDelegate, self).initStyleOption(option, index)
value = index.data()
option.text = QtCore.QDateTime.fromMSecsSinceEpoch(value).toString("dd.MM.yyyy")
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.m_tablewidget = QtWidgets.QTableWidget(0, 2)
delegate = DateTimeDelegate(self.m_tablewidget)
self.m_tablewidget.setItemDelegateForColumn(0, delegate)
self.m_chartview = QtCharts.QChartView()
self.m_chartview.chart().setTheme(QtCharts.QChart.ChartThemeQt)
self.m_chartview.setMinimumWidth(400)
self.m_series = QtCharts.QLineSeries(name="Time-Value")
self.m_mapper = QtCharts.QVXYModelMapper(self, xColumn=0, yColumn=1)
self.m_mapper.setModel(self.m_tablewidget.model())
self.m_mapper.setSeries(self.m_series)
self.m_chartview.chart().addSeries(self.m_mapper.series())
splitter = QtWidgets.QSplitter(self)
splitter.addWidget(self.m_tablewidget)
splitter.addWidget(self.m_chartview)
self.setCentralWidget(splitter)
self.m_time_axis = QtCharts.QDateTimeAxis()
self.m_time_axis.setFormat("dd.MM.yyyy")
self.m_value_axis = QtCharts.QValueAxis()
self.m_chartview.chart().setAxisX(self.m_time_axis, self.m_series)
self.m_chartview.chart().setAxisY(self.m_value_axis, self.m_series)
self.m_value_axis.setRange(0, 0)
self.m_time_axis.setRange(
QtCore.QDateTime.currentDateTime(),
QtCore.QDateTime.currentDateTime().addDays(1),
)
def addRow(self, dt, value):
row = self.m_tablewidget.rowCount()
self.m_tablewidget.insertRow(row)
for col, v in enumerate((dt.toMSecsSinceEpoch(), value)):
it = QtWidgets.QTableWidgetItem()
it.setData(QtCore.Qt.DisplayRole, v)
self.m_tablewidget.setItem(row, col, it)
t_m, t_M = self.m_time_axis.min(), self.m_time_axis.max()
t_m = min(t_m, dt)
t_M = max(t_M, dt)
m, M = self.m_value_axis.min(), self.m_value_axis.max()
m = min(m, value)
M = max(M, value)
self.m_time_axis.setRange(t_m, t_M)
self.m_value_axis.setRange(m, M)
counter = 0
def onTimeout(w):
# Emulate the data
global counter
dt = QtCore.QDateTime.currentDateTime().addDays(counter)
value = random.uniform(-100, 100)
w.addRow(dt, value)
counter += 1
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.resize(640, 480)
w.show()
wrapper = partial(onTimeout, w)
timer = QtCore.QTimer(timeout=wrapper, interval=1000)
timer.start()
sys.exit(app.exec_())
Update:
You do not have to create any QTableWidget in the populate method. I have corrected your logic so that it is added to the top of the QTableWidget, also I have corrected the calculation of the range.
import sys
import random
from PySide2.QtCore import *
from PySide2.QtWidgets import *
from PySide2.QtCharts import QtCharts
class DateTimeDelegate(QStyledItemDelegate):
def initStyleOption(self, option, index):
super(DateTimeDelegate, self).initStyleOption(option, index)
value = index.data()
option.text = QDateTime.fromMSecsSinceEpoch(value).toString("dd.MM.yyyy")
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setGeometry(0, 0, 1280, 400)
self.chart_table()
self.populate()
def chart_table(self):
self.table = QTableWidget(0, 2)
delegate = DateTimeDelegate(self.table)
self.table.setItemDelegateForColumn(0, delegate)
chart = QtCharts.QChart()
self.chartView = QtCharts.QChartView(chart)
self.chartView.setFixedSize(600, 430)
splitter = QSplitter(self)
splitter.addWidget(self.table)
splitter.addWidget(self.chartView)
self.setCentralWidget(splitter)
series = QtCharts.QLineSeries(name="Odoslané")
mapper = QtCharts.QVXYModelMapper(self, xColumn=0, yColumn=1)
mapper.setModel(self.table.model())
mapper.setSeries(series)
chart.addSeries(mapper.series())
self.axis_X = QtCharts.QDateTimeAxis()
self.axis_X.setFormat("MMM yyyy")
self.axis_Y = QtCharts.QValueAxis()
chart.setAxisX(self.axis_X, series)
chart.setAxisY(self.axis_Y, series)
self.axis_Y.setRange(0, 0)
self.axis_Y.setLabelFormat("%.0f")
chart.setTitle("Chart")
def addRow(self, dt, value):
self.table.insertRow(0)
for col, v in enumerate((dt.toMSecsSinceEpoch(), value)):
it = QTableWidgetItem()
it.setData(Qt.DisplayRole, v)
self.table.setItem(0, col, it)
if self.table.rowCount() == 1:
self.axis_X.setRange(dt, dt.addDays(1))
self.axis_Y.setRange(v, v)
else:
t_m, t_M = self.axis_X.min(), self.axis_X.max()
t_m = min(t_m, dt)
t_M = max(t_M, dt)
m, M = self.axis_Y.min(), self.axis_Y.max()
m = min(m, value)
M = max(M, value)
self.axis_X.setRange(t_m, t_M)
self.axis_Y.setRange(m, M)
def populate(self):
for i in range(100):
# simulate filling table with data as I get them from database.
value = random.uniform(1, 40)
fake_dt_str = QDate.currentDate().addDays(i).toString("dd.MM.yyyy")
fake_value_str = str(random.uniform(0, 2))
# Convert simulated data
dt = QDateTime.fromString(fake_dt_str, "dd.MM.yyyy")
value = float(fake_value_str)
self.addRow(dt, value)
def main():
app = QApplication(sys.argv)
gui = MainWindow()
gui.show()
sys.exit(app.exec_())
main()
Related
After looking at some code I found on stackoverflow, I was able to find a way to add a table to a QmessageBox. Now that I have done that, I would like to place a drop down menu in the top right of the QmessageBox and I cannot figure out a way to do that (if it even is possible).
Here is my edited code:
from PyQt4.QtGui import *
from PyQt4.Qt import *
import sys
class MyMessageBox(QMessageBox):
def __init__(self):
QMessageBox.__init__(self)
self.setSizeGripEnabled (True)
self.setWindowTitle('Get Parent Script')
self.setIcon(self.Question)
#self.setText("Hello MessageBox")
self.addButton("Select", QMessageBox.ActionRole)
self.setStandardButtons(QMessageBox.Cancel)
#self.addWidget(QInputDialog())
self.addTableWidget (self)
currentClick = self.exec_()
def addTableWidget (self, parentItem) :
self.l = QVBoxLayout()
self.tableWidget = QTableWidget(parentItem)
self.tableWidget.setObjectName ('tableWidget')
self.tableWidget.setColumnCount(3)
self.tableWidget.setRowCount(2)
self.tableWidget.setHorizontalHeaderLabels(QString("Nuke Script;File Modification Time;User").split(";"))
header = self.tableWidget.horizontalHeader()
header.setResizeMode(0, QHeaderView.ResizeToContents)
header.setResizeMode(1, QHeaderView.Stretch)
header.setResizeMode(2, QHeaderView.Stretch)
stringlist = {u'/SEQ/ZZ/ZZ_012_001/Comp/nuke/scripts/comp':u'user1', u'/SEQ/ZZ/ZZ_012_001/Comp/nuke/scripts/comp/hello': u'user2'}
row = 0
for key, value in stringlist.iteritems():
print key, value
nameitem = QTableWidgetItem(str(key))
codeitem = QTableWidgetItem(str(value))
self.tableWidget.setItem(row,0,nameitem)
self.tableWidget.setItem(row,1,codeitem)
row +=1
self.tableWidget.resize(1000, 170)
self.l.addWidget(self.tableWidget)
self.setLayout(self.l)
def event(self, e):
result = QMessageBox.event(self, e)
self.setMinimumWidth(0)
self.setMaximumWidth(16777215)
self.setMinimumHeight(0)
self.setMaximumHeight(16777215)
self.setSizePolicy(
QSizePolicy.Expanding,
QSizePolicy.Expanding
)
self.resize(1000, 300)
return result
def run_cli():
#app = QtWidgets.QApplication(sys.argv)
app = QApplication(sys.argv)
MyMessageBox()
if __name__ == '__main__':
run_cli()
In your case it is not optimal to use QMessageBox since I involve unnecessary work because this widget already has a predefined layout, instead you can create a widget based on a QDialog:
from PyQt4 import QtCore, QtGui
class Dialog(QtGui.QDialog):
def __init__(self, parent=None):
super(Dialog, self).__init__(parent)
label = QtGui.QLabel("Text")
combo = QtGui.QComboBox()
combo.addItems(["option1", "option2", "option3"])
self.tableWidget = QtGui.QTableWidget(2, 3)
self.tableWidget.setHorizontalHeaderLabels(
QtCore.QString("Nuke Script;File Modification Time;User").split(";")
)
header = self.tableWidget.horizontalHeader()
header.setResizeMode(0, QtGui.QHeaderView.ResizeToContents)
header.setResizeMode(1, QtGui.QHeaderView.Stretch)
header.setResizeMode(2, QtGui.QHeaderView.Stretch)
stringlist = {
u"/SEQ/ZZ/ZZ_012_001/Comp/nuke/scripts/comp": u"user1",
u"/SEQ/ZZ/ZZ_012_001/Comp/nuke/scripts/comp/hello": u"user2",
}
for row, (key, value) in enumerate(stringlist.iteritems()):
nameitem = QtGui.QTableWidgetItem(str(key))
codeitem = QtGui.QTableWidgetItem(str(value))
self.tableWidget.setItem(row, 0, nameitem)
self.tableWidget.setItem(row, 1, codeitem)
box = QtGui.QDialogButtonBox(
QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel,
centerButtons=True,
)
box.accepted.connect(self.accept)
box.rejected.connect(self.reject)
lay = QtGui.QGridLayout(self)
lay.addWidget(label, 0, 0)
lay.addWidget(combo, 0, 1)
lay.addWidget(self.tableWidget, 1, 0, 1, 2)
lay.addWidget(box, 2, 0, 1, 2)
self.resize(640, 240)
def run_cli():
import sys
app = QtGui.QApplication(sys.argv)
w = Dialog()
w.exec_()
if __name__ == "__main__":
run_cli()
I have a QTableWidget in my program. I want it's rows and columns to change color according to the time entry in the table widget. When I first add the time, it's compared to current system time and the color changing does take effect but as time goes by it doesn't keep up and the colors remain the same. I want it to keep updating table item's color. I cant include all the code as it is too long but I have included the relevant part.
#Operations_Tree is the QTABLEWIDGET
def Scheduler_Loop(self):
Timer = QtCore.QTimer(self)
Timer.start(1000)
Timer.timeout.connect(self.test)
def test(self):
time = QtCore.QTime.currentTime()
current_time_text = time.toString('hh:mm:ss')
for row in range(0,self.Operations_Tree.rowCount()):
time = self.Operations_Tree.item(row,1)
time_text = time.text()
#print(time_text)
if time_text >= current_time_text :
for column in range(0,6):
#print("TEST")
try:
table_widget_item = self.Operations_Tree.item(row, column)
table_widget_item.setForeground(QtGui.QColor(0,0,255)) #BLue
table_widget_item.setBackground(QtGui.QColor(238,233,233)) #
table_widget_item.viewport.update()
except AttributeError:
pass
else:
for column in range(0,6):
#print("TEST")
try:
table_widget_item = self.Operations_Tree.item(row, column)
#table_widget_item.setForeground(QtGui.QColor(0,255,0)) #
table_widget_item.setBackground(QtGui.QColor(238,233,233)) #
table_widget_item.viewport.update()
except AttributeError:
pass
self.Operations_Tree.update()
here is a snap of my program:
For me the main problem is that you are comparing strings instead of time, also in Qt it is not necessary to use try-except since many times they hide errors and waste unnecessary resources, it is better to verify.
PyQt5:
from PyQt5 import QtCore, QtGui, QtWidgets
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self.lcd_number = QtWidgets.QLCDNumber()
self.lcd_number.setFrameShape(QtWidgets.QFrame.NoFrame)
self.lcd_number.setFixedHeight(100)
self.lcd_number.setDigitCount(8)
self.Operations_Tree = QtWidgets.QTableWidget()
self.Operations_Tree.setColumnCount(6)
labels = ["On AIR", "TIME", "ITEM", "DURATION", "B.ITEM", "B.I.DURATION"]
self.Operations_Tree.setHorizontalHeaderLabels(labels)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(self.lcd_number)
lay.addWidget(self.Operations_Tree)
for i, t in enumerate(("10:10:10", "12:12:12")):
self.Operations_Tree.insertRow(self.Operations_Tree.rowCount())
it = QtWidgets.QTableWidgetItem(t)
self.Operations_Tree.setItem(i, 1, it)
self.resize(640, 480)
self.Scheduler_Loop()
def Scheduler_Loop(self):
Timer = QtCore.QTimer(self)
Timer.timeout.connect(self.test)
Timer.start(1000)
self.test()
#QtCore.pyqtSlot()
def test(self):
time = QtCore.QTime.currentTime()
self.lcd_number.display(time.toString())
for row in range(self.Operations_Tree.rowCount()):
it = self.Operations_Tree.item(row, 1)
r_time = QtCore.QTime.fromString(it.text(), "h:mm:ss")
flag = r_time >= time
f_color = QtGui.QColor(0, 0, 255) if flag else QtGui.QColor(0, 255, 0)
b_color = QtGui.QColor(238, 233, 233) if flag else QtGui.QColor(238,233,233)
for column in range(0, 6):
it = self.Operations_Tree.item(row, column)
if it is None:
it = QtWidgets.QTableWidgetItem()
self.Operations_Tree.setItem(row, column, it)
it.setForeground(f_color)
it.setBackground(b_color)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
PyQt4:
from PyQt4 import QtCore, QtGui
class Widget(QtGui.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self.lcd_number = QtGui.QLCDNumber()
self.lcd_number.setFrameShape(QtGui.QFrame.NoFrame)
self.lcd_number.setFixedHeight(100)
self.lcd_number.setDigitCount(8)
self.Operations_Tree = QtGui.QTableWidget()
self.Operations_Tree.setColumnCount(6)
labels = ["On AIR", "TIME", "ITEM", "DURATION", "B.ITEM", "B.I.DURATION"]
self.Operations_Tree.setHorizontalHeaderLabels(labels)
lay = QtGui.QVBoxLayout(self)
lay.addWidget(self.lcd_number)
lay.addWidget(self.Operations_Tree)
for i, t in enumerate(("10:10:10", "12:12:12")):
self.Operations_Tree.insertRow(self.Operations_Tree.rowCount())
it = QtGui.QTableWidgetItem(t)
self.Operations_Tree.setItem(i, 1, it)
self.resize(640, 480)
self.Scheduler_Loop()
def Scheduler_Loop(self):
Timer = QtCore.QTimer(self)
Timer.timeout.connect(self.test)
Timer.start(1000)
self.test()
#QtCore.pyqtSlot()
def test(self):
time = QtCore.QTime.currentTime()
self.lcd_number.display(time.toString())
for row in range(self.Operations_Tree.rowCount()):
it = self.Operations_Tree.item(row, 1)
r_time = QtCore.QTime.fromString(it.text(), "h:mm:ss")
flag = r_time >= time
f_color = QtGui.QColor(0, 0, 255) if flag else QtGui.QColor(0, 255, 0)
b_color = QtGui.QColor(238, 233, 233) if flag else QtGui.QColor(238,233,233)
for column in range(0, 6):
it = self.Operations_Tree.item(row, column)
if it is None:
it = QtGui.QTableWidgetItem()
self.Operations_Tree.setItem(row, column, it)
it.setForeground(f_color)
it.setBackground(b_color)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
At present, for this project, I'm working with matplolib to trace my plot.
Nevertheless, I wish to convert at pyqtgraph ( Pyqt5).
I didn't found how trace a discret plot of FFT with pyqtgraph, same the picture.
Have you an idea ?
You can create an item in which the vertical lines are drawn:
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg
class LinePlot(pg.GraphicsObject):
def __init__(self, data):
super(LinePlot, self).__init__()
self._data = []
self.setData(data)
def setData(self, data):
self._data = data
self.generate()
def generate(self):
self.picture = QtGui.QPicture()
p = QtGui.QPainter(self.picture)
p.setPen(pg.mkPen('r', width=2))
for (t, v) in self._data:
if v != 0:
p.drawLine(QtCore.QPointF(t, 0), QtCore.QPointF(t, v))
def paint(self, p, *args):
p.drawPicture(0, 0, self.picture)
def boundingRect(self):
return QtCore.QRectF(self.picture.boundingRect())
if __name__ == '__main__':
import sys
import random
app = QtGui.QApplication(sys.argv)
w = QtGui.QMainWindow()
view = pg.GraphicsLayoutWidget()
w.setCentralWidget(view)
plot = view.addPlot()
vals = [(i, random.randint(0, 255)) for i in range(30)]
plot.addItem(LinePlot(vals))
w.show()
sys.exit(app.exec_())
I'll start by explaining my goal. An e-commerce order containing several products are loaded into a qtableview. The user of the program will scan the ean codes of the products and if the ean code exist in the qtableview, the row should change colour to green or yellow. If the quantity of one product is greater than 1, the colour should turn yellow untill the quantity of scanned products equal the quantity in the order,
The overall goal is to make a quick and easy way to make sure correct products are placed in the correct order.
I have found a lot of answers of how to change colours of row permanently, but not how to change colours based on user input and changing values in the model.
Example of what i want to achieve.
There are the following alternatives:
QIdentityProxyModel: You must overwrite the data method and you calculate the color using the values, and if the color is different from the preset, set the background color to the original model.
import sys
import random
from functools import partial
from PyQt5 import QtCore, QtGui, QtWidgets
def calculate_color(model, row):
max_value = int(model.index(row, 2).data())
current_value = int(model.index(row, 3).data())
if current_value == 0:
return QtGui.QBrush(QtCore.Qt.white)
elif max_value == current_value:
return QtGui.QBrush(QtCore.Qt.green)
else:
return QtGui.QBrush(QtCore.Qt.yellow)
class IdentityProxyModel(QtCore.QIdentityProxyModel):
def data(self, index, role=QtCore.Qt.DisplayRole):
if role == QtCore.Qt.DisplayRole and index.column() in (2, 3):
sm = self.sourceModel()
row = index.row()
color = calculate_color(sm, row)
if color is not None and color != index.data(QtCore.Qt.BackgroundRole):
for i in range(sm.columnCount()):
sm.setData(sm.index(row, i), color, QtCore.Qt.BackgroundRole)
return super(IdentityProxyModel, self).data(index, role)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.tableView = QtWidgets.QTableView()
self.setCentralWidget(self.tableView)
self.model = QtGui.QStandardItemModel()
self.model.setHorizontalHeaderLabels(["Prod Name", "EAN","Quanyity", "Counted"])
proxy = IdentityProxyModel(self)
proxy.setSourceModel(self.model)
self.tableView.setModel(proxy)
data = [["Prod1", "123456", 0, 0],
["Prod2", "234567", 0, 0],
["Prod3", "345678", 0, 0]]
for r, rowData in enumerate(data):
for c, d in enumerate(rowData):
it = QtGui.QStandardItem(str(d))
self.model.setItem(r, c, it)
# launch test
for i in range(self.model.rowCount()):
self.reset(i)
def reset(self, row):
max_value = random.randint(1, 10)
self.model.item(row, 2).setText(str(max_value))
self.model.item(row, 3).setText("0")
QtCore.QTimer.singleShot(1000, partial(self.start_test, row))
def start_test(self, row):
max_value = int(self.model.item(row, 2).text())
time_line = QtCore.QTimeLine(1000*max_value, self)
time_line.setFrameRange(0, max_value)
time_line.frameChanged.connect(partial(self.update_value, row))
# reset after 3 seconds of completion
time_line.finished.connect(lambda r=row: QtCore.QTimer.singleShot(3000, partial(self.reset, r)))
time_line.start()
def update_value(self, r, i):
model = self.tableView.model()
ix = model.index(r, 3)
model.setData(ix, str(i))
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())
Delegate: You must overwrite the paint method as it is called when there is a change in the model and calculate the color using the values, and if the color is different from the preset, set the background color to the original model.
import sys
import random
from functools import partial
from PyQt5 import QtCore, QtGui, QtWidgets
def calculate_color(model, row):
max_value = int(model.index(row, 2).data())
current_value = int(model.index(row, 3).data())
if current_value == 0:
return QtGui.QBrush(QtCore.Qt.white)
elif max_value == current_value:
return QtGui.QBrush(QtCore.Qt.green)
else:
return QtGui.QBrush(QtCore.Qt.yellow)
class ColorDelegate(QtWidgets.QStyledItemDelegate):
def paint(self, painter, option, index):
if index.column() in (2, 3):
model = index.model()
r = index.row()
color = calculate_color(model, r)
if color != index.data(QtCore.Qt.BackgroundRole):
for i in range(model.columnCount()):
model.setData(model.index(r, i), color, QtCore.Qt.BackgroundRole)
super(ColorDelegate, self).paint(painter, option, index)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.tableView = QtWidgets.QTableView()
self.setCentralWidget(self.tableView)
self.model = QtGui.QStandardItemModel()
self.model.setHorizontalHeaderLabels(["Prod Name", "EAN","Quanyity", "Counted"])
self.tableView.setModel(self.model)
self.tableView.setItemDelegate(ColorDelegate(self))
data = [["Prod1", "123456", 0, 0],
["Prod2", "234567", 0, 0],
["Prod3", "345678", 0, 0]]
for r, rowData in enumerate(data):
for c, d in enumerate(rowData):
it = QtGui.QStandardItem(str(d))
self.model.setItem(r, c, it)
# launch test
for i in range(self.model.rowCount()):
self.reset(i)
def reset(self, row):
max_value = random.randint(1, 10)
self.model.item(row, 2).setText(str(max_value))
self.model.item(row, 3).setText("0")
QtCore.QTimer.singleShot(1000, partial(self.start_test, row))
def start_test(self, row):
max_value = int(self.model.item(row, 2).text())
time_line = QtCore.QTimeLine(1000*max_value, self)
time_line.setFrameRange(0, max_value)
time_line.frameChanged.connect(partial(self.update_value, row))
# reset after 3 seconds of completion
time_line.finished.connect(lambda r=row: QtCore.QTimer.singleShot(3000, partial(self.reset, r)))
time_line.start()
def update_value(self, r, i):
model = self.tableView.model()
ix = model.index(r, 3)
model.setData(ix, str(i))
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())
I want to create a table in PyQt5 that has a combobox in each column header. When I try to do it, the following error is returned:
TypeError: setHorizontalHeaderItem(self, int, QTableWidgetItem): argument 2 has unexpected type 'QComboBox'
Apparently the function setHorizontalHeaderItem() doesn't accept widgets as items. So is there a way to achieve this? If not, I would settle with putting the comboboxes above the headers, but they should be aligned with the size of each column, even if the user changes the width with the mouse. I don't know if this is possible either.
My code:
from PyQt5 import QtWidgets
import numpy as np
class App(QtWidgets.QWidget):
def __init__(self):
super(App,self).__init__()
self.data = np.random.rand(5,5)
self.createTable()
self.layout = QtWidgets.QVBoxLayout()
self.layout.addWidget(self.table)
self.setLayout(self.layout)
self.showMaximized()
def createTable(self):
self.header = []
self.table = QtWidgets.QTableWidget(len(self.data), len(self.data[0]))
for i in range(len(self.data[0])):
self.header.append(QtWidgets.QComboBox())
self.header[-1].addItem('Variable')
self.header[-1].addItem('Timestamp')
self.table.setHorizontalHeaderItem(i,self.header[-1])
for i in range(len(self.data)):
for j in range(len(self.data[0])):
self.table.setItem(i,j,QtWidgets.QTableWidgetItem(str(self.data[i][j])))
if __name__ == '__main__':
app = QtWidgets.QApplication([])
ex = App()
app.exec_()
QHeaderView does not support widgets as items so you must create a custom header as I show below:
from PyQt5 import QtCore, QtWidgets
import numpy as np
class HorizontalHeader(QtWidgets.QHeaderView):
def __init__(self, values, parent=None):
super(HorizontalHeader, self).__init__(QtCore.Qt.Horizontal, parent)
self.setSectionsMovable(True)
self.comboboxes = []
self.sectionResized.connect(self.handleSectionResized)
self.sectionMoved.connect(self.handleSectionMoved)
def showEvent(self, event):
for i in range(self.count()):
if i < len(self.comboboxes):
combo = self.comboboxes[i]
combo.clear()
combo.addItems(["Variable", "Timestamp"])
else:
combo = QtWidgets.QComboBox(self)
combo.addItems(["Variable", "Timestamp"])
self.comboboxes.append(combo)
combo.setGeometry(self.sectionViewportPosition(i), 0, self.sectionSize(i)-4, self.height())
combo.show()
if len(self.comboboxes) > self.count():
for i in range(self.count(), len(self.comboboxes)):
self.comboboxes[i].deleteLater()
super(HorizontalHeader, self).showEvent(event)
def handleSectionResized(self, i):
for i in range(self.count()):
j = self.visualIndex(i)
logical = self.logicalIndex(j)
self.comboboxes[i].setGeometry(self.sectionViewportPosition(logical), 0, self.sectionSize(logical)-4, self.height())
def handleSectionMoved(self, i, oldVisualIndex, newVisualIndex):
for i in range(min(oldVisualIndex, newVisualIndex), self.count()):
logical = self.logicalIndex(i)
self.comboboxes[i].setGeometry(self.ectionViewportPosition(logical), 0, self.sectionSize(logical) - 5, height())
def fixComboPositions(self):
for i in range(self.count()):
self.comboboxes[i].setGeometry(self.sectionViewportPosition(i), 0, self.sectionSize(i) - 5, self.height())
class TableWidget(QtWidgets.QTableWidget):
def __init__(self, *args, **kwargs):
super(TableWidget, self).__init__(*args, **kwargs)
header = HorizontalHeader(self)
self.setHorizontalHeader(header)
def scrollContentsBy(self, dx, dy):
super(TableWidget, self).scrollContentsBy(dx, dy)
if dx != 0:
self.horizontalHeader().fixComboPositions()
class App(QtWidgets.QWidget):
def __init__(self):
super(App,self).__init__()
self.data = np.random.rand(10, 10)
self.createTable()
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.table)
self.showMaximized()
def createTable(self):
self.header = []
self.table = TableWidget(*self.data.shape)
for i, row_values in enumerate(self.data):
for j, value in enumerate(row_values):
self.table.setItem(i, j, QtWidgets.QTableWidgetItem(str(value)))
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())