Related
It is not possible to make the text crossed out in a separate cell. It turns out only to apply to everyone. Tried changing in model methods. Also applies to everyone. I looked for an example on the Internet, but it didn't work. The bottom line is for the user to select a cell, click on the button and the text in the cell becomes checked out.
My code
import sys
from PySide2.QtCore import *
from PySide2.QtGui import *
from PySide2.QtWidgets import *
STRIKE_ROLE = Qt.UserRole + 1
import pandas as pd
class StrikeDelegate(QStyledItemDelegate):
def initStyleOption(self, option, index):
super().initStyleOption(option, index)
font = QFont(option.font)
font.setStrikeOut(index.data(STRIKE_ROLE))
option.font = font
class MyDelegate(QItemDelegate):
def setEditorData(self, editor, index):
text = index.data(Qt.EditRole) or index.data(Qt.DisplayRole)
editor.setText(text)
# def initStyleOption(self,option,index):
#
# QtWidgets.QStyledItemDelegate.initStyleOption(option,index)
# option.font().setStrikeOut(True)
class TableModel(QAbstractTableModel):
def __init__(self, data):
super(TableModel, self).__init__()
self._data = data
def setData(self, index, value, role):
self._data.iloc[index.row(), index.column()] = value
self.dataChanged.emit(index, index)
return True
def flags(self, index):
return Qt.ItemIsEnabled | Qt.ItemIsEditable |Qt.ItemIsSelectable | Qt.ItemIsDragEnabled | Qt.ItemIsDropEnabled
def data(self, index, role):
if role == Qt.DisplayRole:
value = self._data.iloc[index.row(), index.column()]
return str(value)
def rename_column(self, index, new_name):
self._data.rename(index={self._data.index[index]: new_name}, inplace=True)
return True
def rowCount(self, index):
return self._data.shape[0]
def columnCount(self, index):
return self._data.shape[1]
def headerData(self, section, orientation, role):
# section is the index of the column/row.
if role == Qt.DisplayRole:
if orientation == Qt.Horizontal:
return str(self._data.columns[section])
if orientation == Qt.Vertical:
return str(self._data.index[section])
def add_empty_row(self):
self.beginResetModel()
try:
self._data = self._data.append(
pd.DataFrame(columns=self._data.columns, data=([[0] * len(self._data.columns)]),
index=['Пусто']))
except IndexError:
self._data = self._data.append(
pd.DataFrame(columns=self._data.columns, data=([[None] * len(self._data.columns)]),
index=[0]))
self.layoutChanged.emit()
self.endResetModel()
# self.inp_.add_delete_row.emit(self.createIndex(self._data.index[-1], 0), 'add_empty_row')
def delete_row(self, index):
self._data.drop(self._data.index[index.row()], inplace=True)
# self._data.reset_index(inplace=True, drop=True)
self.layoutChanged.emit()
class TaskModel(TableModel):
def add_empty_row(self):
self.beginResetModel()
try:
self._data = self._data.append(
pd.DataFrame(columns=self._data.columns, data=([['Новая задача'] * len(self._data.columns)]),
index=[self._data.index[-1] + 1]))
except IndexError:
self._data = self._data.append(
pd.DataFrame(columns=self._data.columns, data=([[None] * len(self._data.columns)]),
index=[0]))
self.layoutChanged.emit()
self.endResetModel()
def data(self, index, role):
value = self._data.iloc[index.row(), index.column()]
if role == Qt.DisplayRole:
return str(value)
if role == STRIKE_ROLE:
font=QFont('Calibri',13)
return font
def setData(self, index, value, role):
self._data.iloc[index.row(), index.column()] = value
self.dataChanged.emit(index, index)
return True
class Ui_Form(object):
def setupUi(self, Form):
if not Form.objectName():
Form.setObjectName(u"Form")
Form.resize(586, 683)
self.Form=Form
self.horizontalLayout_2 = QHBoxLayout(Form)
self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
self.dock_task_calendar = QDockWidget(Form)
self.dock_task_calendar.setObjectName(u"dock_task_calendar")
self.dock_task_calendar.setFeatures(QDockWidget.DockWidgetClosable|QDockWidget.DockWidgetMovable)
self.dockWidgetContents_task = QWidget()
self.dockWidgetContents_task.setObjectName(u"dockWidgetContents_task")
self.verticalLayout_3 = QVBoxLayout(self.dockWidgetContents_task)
self.verticalLayout_3.setObjectName(u"verticalLayout_3")
self.scrollArea_task = QScrollArea(self.dockWidgetContents_task)
self.scrollArea_task.setObjectName(u"scrollArea_task")
self.scrollArea_task.setWidgetResizable(True)
self.scrollAreaWidgetContents_task_2 = QWidget()
self.scrollAreaWidgetContents_task_2.setObjectName(u"scrollAreaWidgetContents_task_2")
self.scrollAreaWidgetContents_task_2.setGeometry(QRect(0, 0, 548, 623))
self.horizontalLayout = QHBoxLayout(self.scrollAreaWidgetContents_task_2)
self.horizontalLayout.setObjectName(u"horizontalLayout")
self.gridLayout = QGridLayout()
self.gridLayout.setObjectName(u"gridLayout")
self.verticalLayout_2 = QVBoxLayout()
self.verticalLayout_2.setObjectName(u"verticalLayout_2")
self.gridLayout.addLayout(self.verticalLayout_2, 4, 0, 1, 1)
self.tasklistView = QListView(self.scrollAreaWidgetContents_task_2)
self.tasklistView.setObjectName(u"tasklistView")
self.tasklistView.setDragEnabled(True)
self.tasklistView.setDragDropOverwriteMode(True)
self.tasklistView.setWordWrap(True)
self.tasklistView.setItemAlignment(Qt.AlignLeading)
self.gridLayout.addWidget(self.tasklistView, 4, 1, 1, 1)
self.model=TaskModel(pd.DataFrame([],columns=['a']))
self.delegater=MyDelegate()
self.tasklistView.setModel(self.model)
self.tasklistView.setItemDelegate(self.delegater)
self.label_task = QLabel(self.scrollAreaWidgetContents_task_2)
self.label_task.setObjectName(u"label_task")
sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.label_task.sizePolicy().hasHeightForWidth())
self.label_task.setSizePolicy(sizePolicy)
self.label_task.setMaximumSize(QSize(16777215, 20))
self.label_task.setAlignment(Qt.AlignCenter)
self.gridLayout.addWidget(self.label_task, 2, 1, 1, 1)
self.calendar = QCalendarWidget(self.scrollAreaWidgetContents_task_2)
self.calendar.setObjectName(u"calendar")
sizePolicy1 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
sizePolicy1.setHorizontalStretch(0)
sizePolicy1.setVerticalStretch(0)
sizePolicy1.setHeightForWidth(self.calendar.sizePolicy().hasHeightForWidth())
self.calendar.setSizePolicy(sizePolicy1)
self.calendar.setMaximumSize(QSize(16777215, 400))
self.gridLayout.addWidget(self.calendar, 1, 1, 1, 1)
self.verticalLayout = QVBoxLayout()
self.verticalLayout.setObjectName(u"verticalLayout")
self.add_task = QPushButton(self.scrollAreaWidgetContents_task_2)
self.add_task.setObjectName(u"add_task")
sizePolicy2 = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
sizePolicy2.setHorizontalStretch(0)
sizePolicy2.setVerticalStretch(0)
sizePolicy2.setHeightForWidth(self.add_task.sizePolicy().hasHeightForWidth())
self.add_task.setSizePolicy(sizePolicy2)
self.add_task.setMaximumSize(QSize(40, 40))
self.verticalLayout.addWidget(self.add_task)
self.del_task = QPushButton(self.scrollAreaWidgetContents_task_2)
self.del_task.setObjectName(u"del_task")
self.del_task.setMaximumSize(QSize(40, 40))
self.verticalLayout.addWidget(self.del_task)
self.check_task = QPushButton(self.scrollAreaWidgetContents_task_2)
self.check_task.setObjectName(u"check_task")
self.check_task.setMaximumSize(QSize(40, 40))
self.verticalLayout.addWidget(self.check_task)
self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
self.verticalLayout.addItem(self.verticalSpacer)
self.gridLayout.addLayout(self.verticalLayout, 4, 2, 1, 1)
self.horizontalLayout.addLayout(self.gridLayout)
self.scrollArea_task.setWidget(self.scrollAreaWidgetContents_task_2)
self.verticalLayout_3.addWidget(self.scrollArea_task)
self.dock_task_calendar.setWidget(self.dockWidgetContents_task)
self.horizontalLayout_2.addWidget(self.dock_task_calendar)
self.add_task.pressed.connect(self.model.add_empty_row)
self.retranslateUi(Form)
self.Form.show()
# setupUi
def retranslateUi(self, Form):
Form.setWindowTitle(QCoreApplication.translate("Form", u"Form", None))
self.dock_task_calendar.setWindowTitle("")
self.label_task.setText(QCoreApplication.translate("Form", u"C\u043f\u0438\u0441\u043e\u043a \u0434\u0435\u043b/\u0437\u0430\u0434\u0430\u0447", None))
self.add_task.setText(QCoreApplication.translate("Form", u"PushButton", None))
self.del_task.setText(QCoreApplication.translate("Form", u"PushButton", None))
self.check_task.setText(QCoreApplication.translate("Form", u"PushButton", None))
# retranslateUi
if __name__ == '__main__':
app = QApplication(sys.argv)
translator = QTranslator()
if len(sys.argv) > 1:
locale = sys.argv[1]
else:
locale = QLocale.system().name()
translator.load('qt_%s' % locale,
QLibraryInfo.location(QLibraryInfo.TranslationsPath))
app.installTranslator(translator)
form=Ui_Form()
form.setupUi(QWidget())
app.exec_()
Since the OP has not provided an MRE then my solution will only limit the general logic which is:
Create a new role for each item where a boolean (or any other variable) is stored if the strike is applied.
Use a delegate to set the strike in the font.
With the clicked signal change the value stored in the role strike for selected rows.
from functools import cached_property
from PyQt5 import QtCore, QtGui, QtWidgets
STRIKE_ROLE = QtCore.Qt.UserRole + 1
class StrikeDelegate(QtWidgets.QStyledItemDelegate):
def initStyleOption(self, option, index):
super().initStyleOption(option, index)
font = QtGui.QFont(option.font)
font.setStrikeOut(index.data(STRIKE_ROLE))
option.font = font
def createEditor(self, parent, option, index):
editor = super().createEditor(parent, option, index)
font = QtGui.QFont(editor.font())
font.setStrikeOut(index.data(STRIKE_ROLE))
editor.setFont(font)
return editor
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
central_widget = QtWidgets.QWidget()
self.setCentralWidget(central_widget)
hlay = QtWidgets.QHBoxLayout(central_widget)
vlay = QtWidgets.QVBoxLayout()
vlay.addWidget(self.add_button)
vlay.addWidget(self.remove_button)
vlay.addWidget(self.strike_button)
vlay.addStretch()
hlay.addWidget(self.view)
hlay.addLayout(vlay)
self.add_button.clicked.connect(self.add)
self.remove_button.clicked.connect(self.remove)
self.strike_button.clicked.connect(self.strike)
#cached_property
def model(self):
return QtGui.QStandardItemModel()
#cached_property
def view(self):
view = QtWidgets.QListView()
delegate = StrikeDelegate()
view.setItemDelegate(delegate)
view.setModel(self.model)
return view
#cached_property
def add_button(self):
return QtWidgets.QPushButton("Add")
#cached_property
def remove_button(self):
return QtWidgets.QPushButton("Remove")
#cached_property
def strike_button(self):
return QtWidgets.QPushButton("Strike")
def add(self):
text, ok = QtWidgets.QInputDialog.getText(self, "Title", "Text:")
if not ok:
return
item = QtGui.QStandardItem(text)
item.setData(True, STRIKE_ROLE)
self.model.appendRow(item)
def remove(self):
rows = [index.row() for index in self.view.selectedIndexes()]
for row in sorted(rows, reverse=True):
self.model.takeRow(row)
def strike(self):
for index in self.view.selectedIndexes():
strike = self.model.data(index, STRIKE_ROLE)
self.model.setData(index, not strike, STRIKE_ROLE)
def main():
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
I’m working on a Python GUI application with PyQt5 which has a QTableView for showing data.
Here is the code:
import sys
from PyQt5 import QtWidgets, QtCore
from PyQt5.QtCore import Qt
class DataModel(QtCore.QAbstractTableModel):
def __init__(self):
super().__init__()
self.data = []
def data(self, index, role):
if role == Qt.DisplayRole:
return self.data[index.row()][index.column()]
def rowCount(self, index):
return len(self.data)
def columnCount(self, index):
return len(self.data[0])
class MainWindow(UI.UserInterface):
def __init__(self):
super().__init__()
self.model = DataModel()
self.load()
self.TableView.setModel(self.model)
self.TableView.resizeColumnsToContents()
self.TableView.horizontalHeader().setStretchLastSection(True)
def load(self):
try:
self.model.data = [(1, '2020-01-10 00:00:00', 'KANIA', 'HENRYK', 4219)]
except Exception:
pass
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
The class UI.UserInterface is in separate module. It has the QWidgets of the interface and layout QWidgets. One of them is QTableView.
I can't seem to find a way to set the header labels for the QTableView.
I looked for different solutions (some of them below) but none of them worked:
https://doc.qt.io/qt-5/sql-presenting.html (this one is written in C++. I don't quite understand it)
You must implement headerData():
class DataModel(QtCore.QAbstractTableModel):
# ...
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
return 'Column {}'.format(section + 1)
return super().headerData(section, orientation, role)
Obviously you can set your own labels even with a simple list containing the labels you want to show.
Note that you should be very careful with naming new attributes to subclasses as they might already exist.
Most importantly, you should not overwrite self.data.
here is an exemple using a QtableView and a set headerdata and you willbe able to modifier the data from the tableview
def exemple_table(self):
database = QSqlDatabase("QPSQL")
database.setHostName("localhost")
database.setDatabaseName("database")
database.setUserName("postgres")
database.setPassword("password")
database.open()
model_ft = QSqlTableModel(db=database)
model_ft.setTable('table')
model_ft.setHeaderData(0, Qt.Horizontal,"id")
model_ft.setHeaderData(1, Qt.Horizontal,"exemple01")
model_ft.setHeaderData(2, Qt.Horizontal,"exemple02")
model_ft.setHeaderData(3, Qt.Horizontal,"exemple03")
model_ft.setHeaderData(4, Qt.Horizontal,"exemple04")
model_ft.setHeaderData(5, Qt.Horizontal,"exemple05")
model_ft.setHeaderData(6, Qt.Horizontal,"exemple06")
model_ft.setHeaderData(7, Qt.Horizontal,"exemple07")
model_ft.removeColumns(8,1)
date = str(datetime.date.today())
self.tableView.setModel(model_ft)
self.tableView.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
model_ft.setSort(0, Qt.DescendingOrder)
model_ft.select()
filter_ft = "date_d ='%s' " % (date_1)
model_ft.setFilter(filter_ft)
ps im using postgresql you can find other drivers here https://doc.qt.io/qt-5/sql-driver.html
and the filtre you can use all the SQL function
Override headerData method of QTableAbstractModel to set Columns and Rows name
def headerData(self, section: int, orientation: PySide6.QtCore.Qt.Orientation, role: int = ...):
#for setting columns name
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return f"Column {section + 1}"
#for setting rows name
if orientation == Qt.Vertical and role == Qt.DisplayRole:
return f"Row {section + 1}"
Working code:
import sys
import requests
import PySide6
from PySide6.QtWidgets import QTableView, QWidget, QApplication, QGridLayout, QHeaderView
from PySide6.QtCore import Qt, QAbstractTableModel
from PySide6.QtGui import QColor, QIcon, QPixmap
from datetime import datetime
class MagicIcon():
def __init__(self, link):
self.link = link
self.icon = QIcon()
try:
response = requests.get(self.link)
pixmap = QPixmap()
pixmap.loadFromData(response.content)
self.icon = QIcon(pixmap)
except:
pass
class TableModel(QAbstractTableModel):
def headerData(self, section: int, orientation: PySide6.QtCore.Qt.Orientation, role: int = ...):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
# return f"Column {section + 1}"
return self.columns[section]
if orientation == Qt.Vertical and role == Qt.DisplayRole:
return f"{section + 1}"
def __init__(self, _data):
self.columns = ["Account", "Investment", "KYC", "Investment Date"]
# super().__init__(self)
super(TableModel, self).__init__()
self._data = _data
self.calendarLink = "https://img.icons8.com/fluency/48/000000/windows-calendar.png"
self.dollarLink = "https://img.icons8.com/external-vitaliy-gorbachev-lineal-color-vitaly-gorbachev/40/000000/external-dollar-currency-vitaliy-gorbachev-lineal-color-vitaly-gorbachev-1.png"
self.analysis = "https://img.icons8.com/external-flatarticons-blue-flatarticons/65/000000/external-analysis-digital-marketing-flatarticons-blue-flatarticons-1.png"
self.bug = "https://img.icons8.com/offices/30/000000/bug.png"
self.account = "https://img.icons8.com/plumpy/24/000000/edit-administrator.png"
self.approvedLink = "https://img.icons8.com/external-bearicons-flat-bearicons/40/000000/external-approved-approved-and-rejected-bearicons-flat-bearicons-9.png"
self.rejectedLink = "https://img.icons8.com/external-bearicons-flat-bearicons/40/000000/external-rejected-approved-and-rejected-bearicons-flat-bearicons-11.png"
self.naLink = "https://img.icons8.com/color/48/000000/not-applicable.png"
self.calendarIcon = MagicIcon(self.calendarLink).icon
self.accountIcon = MagicIcon(self.account).icon
self.dollarIcon = MagicIcon(self.dollarLink).icon
self.approvedIcon = MagicIcon(self.approvedLink).icon
self.rejectedIcon = MagicIcon(self.rejectedLink).icon
self.naIcon = MagicIcon(self.naLink).icon
def data(self, index, role):
if role == Qt.DisplayRole:
value = self._data[index.row()][index.column()]
if isinstance(value, datetime):
return value.strftime("%Y-%m-%d")
if isinstance(value, float):
return f"{value:.2f}"
return value
if role == Qt.TextAlignmentRole:
return Qt.AlignHCenter + Qt.AlignVCenter
if role == Qt.BackgroundRole:
return QColor("#adcdff") if index.row() % 2 == 0 else QColor("#d8ffc2")
if role == Qt.DecorationRole:
value = self._data[index.row()][index.column()]
if value is None:
return self.naIcon
if isinstance(value, datetime):
return self.calendarIcon
if index.column() == 0:
return self.accountIcon
if index.column() == 1:
return self.dollarIcon
if index.column() == 2:
if value == True:
return self.approvedIcon
elif value == False:
return self.rejectedIcon
return self.naIcon
def rowCount(self, index):
return len(self._data)
def columnCount(self, index):
return len(self._data[0])
class MainWindow(QWidget):
def __init__(self):
# super().__init__()
super(MainWindow, self).__init__()
self.resizeEvent = self.onResize
self.table = QTableView()
self.setWindowIcon(MagicIcon(
"https://img.icons8.com/external-flatarticons-blue-flatarticons/65/000000/external-analysis-digital-marketing-flatarticons-blue-flatarticons-1.png"
).icon)
self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
# self.table.setSizeAdjustPolicy(QHeaderView.AdjustIgnored)
# self.table.verticalHeader().setSectionResizeMode(QHeaderView.Stretch)
data = [
["Andrew Mike", 15.255, True, datetime(2022, 1, 5)],
["Eliza Petterson", 353.555, False, datetime(2020, 1, 5)],
["Joseph Samuel", 123, None, datetime(2020, 1, 15)],
["Nita Singh", 266, True, datetime(2022, 2, 7)],
["Rahul Chakrabarti", 102, True, datetime(2019, 10, 15)],
]
self.model = TableModel(data)
self.table.setModel(self.model)
self.header = self.table.horizontalHeader()
# self.header.setSectionResizeMode(0, QHeaderView.Stretch)
# self.header.setSectionResizeMode(1, QHeaderView.)
# self.header.setSectionResizeMode(2, QHeaderView.ResizeToContents)
self.layout = QGridLayout()
self.layout.addWidget(self.table, 0, 0)
self.setLayout(self.layout)
def onResize(self, event):
# print('old', event.oldSize(), 'new', event.size())
# super(MainWindow, self).resizeEvent(event)
pass
if __name__ == "__main__":
app = QApplication(sys.argv)
wid = MainWindow()
wid.show()
sys.exit(app.exec())
I am trying to make a PyQt5 GUI to show a Pandas dataframe in the form of a table and provide column filtering options, similar to the Microsoft Excel filters. So far I managed to adopt a similar SO answer. Here is the picture of my table in the GUI:
As shown in the figure above, there are two ways to filter columns: the Regex Filter and clicking on each column. There is however a problem I need help to address: the currently applied filters (either regex filter or column click) disappear when I filter a second column. I want the second filter as AND, i.e. a filter that satisfies column 1 AND column 2.
Here is my code:
#!/usr/bin/env python
#-*- coding:utf-8 -*-
from PyQt5 import QtCore, QtGui, QtWidgets
import pandas as pd
class PandasModel(QtCore.QAbstractTableModel):
def __init__(self, df=pd.DataFrame(), parent=None):
QtCore.QAbstractTableModel.__init__(self, parent=parent)
self._df = df.copy()
def toDataFrame(self):
return self._df.copy()
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if role != QtCore.Qt.DisplayRole:
return QtCore.QVariant()
if orientation == QtCore.Qt.Horizontal:
try:
return self._df.columns.tolist()[section]
except (IndexError, ):
return QtCore.QVariant()
elif orientation == QtCore.Qt.Vertical:
try:
# return self.df.index.tolist()
return self._df.index.tolist()[section]
except (IndexError, ):
return QtCore.QVariant()
def data(self, index, role=QtCore.Qt.DisplayRole):
if role != QtCore.Qt.DisplayRole:
return QtCore.QVariant()
if not index.isValid():
return QtCore.QVariant()
return QtCore.QVariant(str(self._df.iloc[index.row(), index.column()]))
def setData(self, index, value, role):
row = self._df.index[index.row()]
col = self._df.columns[index.column()]
if hasattr(value, 'toPyObject'):
# PyQt4 gets a QVariant
value = value.toPyObject()
else:
# PySide gets an unicode
dtype = self._df[col].dtype
if dtype != object:
value = None if value == '' else dtype.type(value)
self._df.set_value(row, col, value)
return True
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self._df.index)
def columnCount(self, parent=QtCore.QModelIndex()):
return len(self._df.columns)
def sort(self, column, order):
colname = self._df.columns.tolist()[column]
self.layoutAboutToBeChanged.emit()
self._df.sort_values(colname, ascending= order == QtCore.Qt.AscendingOrder, inplace=True)
self._df.reset_index(inplace=True, drop=True)
self.layoutChanged.emit()
class myWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(myWindow, self).__init__(parent)
self.centralwidget = QtWidgets.QWidget(self)
self.lineEdit = QtWidgets.QLineEdit(self.centralwidget)
self.view = QtWidgets.QTableView(self.centralwidget)
self.comboBox = QtWidgets.QComboBox(self.centralwidget)
self.label = QtWidgets.QLabel(self.centralwidget)
self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
self.gridLayout.addWidget(self.lineEdit, 0, 1, 1, 1)
self.gridLayout.addWidget(self.view, 1, 0, 1, 3)
self.gridLayout.addWidget(self.comboBox, 0, 2, 1, 1)
self.gridLayout.addWidget(self.label, 0, 0, 1, 1)
self.setCentralWidget(self.centralwidget)
self.label.setText("Regex Filter")
self.load_sites()
self.comboBox.addItems(["{0}".format(col) for col in self.model._df.columns])
self.lineEdit.textChanged.connect(self.on_lineEdit_textChanged)
self.comboBox.currentIndexChanged.connect(self.on_comboBox_currentIndexChanged)
self.horizontalHeader = self.view.horizontalHeader()
self.horizontalHeader.sectionClicked.connect(self.on_view_horizontalHeader_sectionClicked)
def load_sites(self):
df = pd.DataFrame({'site_codes': ['01', '02', '03', '04'],
'status': ['open', 'open', 'open', 'closed'],
'Location': ['east', 'north', 'south', 'east'],
'data_quality': ['poor', 'moderate', 'high', 'high']})
self.model = PandasModel(df)
self.proxy = QtCore.QSortFilterProxyModel(self)
self.proxy.setSourceModel(self.model)
self.view.setModel(self.proxy)
self.view.resizeColumnsToContents()
#QtCore.pyqtSlot(int)
def on_view_horizontalHeader_sectionClicked(self, logicalIndex):
self.logicalIndex = logicalIndex
self.menuValues = QtWidgets.QMenu(self)
self.signalMapper = QtCore.QSignalMapper(self)
self.comboBox.blockSignals(True)
self.comboBox.setCurrentIndex(self.logicalIndex)
self.comboBox.blockSignals(True)
valuesUnique = self.model._df.iloc[:, self.logicalIndex].unique()
actionAll = QtWidgets.QAction("All", self)
actionAll.triggered.connect(self.on_actionAll_triggered)
self.menuValues.addAction(actionAll)
self.menuValues.addSeparator()
for actionNumber, actionName in enumerate(sorted(list(set(valuesUnique)))):
action = QtWidgets.QAction(actionName, self)
self.signalMapper.setMapping(action, actionNumber)
action.triggered.connect(self.signalMapper.map)
self.menuValues.addAction(action)
self.signalMapper.mapped.connect(self.on_signalMapper_mapped)
headerPos = self.view.mapToGlobal(self.horizontalHeader.pos())
posY = headerPos.y() + self.horizontalHeader.height()
posX = headerPos.x() + self.horizontalHeader.sectionPosition(self.logicalIndex)
self.menuValues.exec_(QtCore.QPoint(posX, posY))
#QtCore.pyqtSlot()
def on_actionAll_triggered(self):
filterColumn = self.logicalIndex
filterString = QtCore.QRegExp( "",
QtCore.Qt.CaseInsensitive,
QtCore.QRegExp.RegExp
)
self.proxy.setFilterRegExp(filterString)
self.proxy.setFilterKeyColumn(filterColumn)
#QtCore.pyqtSlot(int)
def on_signalMapper_mapped(self, i):
stringAction = self.signalMapper.mapping(i).text()
filterColumn = self.logicalIndex
filterString = QtCore.QRegExp( stringAction,
QtCore.Qt.CaseSensitive,
QtCore.QRegExp.FixedString
)
self.proxy.setFilterRegExp(filterString)
self.proxy.setFilterKeyColumn(filterColumn)
#QtCore.pyqtSlot(str)
def on_lineEdit_textChanged(self, text):
search = QtCore.QRegExp( text,
QtCore.Qt.CaseInsensitive,
QtCore.QRegExp.RegExp
)
self.proxy.setFilterRegExp(search)
#QtCore.pyqtSlot(int)
def on_comboBox_currentIndexChanged(self, index):
self.proxy.setFilterKeyColumn(index)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
main = myWindow()
main.show()
main.resize(800, 600)
sys.exit(app.exec_())
If you want to implement a custom filtering process then you must override the filterAcceptsRow method, obtain the texts of each column and verify if they meet the condition, if they do return True, otherwise False. To recalculate the filter you must call the invalidateFilter method:
class CustomProxyModel(QtCore.QSortFilterProxyModel):
def __init__(self, parent=None):
super().__init__(parent)
self._filters = dict()
#property
def filters(self):
return self._filters
def setFilter(self, expresion, column):
if expresion:
self.filters[column] = expresion
elif column in self.filters:
del self.filters[column]
self.invalidateFilter()
def filterAcceptsRow(self, source_row, source_parent):
for column, expresion in self.filters.items():
text = self.sourceModel().index(source_row, column, source_parent).data()
regex = QtCore.QRegExp(
expresion, QtCore.Qt.CaseInsensitive, QtCore.QRegExp.RegExp
)
if regex.indexIn(text) == -1:
return False
return True
class myWindow(QtWidgets.QMainWindow):
# ...
def load_sites(self):
# ...
self.model = PandasModel(df)
self.proxy = CustomProxyModel(self)
self.proxy.setSourceModel(self.model)
self.view.setModel(self.proxy)
self.view.resizeColumnsToContents()
print("finished loading sites")
# ...
#QtCore.pyqtSlot()
def on_actionAll_triggered(self):
filterColumn = self.logicalIndex
self.proxy.setFilter("", filterColumn)
#QtCore.pyqtSlot(int)
def on_signalMapper_mapped(self, i):
stringAction = self.signalMapper.mapping(i).text()
filterColumn = self.logicalIndex
self.proxy.setFilter(stringAction, filterColumn)
#QtCore.pyqtSlot(str)
def on_lineEdit_textChanged(self, text):
self.proxy.setFilter(text, self.proxy.filterKeyColumn())
#QtCore.pyqtSlot(int)
def on_comboBox_currentIndexChanged(self, index):
self.proxy.setFilterKeyColumn(index)
Plus:
If you want to change the font of the QHeaderView then you must return the font in the headerData as shown below:
class PandasModel(QtCore.QAbstractTableModel):
def __init__(self, df=pd.DataFrame(), parent=None):
QtCore.QAbstractTableModel.__init__(self, parent=parent)
self._df = df.copy()
self.bolds = dict()
def toDataFrame(self):
return self._df.copy()
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if orientation == QtCore.Qt.Horizontal:
if role == QtCore.Qt.DisplayRole:
try:
return self._df.columns.tolist()[section]
except (IndexError,):
return QtCore.QVariant()
elif role == QtCore.Qt.FontRole:
return self.bolds.get(section, QtCore.QVariant())
elif orientation == QtCore.Qt.Vertical:
if role == QtCore.Qt.DisplayRole:
try:
# return self.df.index.tolist()
return self._df.index.tolist()[section]
except (IndexError,):
return QtCore.QVariant()
return QtCore.QVariant()
def setFont(self, section, font):
self.bolds[section] = font
self.headerDataChanged.emit(QtCore.Qt.Horizontal, 0, self.columnCount())
# ...
class myWindow(QtWidgets.QMainWindow):
# ...
#QtCore.pyqtSlot()
def on_actionAll_triggered(self):
filterColumn = self.logicalIndex
self.proxy.setFilter("", filterColumn)
font = QtGui.QFont()
self.model.setFont(filterColumn, font)
#QtCore.pyqtSlot(int)
def on_signalMapper_mapped(self, i):
stringAction = self.signalMapper.mapping(i).text()
filterColumn = self.logicalIndex
self.proxy.setFilter(stringAction, filterColumn)
font = QtGui.QFont()
font.setBold(True)
self.model.setFont(filterColumn, font)
I have below snippet to scrap the data from website and showing the result in table view. How to update or refresh the table for every 1 minute or 30 seconds. Is there anyway to do this in pyside?
import operator
from PySide.QtCore import *
from PySide.QtGui import *
from urllib import request
from lxml import etree
def getData():
url = "" # Removed valid url
response = request.urlopen(url)
html_parser = etree.HTMLParser()
tree = etree.parse(response, html_parser)
data_sets = tree.xpath("//div[#class='dataList']")
headers = ['Company Name','LTP','Change', '%Chg', 'Volume (lacs)', '30 Days % Change','365 Days % Change']
share_results = []
for element in data_sets:
result_list = []
share_comp_name = element.xpath('ul/li/p/a')[0]
result_list.append(share_comp_name.text)
share_value_list = element.xpath('ul/li/span')
counter = 0
for data in share_value_list:
result_list.append(float(str(data.text)))
counter += 1
share_results.append(tuple(result_list))
return headers, share_results
class MyWindow(QWidget):
def __init__(self, data_list, header, *args):
QWidget.__init__(self, *args)
self.setGeometry(300, 200, 550, 450)
self.setWindowTitle("Click on column title to sort")
table_model = MyTableModel(self, data_list, header)
table_view = QTableView()
table_view.setModel(table_model)
font = QFont("Verdana", 9)
table_view.setFont(font)
table_view.resizeColumnsToContents()
table_view.setSortingEnabled(True)
layout = QVBoxLayout(self)
layout.addWidget(table_view)
self.setLayout(layout)
class MyTableModel(QAbstractTableModel):
def __init__(self, parent, mylist, header, *args):
QAbstractTableModel.__init__(self, parent, *args)
self.mylist = mylist
self.header = header
def rowCount(self, parent):
return len(self.mylist)
def columnCount(self, parent):
return len(self.header)
def data(self, index, role):
if not index.isValid():
return None
elif role != Qt.DisplayRole:
return None
return self.mylist[index.row()][index.column()]
def headerData(self, col, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return self.header[col]
return None
def sort(self, col, order):
"""sort table by given column number col"""
self.emit(SIGNAL("layoutAboutToBeChanged()"))
self.mylist = sorted(self.mylist,
key=operator.itemgetter(col))
if order == Qt.DescendingOrder:
self.mylist.reverse()
self.emit(SIGNAL("layoutChanged()"))
header, data_list = getData()
app = QApplication([])
win = MyWindow(data_list, header)
win.show()
app.exec_()
Please help.
Used Qtimer to to change the model for Table view.
import operator
from PySide.QtCore import *
from PySide.QtGui import *
from urllib import request
from lxml import etree
import sched, time
s = sched.scheduler(time.time, time.sleep)
def getData():
url = "" # Removed url
response = request.urlopen(url)
html_parser = etree.HTMLParser()
tree = etree.parse(response, html_parser)
data_sets = tree.xpath("//div[#class='dataList']")
headers = ['Company Name','LTP','Change', '%Chg', 'Volume (lacs)', '30 Days % Change','365 Days % Change']
share_results = []
for element in data_sets:
result_list = []
share_comp_name = element.xpath('ul/li/p/a')[0]
result_list.append(share_comp_name.text)
share_value_list = element.xpath('ul/li/span')
counter = 0
for data in share_value_list:
result_list.append(float(str(data.text)))
counter += 1
share_results.append(tuple(result_list))
return headers, share_results
class MyWindow(QWidget):
def __init__(self, *args):
QWidget.__init__(self, *args)
self.setGeometry(300, 200, 550, 450)
self.setWindowTitle("Top Shares")
self.table_model = MyTableModel(self)
self.table_view = QTableView()
self.table_view.setModel(self.table_model)
font = QFont("Verdana", 9)
self.table_view.setFont(font)
self.table_view.resizeColumnsToContents()
self.table_view.setSortingEnabled(True)
layout = QVBoxLayout(self)
layout.addWidget(self.table_view)
self.setLayout(layout)
# Added timer
timer = QTimer(self)
timer.timeout.connect(self.show_data)
timer.start(10000)
def show_data(self):
self.table_model = MyTableModel(self)
self.table_view.setModel(self.table_model)
class MyTableModel(QAbstractTableModel):
class_counter = 0
def __init__(self, parent, *args):
QAbstractTableModel.__init__(self, parent, *args)
self.header, self.mylist = getData()
def rowCount(self, parent):
return len(self.mylist)
def columnCount(self, parent):
return len(self.header)
def data(self, index, role):
if not index.isValid():
return None
elif role != Qt.DisplayRole:
return None
return self.mylist[index.row()][index.column()]
def headerData(self, col, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return self.header[col]
return None
def sort(self, col, order):
"""sort table by given column number col"""
self.emit(SIGNAL("layoutAboutToBeChanged()"))
self.mylist = sorted(self.mylist,
key=operator.itemgetter(col))
if order == Qt.DescendingOrder:
self.mylist.reverse()
self.emit(SIGNAL("layoutChanged()"))
app = QApplication([])
win = MyWindow()
win.show()
win.repaint()
win.update()
app.exec_()
I am trying to build a program with two tabs. In Tab1 I select point coordinates (x,y) from an image into values self.a. Besides the image I also have some other UI in Tab1 (i.e. a table). Now, I want to pass the values self.a to Tab2 (without inheriting all the other stuff). Keep in mind that self.a can be constantly updated when a new point is clicked.
from PySide import QtGui, QtCore
import pandas as pd
import pyqtgraph as pg
import numpy as np
QVariant = lambda value=None: value
class Widget(QtGui.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
v_global_layout = QtGui.QVBoxLayout()
v_global_layout.addWidget(TabDialog())
v_global_layout.setAlignment(QtCore.Qt.AlignTop)
self.setLayout(v_global_layout)
class TabDialog(QtGui.QDialog):
def __init__(self, parent=None):
super().__init__(parent)
tab_widget = QtGui.QTabWidget()
tab_widget.addTab(Tab1(), "1")
tab_widget.addTab(Tab2(), "2")
main_layout = QtGui.QVBoxLayout()
main_layout.addWidget(tab_widget)
self.setLayout(main_layout)
class Tab1(QtGui.QTabWidget):
def __init__(self):
super().__init__()
layout = QtGui.QHBoxLayout()
self.fig = pg.PlotWidget(name='Example: Selecting scatter points')
self.plot_area = self.fig.plotItem
self.a = pg.ScatterPlotItem(pxMode=False)
spots = []
for i in range(10):
for j in range(10):
spots.append({'pos': (1*i, 1*j), 'size': 1, 'pen': {'color': 'w', 'width': 2},
'brush': pg.intColor(i*10+j, 100)})
self.a.addPoints(spots)
self.plot_area.addItem(self.a)
self.a.dataModel = DataFrameModel()
self.a.dataTable = QtGui.QTableView()
self.a.dataTable.setModel(self.a.dataModel)
layout.addWidget(self.a.dataTable)
layout.addWidget(self.fig)
self.setLayout(layout)
self.a.array = np.zeros((0, 2))
def clicked(self, points):
for p in points:
p.setPen('b', width=2)
position = p.viewPos()
self.array = np.append(self.array, np.array([[position.x(), position.y()]]), axis=0)
c = range(len(self.array))
c = list(map(str, c))
self.dataModel.signalUpdate(self.array, columns=c)
self.dataModel.printValues() # also: print(self.array)
self.a.sigClicked.connect(clicked)
class Tab2(QtGui.QTabWidget):
def __init__(self):
super().__init__()
layout = QtGui.QHBoxLayout()
##### Here I want to use Tab1.a and not inherit all the other stuff(layout) #####
#print("values = ", Tab1.a.array) # a should change when a new point is selected in Tab1
#####################################
self.setLayout(layout)
class DataFrameModel(QtCore.QAbstractTableModel):
""" data model for a DataFrame class """
def __init__(self):
super(DataFrameModel, self).__init__()
self.df = pd.DataFrame()
def signalUpdate(self, dataIn, columns):
self.df = pd.DataFrame(dataIn, columns)
self.layoutChanged.emit()
def printValues(self):
print("DataFrame values:\n", self.df.values)
def values(self):
return self.df.values
#------------- table display functions -----------------
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if role != QtCore.Qt.DisplayRole:
return QVariant()
if orientation == QtCore.Qt.Horizontal:
try:
return self.df.columns.tolist()[section]
except (IndexError, ):
return QVariant()
elif orientation == QtCore.Qt.Vertical:
try:
# return self.df.index.tolist()
return self.df.index.tolist()[section]
except (IndexError, ):
return QVariant()
def data(self, index, role=QtCore.Qt.DisplayRole):
if role != QtCore.Qt.DisplayRole:
return QVariant()
if not index.isValid():
return QVariant()
return QVariant(str(self.df.ix[index.row(), index.column()]))
def rowCount(self, index=QtCore.QModelIndex()):
return self.df.shape[0]
def columnCount(self, index=QtCore.QModelIndex()):
return self.df.shape[1]
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
main_window = Widget()
main_window.setGeometry(100, 100, 640, 480)
main_window.show()
sys.exit(app.exec_())
In this case, you can just use signals to get the job done.
Here you are trying to access Tab1.a like a static property, when it is not one. Ideally, we should try and decouple the different widgets. We should try and keep the dependency between them to a minimum and treat each of them as ignorant and unaware of each other. The TabDialog can be the one that knows about each of these widgets and the connections between them (In this case, Tab1 and Tab2). And hence, the TabDialog can take the responsibility of communication between these widgets.
To do this, we have the two tabs as properties of the TabDialog class like so:
# Have the tabs as this dialog's class properties
self.tab1 = Tab1(image)
self.tab2 = Tab2()
tab_widget.addTab(self.tab1, "1")
tab_widget.addTab(self.tab2, "2")
In the class Tab2, let us assume that the value you want to map with Tab1.a is points_from_tab1_a:
class Tab2(QtGui.QTabWidget):
def __init__(self):
super().__init__()
layout = QtGui.QHBoxLayout()
self.points_from_tab1_a = []
self.setLayout(layout)
Now, in TabDialog, we connect the sigClicked signal of tab1.a to a method that updates tab2.points_from_tab1_a:
self.tab1.a.sigClicked.connect(self.pointChanged)
def pointChanged(self, points):
tab2.points_from_tab1_a = tab1.a
And that should do the trick. So, your full code snippet, after these changes, would look like:
from PySide import QtGui, QtCore
import pandas as pd
import pyqtgraph as pg
import numpy as np
QVariant = lambda value=None: value
class Widget(QtGui.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
v_global_layout = QtGui.QVBoxLayout()
v_global_layout.addWidget(TabDialog())
v_global_layout.setAlignment(QtCore.Qt.AlignTop)
self.setLayout(v_global_layout)
class TabDialog(QtGui.QDialog):
def __init__(self, parent=None):
super().__init__(parent)
tab_widget = QtGui.QTabWidget()
# Have the tabs as this dialog's class properties
self.tab1 = Tab1(image)
self.tab2 = Tab2()
tab_widget.addTab(self.tab1, "1")
tab_widget.addTab(self.tab2, "2")
self.tab1.a.sigClicked.connect(self.pointChanged)
main_layout = QtGui.QVBoxLayout()
main_layout.addWidget(tab_widget)
self.setLayout(main_layout)
def pointChanged(self, points):
tab2.points_from_tab1_a = tab1.a
class Tab1(QtGui.QTabWidget):
def __init__(self):
super().__init__()
layout = QtGui.QHBoxLayout()
self.fig = pg.PlotWidget(name='Example: Selecting scatter points')
self.plot_area = self.fig.plotItem
self.a = pg.ScatterPlotItem(pxMode=False)
spots = []
for i in range(10):
for j in range(10):
spots.append({'pos': (1*i, 1*j), 'size': 1, 'pen': {'color': 'w', 'width': 2},
'brush': pg.intColor(i*10+j, 100)})
self.a.addPoints(spots)
self.plot_area.addItem(self.a)
self.a.dataModel = DataFrameModel()
self.a.dataTable = QtGui.QTableView()
self.a.dataTable.setModel(self.a.dataModel)
layout.addWidget(self.a.dataTable)
layout.addWidget(self.fig)
self.setLayout(layout)
self.a.array = np.zeros((0, 2))
def clicked(self, points):
for p in points:
p.setPen('b', width=2)
position = p.viewPos()
self.array = np.append(self.array, np.array([[position.x(), position.y()]]), axis=0)
c = range(len(self.array))
c = list(map(str, c))
self.dataModel.signalUpdate(self.array, columns=c)
self.dataModel.printValues() # also: print(self.array)
self.a.sigClicked.connect(clicked)
class Tab2(QtGui.QTabWidget):
def __init__(self):
super().__init__()
layout = QtGui.QHBoxLayout()
self.points_from_tab1_a = []
##### Here I want to use Tab1.a and not inherit all the other stuff(layout) #####
#print("values = ", Tab1.a.array) # a should change when a new point is selected in Tab1
#####################################
self.setLayout(layout)
class DataFrameModel(QtCore.QAbstractTableModel):
""" data model for a DataFrame class """
def __init__(self):
super(DataFrameModel, self).__init__()
self.df = pd.DataFrame()
def signalUpdate(self, dataIn, columns):
self.df = pd.DataFrame(dataIn, columns)
self.layoutChanged.emit()
def printValues(self):
print("DataFrame values:\n", self.df.values)
def values(self):
return self.df.values
#------------- table display functions -----------------
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if role != QtCore.Qt.DisplayRole:
return QVariant()
if orientation == QtCore.Qt.Horizontal:
try:
return self.df.columns.tolist()[section]
except (IndexError, ):
return QVariant()
elif orientation == QtCore.Qt.Vertical:
try:
# return self.df.index.tolist()
return self.df.index.tolist()[section]
except (IndexError, ):
return QVariant()
def data(self, index, role=QtCore.Qt.DisplayRole):
if role != QtCore.Qt.DisplayRole:
return QVariant()
if not index.isValid():
return QVariant()
return QVariant(str(self.df.ix[index.row(), index.column()]))
def rowCount(self, index=QtCore.QModelIndex()):
return self.df.shape[0]
def columnCount(self, index=QtCore.QModelIndex()):
return self.df.shape[1]
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
main_window = Widget()
main_window.setGeometry(100, 100, 640, 480)
main_window.show()
sys.exit(app.exec_())
Feel free to change it to suit your needs, using the signals and slots concept. Hope this was useful.