I have to create one or more displayboards that will contain smaller tables as you can see from the photos.
Image with 10 names - unusable:
Image with 6 names - 5 are missing:
The problem is that I can't get either a display board extended in length with a scrollbar or two or more displayboards (it depends on how many names are to be entered). I have tried various systems but in the case of "long displayboard" even varying the values in window.setGeometry (QtCore.QRect (0, 0, 980, 620)) I can't get the scrollbar, in the case of more displayboards I just can't manage get nothing.
This is the starting piece of code that I used to get the code to work
from functions import *
from PyQt5 import QtCore, QtWidgets
class SpecialStyledItemDelegate(QtWidgets.QStyledItemDelegate):
def __init__(self, parent=None):
super().__init__(parent)
self._values = dict()
def add_text(self, text, row):
self._values[row] = text
def initStyleOption(self, option, index):
super().initStyleOption(option, index)
row = index.row()
if row in self._values:
option.text = self._values[row]
option.displayAlignment = QtCore.Qt.AlignCenter
class MakeStrip(QtWidgets.QWidget):
def __init__(self, anno, mese):
super().__init__()
self.initUI(anno, mese)
def initUI(self, anno, mese):
self.title = "MAMbo - Strips di '%s' '%s' " % (mese, anno)
self.setWindowTitle(self.title)
self.setGeometry(50, 100, 250, 800)
self.callTable(anno, mese)
self.Close_btn = QtWidgets.QPushButton(self)
self.Close_btn.setGeometry(QtCore.QRect(1000, 570, 70, 20))
self.Close_btn.setStyleSheet("background-color : yellow")
self.Close_btn.setObjectName("Strip_Add_btn")
_translate = QtCore.QCoreApplication.translate
self.Close_btn.setText(_translate("Library_main", "Close"))
self.Close_btn.clicked.connect(self.close)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.tableWidget)
layout.addWidget(self.Close_btn)
def callTable(self, anno, mese):
anno = "2021"
self.alphamese = "January"
# Create table
self.tableWidget = QtWidgets.QTableWidget()
self.tableWidget.setRowCount(10)
self.tableWidget.setColumnCount(3)
self.special_delegate = SpecialStyledItemDelegate()
self.tableWidget.setItemDelegate(self.special_delegate)
self.special_delegate.add_text("JOE DOE", 0)
self.special_delegate.add_text("January", 1)
h_header = self.tableWidget.horizontalHeader()
h_header.hide()
for i in range(h_header.count()):
h_header.setSectionResizeMode(i, QtWidgets.QHeaderView.ResizeToContents)
v_header = self.tableWidget.verticalHeader()
v_header.hide()
v_header.setDefaultSectionSize(13)
self.tableWidget.setSpan(1, 0, 1, 3)
self.tableWidget.setSpan(0, 0, 1, 3)
for zz in range(1, 10):
dow = "do{}".format(zz)
value = "AAAAAAAA{}".format(zz)
self.tableWidget.setItem(zz + 1, 0, QtWidgets.QTableWidgetItem(str(zz)))
self.tableWidget.setItem(zz + 1, 1, QtWidgets.QTableWidgetItem(dow))
item = QtWidgets.QTableWidgetItem(value)
item.setTextAlignment(QtCore.Qt.AlignCenter)
self.tableWidget.setItem(zz + 1, 2, item)
def close (self):
Functions.error_handler(message="message")
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
anno = "2021"
mese = "January"
ex = MakeStrip(anno, mese)
ex.show()
sys.exit(app.exec_())
Below, the job code
import sys
from PyQt5 import QtWidgets
from PyQt5 import QtCore
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QMainWindow, QApplication, QWidget, QAction, QTableWidget, QTableWidgetItem, QVBoxLayout
from functions import *
from index import *
class SpecialStyledItemDelegate(QtWidgets.QStyledItemDelegate):
def __init__(self, parent=None):
super().__init__(parent)
self._values = dict()
def add_text(self, text, row):
self._values[row] = text
def initStyleOption(self, option, index):
super().initStyleOption(option, index)
row = index.row()
if row in self._values:
option.text = self._values[row]
option.displayAlignment = QtCore.Qt.AlignCenter
class Make_Strip():
def MakeStrip(self, window: QWindow, anno, mese, cursor):
global table
layout = QHBoxLayout()
self.alphamese = Functions.convert_number_month[mese]
cursor = Functions.openDB(self)
query = "SELECT * FROM `alldata` WHERE anno = '%s' AND mese = '%s' " % (anno, mese)
self.cursor.execute(query)
search = self.cursor.fetchall()
#numrows = self.cursor.rowcount()
cnt= 0
x_ax = 10
for row in search:
if cnt == 6: break # This is to have something useful (picture 2)
self.vol_name = row['nome']
self.c_query = "SELECT grp FROM volontari WHERE cognome = '%s'" % (self.vol_name)
self.cursor.execute(self.c_query)
self.grp = self.cursor.fetchall()
for val in self.grp:
self.group = val['grp']
if self.group == 'C':
table = QTableWidget()
table.setGeometry(QtCore.QRect(x_ax, 5, 150, 597))
table.setObjectName("StripTable")
table.setRowCount(33)
table.setColumnCount(3)
self.special_delegate = SpecialStyledItemDelegate()
table.setItemDelegate(self.special_delegate)
h_header = table.horizontalHeader()
h_header.hide()
for i in range(h_header.count()):
h_header.setSectionResizeMode(i, QtWidgets.QHeaderView.ResizeToContents)
v_header = table.verticalHeader()
v_header.hide()
v_header.setDefaultSectionSize(18)
table.setSpan(1, 0, 1, 3)
table.setSpan(0, 0, 1, 3)
zz = 1
self.m_query = "SELECT * FROM `alldata` WHERE anno = '%s' AND mese = '%s' AND nome = '%s'" %(anno, mese, self.vol_name)
self.cursor.execute(self.m_query)
result = self.cursor.fetchall()
self.special_delegate.add_text(self.vol_name, 0)
self.special_delegate.add_text(self.alphamese, 1)
daysinmonth = calendar.monthrange(int(anno), int(mese))[1]
while zz <= daysinmonth:
for inrow in result:
day = 'd' + str(zz)
self.value = inrow[day]
a_date = datetime.date(int(anno), int(mese), zz)
self.dow = a_date.strftime("%a")
self.dow = Functions.convert_en_it[self.dow]
if (self.dow == 'Sun' or self.dow == 'Sat') and (self.value == 'P' or self.value == 'D' or self.value == 'F' or self.value == 'B'):
self.value = Functions.eva_mp1[self.value]
else: self.value = Functions.eva_mp[self.value]
table.setItem(zz+1, 0, QtWidgets.QTableWidgetItem(str(zz)))
table.setItem(zz+1, 1, QtWidgets.QTableWidgetItem(self.dow))
item = QtWidgets.QTableWidgetItem(self.value)
item.setTextAlignment(QtCore.Qt.AlignCenter)
table.setItem(zz+1, 2, item)
zz += 1
#table.show()
cnt = cnt + 1
x_ax = x_ax + 150
layout.addWidget(table)
pippo = 0
window.setGeometry(QtCore.QRect(0, 0, 980, 620))
window.setLayout(layout)
window.show()
Related
I have 2 widgets inside of MainWindow, one of which is the main chart that will cover the top 1/3 of the page (StockChart) but the other widget needs to be made up of 4 graphs equally split up on the bottom 2/3 of the page (ExtraCharts), the problem is I'm having a hard time getting them to show up and even run without errors, here is the current code:
import sys
from PySide6.QtCore import *
from PySide6.QtWidgets import *
from PySide6.QtGui import *
from PySide6.QtCharts import *
from sqlite import MySql
class MainWindow(QMainWindow):
def __init__(self, db, parent=None):
super(MainWindow, self).__init__(parent)
self.setWindowState(Qt.WindowMaximized)
self.setWindowTitle("Indicator Tool")
self.db = db
self.data = db.view_chart_data("5m")
self.stockWidget = StockChart(self)
self.extraCharts = ExtraCharts(self)
self.initUI()
def initUI(self):
grid = QGridLayout()
grid.setRowStretch(0, 1)
grid.setRowStretch(1, 2)
#Widget, row, column, rowspan, colspan
grid.addWidget(self.stockWidget.chartView, 0, 0, 2, 2)
grid.addWidget(self.extraCharts, 1, 0, 2, 2)
centralWidget = QWidget()
centralWidget.setLayout(grid)
self.setCentralWidget(centralWidget)
class StockChart(QWidget):
def __init__(self, parent):
super(StockChart, self).__init__(parent)
self.data = parent.data
self.drawChart()
def drawChart(self):
series = QCandlestickSeries()
for row in self.data:
time = row[1] * 1000
open = row[2]
high = row[3]
low = row[4]
close = row[5]
volume = row[6]
series.append(QCandlestickSet(open, high, low, close, time))
self.chart = QChart()
self.chart.legend().hide()
self.chart.addSeries(series)
self.chart.createDefaultAxes()
self.chart.setTitle('/ES Candlestick Chart')
self.chartView = QChartView(self.chart)
self.chartView.setRenderHint(QPainter.Antialiasing)
class ExtraCharts(QWidget):
def __init__(self, parent):
super(ExtraCharts, self).__init__(parent)
self.data = parent.data
self.drawChart()
def drawChart(self):
series = QCandlestickSeries()
for row in self.data:
time = row[1] * 1000
open = row[2]
high = row[3]
low = row[4]
close = row[5]
volume = row[6]
series.append(QCandlestickSet(open, high, low, close, time))
chart_1 = QChart()
chart_1.legend().hide()
chart_1.addSeries(series)
chart_1.createDefaultAxes()
chart_1.setTitle('Chart 1')
chart_2 = QChart()
chart_2.legend().hide()
chart_2.addSeries(series)
chart_2.createDefaultAxes()
chart_2.setTitle('Chart 2')
chart_3 = QChart()
chart_3.legend().hide()
chart_3.addSeries(series)
chart_3.createDefaultAxes()
chart_3.setTitle('Chart 3')
chart_4 = QChart()
chart_4.legend().hide()
chart_4.addSeries(series)
chart_4.createDefaultAxes()
chart_4.setTitle('Chart 4')
chartView_1 = QChartView(chart_1)
chartView_1.setRenderHint(QPainter.Antialiasing)
chartView_2 = QChartView(chart_2)
chartView_2.setRenderHint(QPainter.Antialiasing)
chartView_3 = QChartView(chart_3)
chartView_3.setRenderHint(QPainter.Antialiasing)
chartView_4 = QChartView(chart_4)
chartView_4.setRenderHint(QPainter.Antialiasing)
grid = QGridLayout()
# grid.setRowStretch(0, 1)
# grid.setRowStretch(1, 2)
#Widget, row, column, rowspa, colspan
grid.addWidget(chartView_1, 0, 0)
grid.addWidget(chartView_2, 0, 1)
grid.addWidget(chartView_3, 1, 0)
grid.addWidget(chartView_4, 1, 1)
chart_1.show()
chart_2.show()
chart_3.show()
chart_4.show()
if __name__ == "__main__":
app = QApplication([])
db = MySql()
window = MainWindow(db)
window.show()
sys.exit(app.exec())
and as of right now im getting this error:
"Can not find axis on the chart."
Segmentation fault (core dumped)
I know my method of doing this is probably wrong.
The problem is that a QXSerie can only be part of a QChart, and in your case you are creating violating that rule.
On the other hand, it is not good to access parent properties such as data, instead it is better to make the method receive the data.
import sys
from functools import cached_property
from PySide6.QtCore import Qt
from PySide6.QtWidgets import (
QApplication,
QGridLayout,
QMainWindow,
QVBoxLayout,
QWidget,
)
from PySide6.QtGui import QPainter
from PySide6.QtCharts import QCandlestickSeries, QCandlestickSet, QChart, QChartView
from sqlite import MySql
class MainWindow(QMainWindow):
def __init__(self, db, parent=None):
super(MainWindow, self).__init__(parent)
self.setWindowState(Qt.WindowMaximized)
self.setWindowTitle("Indicator Tool")
self.db = db
data = db.view_chart_data("5m")
self.stockWidget = StockChart()
self.extraCharts = ExtraCharts()
self.stockWidget.drawChart(data)
self.extraCharts.drawChart(data)
self.initUI()
def initUI(self):
grid = QGridLayout()
grid.setRowStretch(0, 1)
grid.setRowStretch(1, 2)
grid.addWidget(self.stockWidget, 0, 0, 2, 2)
grid.addWidget(self.extraCharts, 1, 0, 2, 2)
centralWidget = QWidget()
centralWidget.setLayout(grid)
self.setCentralWidget(centralWidget)
class StockChart(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
lay = QVBoxLayout(self)
lay.addWidget(self.chart_view)
#cached_property
def chart_view(self):
chart = QChart()
chart.legend().hide()
chart.createDefaultAxes()
chart.setTitle("/ES Candlestick Chart")
view = QChartView(chart)
view.setRenderHint(QPainter.Antialiasing)
return view
def drawChart(self, data):
series = QCandlestickSeries()
for row in data:
time = row[1] * 1000
open = row[2]
high = row[3]
low = row[4]
close = row[5]
volume = row[6]
series.append(QCandlestickSet(open, high, low, close, time))
self.chart_view.chart().addSeries(series)
class ExtraCharts(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.init_ui()
#cached_property
def views(self):
return list()
def init_ui(self):
grid = QGridLayout(self)
positions = (
(0, 0, "Chart 1"),
(0, 1, "Chart 2"),
(1, 0, "Chart 3"),
(1, 1, "Chart 4"),
)
for (row, column, title) in positions:
view = QChartView()
view.setRenderHint(QPainter.Antialiasing)
view.chart().setTitle(title)
view.chart().legend().hide()
view.chart().createDefaultAxes()
grid.addWidget(view, row, column)
self.views.append(view)
def drawChart(self, data):
for view in self.views:
series = QCandlestickSeries()
view.chart().addSeries(series)
for row in data:
time = row[1] * 1000
open = row[2]
high = row[3]
low = row[4]
close = row[5]
volume = row[6]
series.append(QCandlestickSet(open, high, low, close, time))
if __name__ == "__main__":
app = QApplication([])
db = MySql()
window = MainWindow(db)
window.show()
sys.exit(app.exec())
Below is the example code:
import sys
from PyQt5 import QtCore, QtWidgets, QtSql, uic
class FilterProxyModel(QtCore.QSortFilterProxyModel):
def __init__(self, parent=None):
super().__init__(parent)
self._filter_value = None
#property
def filter_value(self):
return self._filter_value
#filter_value.setter
def filter_value(self, value):
self._filter_value = value
self.invalidateFilter()
def filterAcceptsRow(self, sourceRow, sourceParent):
if self.filter_value is None:
return super().filterAcceptsRow(sourceRow, sourceParent)
if self.filterKeyColumn() >= 0:
value = (
self.sourceModel()
.index(sourceRow, self.filterKeyColumn(), sourceParent)
.data(self.filterRole())
)
return value == self.filter_value
for column in range(self.columnCount()):
value = (
self.sourceModel()
.index(sourceRow, column, sourceParent)
.data(self.filterRole())
)
if value == self.filter_value:
return True
return False
def setFilterRegExp(self, filter):
self.filter_value = None
super().setFilterRegExp(filter)
class UI(QtWidgets.QMainWindow):
def __init__(self):
super(UI, self).__init__()
uic.loadUi("tableview.ui", self)
self.tableView.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
db = QtSql.QSqlDatabase.addDatabase("QSQLITE")
db.setDatabaseName("book.db")
db.open()
self.model = QtSql.QSqlTableModel(self)
self.model.setTable("card")
self.model.select()
self.proxy = FilterProxyModel(self)
self.proxy.setSourceModel(self.model)
self.tableView.setModel(self.proxy)
self.model.select()
self.edit.clicked.connect(self.edit_items)
self.refresh.clicked.connect(self.refresh_table)
r = self.model.record()
column_names = [r.field(i).name().title() for i in range(r.count())]
self.comboBox.addItems([x for x in column_names])
self.horizontalHeader = self.tableView.horizontalHeader()
self.horizontalHeader.sectionClicked.connect(
self.tableView_horizontalHeader_sectionClicked
)
self.lineEdit.textChanged.connect(self.lineEdit_textChanged)
def tableView_horizontalHeader_sectionClicked(self, logicalIndex):
menu = QtWidgets.QMenu(self)
values = []
for row in range(self.model.rowCount()):
value = self.model.index(row, logicalIndex).data(self.proxy.filterRole())
values.append(value)
action_all = QtWidgets.QAction("All", self)
action_all.setData(None)
menu.addAction(action_all)
menu.addSeparator()
for value in sorted(list(set(values))):
action = QtWidgets.QAction(str(value), self)
action.setData(value)
menu.addAction(action)
headerPos = self.tableView.mapToGlobal(self.horizontalHeader.pos())
posY = headerPos.y() + self.horizontalHeader.height()
posX = headerPos.x() + self.horizontalHeader.sectionPosition(logicalIndex)
action = menu.exec_(QtCore.QPoint(posX, posY))
if action is not None:
self.proxy.setFilterKeyColumn(logicalIndex)
self.proxy.filter_value = action.data()
def lineEdit_textChanged(self):
search = QtCore.QRegExp(
self.lineEdit.text(), QtCore.Qt.CaseInsensitive, QtCore.QRegExp.RegExp
)
self.proxy.setFilterKeyColumn(self.comboBox.currentIndex())
self.proxy.setFilterRegExp(search)
def edit_items(self):
if not self.model.rowCount():
return
index = self.tableView.currentIndex()
if index.isValid():
row = index.row()
else:
row = 0
name_line = QtWidgets.QLineEdit(readOnly=True)
age_edit = QtWidgets.QSpinBox()
gender_combo = QtWidgets.QComboBox()
genders = "M", "F"
gender_combo.addItems(genders)
date_of_birth = QtWidgets.QDateEdit()
date_of_birth.setDisplayFormat("d-MMM-yyyy")
updateButton = QtWidgets.QPushButton("Update")
mapper = QtWidgets.QDataWidgetMapper()
mapper.setSubmitPolicy(QtWidgets.QDataWidgetMapper.ManualSubmit)
mapper.setModel(self.tableView.model())
mapper.addMapping(name_line, 0)
mapper.addMapping(age_edit, 1)
mapper.addMapping(gender_combo, 2)
mapper.addMapping(date_of_birth, 3)
mapper.setCurrentIndex(row)
dialog = QtWidgets.QDialog()
dialog.setWindowTitle("Edit Window")
layout = QtWidgets.QVBoxLayout(dialog)
formLayout = QtWidgets.QFormLayout()
layout.addLayout(formLayout)
formLayout.addRow("Name", name_line)
formLayout.addRow("Age", age_edit)
formLayout.addRow("Gender", gender_combo)
formLayout.addRow("Date of Birth", date_of_birth)
layout.addWidget(updateButton)
updateButton.clicked.connect(dialog.accept)
if dialog.exec_():
mapper.submit()
def refresh_table(self):
print("refresh")
def main():
app = QtWidgets.QApplication(sys.argv)
w = UI()
w.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
I am trying to edit row data from Qsqltablemodel using with QDataWidgetMapper(). The date column is in text format in My database table, I want to use date format as "d-MMM-yyyy". When i am trying to edit the row, the date column is setting the default date format as "1-1-2000".
How to do this?
Below is the example Image:
By default QtSql can handle string columns with a specific format such as QDate and QDateTime (as indicated in the sqlite docs), but in this case it does not comply with those formats so Qt does not know how to interpret them and displays them as text. So you must convert that text into QDate, and vice versa, using a delegate:
class ItemDelegate(QtWidgets.QItemDelegate):
def setEditorData(self, editor, index):
if index.column() == 3 and isinstance(editor, QtWidgets.QDateEdit):
text = index.data()
date = QtCore.QDate.fromString(text, "d-MMM-yyyy")
editor.setDate(date)
return
super().setEditorData(editor, index)
def setModelData(self, editor, model, index):
if index.column() == 3 and isinstance(editor, QtWidgets.QDateEdit):
text = editor.date().toString("d-MMM-yyyy")
model.setData(index, text)
return
super().setModelData(editor, model, index)
# ...
mapper = QtWidgets.QDataWidgetMapper()
delegate = ItemDelegate(mapper)
mapper.setItemDelegate(delegate)
# ...
Just for a sport of it I am playing around with a demo code from #ekhumoro (all credits for original Qt4 code goes to him), where he inserted a new line of QLineEdit widgets into QHeaderview of QTableView. I ported the code to Qt5 and started to add different widget to the header. No problems with a QComboBox, QCheckBox, an empty space (QWidget) and a QPushButton.
However, when I created a composed QWidget containting a QHBoxLayout with a QPushButton (it's the one with "=" sign, in column "Three") and a QLineEdit. All the controls are linked to relevant slots and it runs fine, including the QLineEdit from the composed field in column Three, but except the QPushButton from that composed widget. The ChangeIntButtonSymbol(self) slot def should cycle the button's Text between <|=|> values. I always get an error:
AttributeError: 'FilterHeader' object has no attribute 'text'
which indicates, that unlike in other cases, here the context of the parent (retrieved by self.sender()) widget is different, the def received FilterHeader class as a parent instead the btn. I tried also passing an argument using lambda:
self.btn.clicked.connect(lambda: self.changebuttonsymbol.emit(self.btn))
...but the result was exactly the same (with different wording in the error).
Clearly, I'm not getting fully the architecture of this QHeaderView extension and making some basic mistake. Full demo bellow, the problem occures when "=" button is clicked, any solutions or hints appreciated.
import sys
from PyQt5 import QtCore, QtGui
from PyQt5.QtWidgets import QHeaderView, QWidget, QLineEdit, QApplication, QTableView, QVBoxLayout,QHBoxLayout, QLineEdit, QComboBox, QPushButton, QCheckBox
from PyQt5.QtCore import pyqtSignal
class FilterHeader(QHeaderView):
filterActivated = QtCore.pyqtSignal()
changebuttonsymbol = QtCore.pyqtSignal()
def __init__(self, parent):
super().__init__(QtCore.Qt.Horizontal, parent)
self._editors = []
self._padding = 4
self.setStretchLastSection(True)
#self.setResizeMode(QHeaderView.Stretch)
self.setDefaultAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
self.setSortIndicatorShown(False)
self.sectionResized.connect(self.adjustPositions)
parent.horizontalScrollBar().valueChanged.connect(self.adjustPositions)
def setFilterBoxes(self, count):
while self._editors:
editor = self._editors.pop()
editor.deleteLater()
for index in range(count):
if index == 1: # Empty
editor = QWidget()
elif index == 2: # Number filter (>|=|<)
editor = QWidget(self.parent())
edlay = QHBoxLayout()
edlay.setContentsMargins(0, 0, 0, 0)
edlay.setSpacing(0)
self.btn = QPushButton()
self.btn.setText("=")
self.btn.setFixedWidth(20)
#self.btn.clicked.connect(lambda: self.changebuttonsymbol.emit(self.btn))
self.btn.clicked.connect(self.changebuttonsymbol.emit)
#btn.setViewportMargins(0, 0, 0, 0)
linee = QLineEdit(self.parent())
linee.setPlaceholderText('Filter')
linee.returnPressed.connect(self.filterActivated.emit)
#linee.setViewportMargins(0, 0, 0, 0)
edlay.addWidget(self.btn)
edlay.addWidget(linee)
editor.setLayout(edlay)
elif index == 3:
editor = QComboBox(self.parent())
editor.addItems(["", "Combo", "One", "Two", "Three"])
editor.currentIndexChanged.connect(self.filterActivated.emit)
elif index == 4:
editor = QPushButton(self.parent())
editor.clicked.connect(self.filterActivated.emit)
editor.setText("Button")
elif index == 5:
editor = QCheckBox(self.parent())
editor.clicked.connect(self.filterActivated.emit)
editor.setTristate(True)
editor.setCheckState(1)
editor.setText("CheckBox")
else: # string filter
editor = QLineEdit(self.parent())
editor.setPlaceholderText('Filter')
editor.returnPressed.connect(self.filterActivated.emit)
self._editors.append(editor)
self.adjustPositions()
def sizeHint(self):
size = super().sizeHint()
if self._editors:
height = self._editors[0].sizeHint().height()
size.setHeight(size.height() + height + self._padding)
return size
def updateGeometries(self):
if self._editors:
height = self._editors[0].sizeHint().height()
self.setViewportMargins(0, 0, 0, height + self._padding)
else:
self.setViewportMargins(0, 0, 0, 0)
super().updateGeometries()
self.adjustPositions()
def adjustPositions(self):
for index, editor in enumerate(self._editors):
height = editor.sizeHint().height()
CompensateY = 0
CompensateX = 0
if self._editors[index].__class__.__name__ == "QComboBox":
CompensateY = +2
elif self._editors[index].__class__.__name__ == "QWidget":
CompensateY = -1
elif self._editors[index].__class__.__name__ == "QPushButton":
CompensateY = -1
elif self._editors[index].__class__.__name__ == "QCheckBox":
CompensateY = 4
CompensateX = 4
editor.move( self.sectionPosition(index) - self.offset() + 1 + CompensateX, height + (self._padding // 2) + 2 + CompensateY)
editor.resize(self.sectionSize(index), height)
def filterText(self, index):
if 0 <= index < len(self._editors):
if self._editors[index].__class__.__name__ == "QLineEdit":
return self._editors[index].text()
return ''
def setFilterText(self, index, text):
if 0 <= index < len(self._editors):
self._editors[index].setText(text)
def clearFilters(self):
for editor in self._editors:
editor.clear()
class Window(QWidget):
def __init__(self):
super(Window, self).__init__()
self.view = QTableView()
layout = QVBoxLayout(self)
layout.addWidget(self.view)
header = FilterHeader(self.view)
self.view.setHorizontalHeader(header)
model = QtGui.QStandardItemModel(self.view)
model.setHorizontalHeaderLabels('One Two Three Four Five Six Seven'.split())
self.view.setModel(model)
header.setFilterBoxes(model.columnCount())
header.filterActivated.connect(self.handleFilterActivated)
header.changebuttonsymbol.connect(self.ChangeIntButtonSymbol)
def handleFilterActivated(self):
header = self.view.horizontalHeader()
for index in range(header.count()):
if index != 4:
print((index, header.filterText(index)))
else:
print("Button")
def ChangeIntButtonSymbol(self):
print("Int button triggered")
nbtn = self.sender()
print(str(nbtn))
if nbtn.text() == "=":
nbtn.setText(">")
elif nbtn.text() == ">":
nbtn.setText("<")
else:
nbtn.setText("=")
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Window()
window.setGeometry(800, 100, 600, 300)
window.show()
sys.exit(app.exec_())
sender() is a method that indicates to which object the signal belongs, and it is obvious changebuttonsymbol belongs to the header that obviously does not have a text() method. On the other hand, it is better for each class to manage its own objects, so the change of the button text must be implemented in the header.
Finally, if a complex widget is used, it is better to have it in a class.
import sys
from PyQt5.QtCore import pyqtSignal, Qt
from PyQt5.QtGui import QStandardItemModel
from PyQt5.QtWidgets import (
QHeaderView,
QWidget,
QLineEdit,
QApplication,
QTableView,
QVBoxLayout,
QHBoxLayout,
QComboBox,
QPushButton,
QCheckBox,
)
class Widget(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.btn = QPushButton()
self.btn.setText("=")
self.btn.setFixedWidth(20)
self.linee = QLineEdit()
self.linee.setPlaceholderText("Filter")
lay = QHBoxLayout(self)
lay.setContentsMargins(0, 0, 0, 0)
lay.setSpacing(0)
lay.addWidget(self.btn)
lay.addWidget(self.linee)
class FilterHeader(QHeaderView):
filterActivated = pyqtSignal()
def __init__(self, parent):
super().__init__(Qt.Horizontal, parent)
self._editors = []
self._padding = 4
self.setStretchLastSection(True)
# self.setResizeMode(QHeaderView.Stretch)
self.setDefaultAlignment(Qt.AlignLeft | Qt.AlignVCenter)
self.setSortIndicatorShown(False)
self.sectionResized.connect(self.adjustPositions)
parent.horizontalScrollBar().valueChanged.connect(self.adjustPositions)
def setFilterBoxes(self, count):
while self._editors:
editor = self._editors.pop()
editor.deleteLater()
for index in range(count):
editor = self.create_editor(self.parent(), index)
self._editors.append(editor)
self.adjustPositions()
def create_editor(self, parent, index):
if index == 1: # Empty
editor = QWidget()
elif index == 2: # Number filter (>|=|<)
editor = Widget(parent)
editor.linee.returnPressed.connect(self.filterActivated)
editor.btn.clicked.connect(self.changebuttonsymbol)
elif index == 3:
editor = QComboBox(parent)
editor.addItems(["", "Combo", "One", "Two", "Three"])
editor.currentIndexChanged.connect(self.filterActivated)
elif index == 4:
editor = QPushButton(parent)
editor.clicked.connect(self.filterActivated)
editor.setText("Button")
elif index == 5:
editor = QCheckBox(parent)
editor.clicked.connect(self.filterActivated)
editor.setTristate(True)
editor.setCheckState(Qt.Checked)
editor.setText("CheckBox")
else:
editor = QLineEdit(parent)
editor.setPlaceholderText("Filter")
editor.returnPressed.connect(self.filterActivated)
return editor
def sizeHint(self):
size = super().sizeHint()
if self._editors:
height = self._editors[0].sizeHint().height()
size.setHeight(size.height() + height + self._padding)
return size
def updateGeometries(self):
if self._editors:
height = self._editors[0].sizeHint().height()
self.setViewportMargins(0, 0, 0, height + self._padding)
else:
self.setViewportMargins(0, 0, 0, 0)
super().updateGeometries()
self.adjustPositions()
def adjustPositions(self):
for index, editor in enumerate(self._editors):
if not isinstance(editor, QWidget):
continue
height = editor.sizeHint().height()
compensate_y = 0
compensate_x = 0
if type(editor) is QComboBox:
compensate_y = +2
elif type(editor) in (QWidget, Widget):
compensate_y = -1
elif type(editor) is QPushButton:
compensate_y = -1
elif type(editor) is QCheckBox:
compensate_y = 4
compensate_x = 4
editor.move(
self.sectionPosition(index) - self.offset() + 1 + compensate_x,
height + (self._padding // 2) + 2 + compensate_y,
)
editor.resize(self.sectionSize(index), height)
def filterText(self, index):
for editor in self._editors:
if hasattr(editor, "text") and callable(editor.text):
return editor.text()
return ""
def setFilterText(self, index, text):
for editor in self._editors:
if hasattr(editor, "setText") and callable(editor.setText):
editor.setText(text)
def clearFilters(self):
for editor in self._editors:
editor.clear()
def changebuttonsymbol(self):
nbtn = self.sender()
if nbtn.text() == "=":
nbtn.setText(">")
elif nbtn.text() == ">":
nbtn.setText("<")
else:
nbtn.setText("=")
class Window(QWidget):
def __init__(self):
super(Window, self).__init__()
self.view = QTableView()
layout = QVBoxLayout(self)
layout.addWidget(self.view)
header = FilterHeader(self.view)
self.view.setHorizontalHeader(header)
model = QStandardItemModel(self.view)
model.setHorizontalHeaderLabels("One Two Three Four Five Six Seven".split())
self.view.setModel(model)
header.setFilterBoxes(model.columnCount())
header.filterActivated.connect(self.handleFilterActivated)
def handleFilterActivated(self):
header = self.view.horizontalHeader()
for index in range(header.count()):
if index != 4:
print(index, header.filterText(index))
else:
print("Button")
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Window()
window.setGeometry(800, 100, 600, 300)
window.show()
sys.exit(app.exec_())
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()
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_())