I am using QTableView to display Pandas.Thank to the codes from StackOverFlow, I can do it.
I want to update the value of the cell on the right of a ComboBox when I check or uncheck some values of the ComboBox but I am new to PyQt5 and I don't know how to do.
When I select 'A' => the cell on the right will be 'A', then I check 'B' => the cell on the right will be 'A,B'. If I deselect A => the cell on the right will be 'B'
I have been searching but I cant not find a solution.
I really need your help.
This is what I am trying to do.
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QApplication, QWidget, QTableWidget, QComboBox, QVBoxLayout
from PyQt5.QtCore import Qt
dept_list = ['A','B','C','D','E','F','G','H']
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, data):
super().__init__()
self._data = data
def data(self, index, role):
if role == Qt.DisplayRole or role == Qt.EditRole:
# See below for the nested-list data structure.
# .row() indexes into the outer list,
# .column() indexes into the sub-list
value = self._data.iloc[index.row(), index.column()]
return str(value)
def rowCount(self, index):
# The length of the outer list.
return self._data.shape[0]
def columnCount(self, index):
# The following takes the first sub-list, and returns
# the length (only works if all rows are an equal length)
return self._data.shape[1]
def flags(self, index):
return Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable
def setData(self, index, value, role):
if role == Qt.EditRole:
self._data.iloc[index.row(), index.column()] = value
self._data.iat[index.row(), self._data.shape[1]-1] = value
self.dataChanged.emit(index, index)
return True
def headerData(self, section, orientation, role):
if role == Qt.DisplayRole:
if orientation == Qt.Horizontal:
return str(self._data.columns[section])
if orientation == Qt.Vertical:
return str(self._data.index[section])
class CheckableComboBox(QComboBox):
def __init__(self):
super().__init__()
self._changed = False
self.view().pressed.connect(self.handleItemPressed)
self.checked_item=[]
for i in range(len(dept_list)):
self.addItem(dept_list[i])
self.setItemChecked(i, False)
def setItemChecked(self, index, checked=False):
item = self.model().item(index, self.modelColumn()) # QStandardItem object
if checked:
item.setCheckState(Qt.Checked)
else:
item.setCheckState(Qt.Unchecked)
def handleItemPressed(self, index):
item = self.model().itemFromIndex(index)
if item.checkState() == Qt.Checked:
item.setCheckState(Qt.Unchecked)
if item.text() in self.checked_item:
self.checked_item.remove(item.text())
print(self.checked_item)
else:
item.setCheckState(Qt.Checked)
self.checked_item.append(item.text())
print(self.checked_item)
self._changed = True
self.check_items()
def hidePopup(self):
if not self._changed:
super().hidePopup()
self._changed = False
def item_checked(self, index):
# getting item at index
item = self.model().item(index, 0)
# return true if checked else false
return item.checkState() == Qt.Checked
def check_items(self):
# blank list
checkedItems = []
# traversing the items
for i in range(self.count()):
# if item is checked add it to the list
if self.item_checked(i):
checkedItems.append(i)
# checkedItems.append(self.model().item(i, 0).text())
# call this method
self.update_labels(checkedItems)
# method to update the label
def update_labels(self, item_list):
n = ''
count = 0
# traversing the list
for i in item_list:
# if count value is 0 don't add comma
if count == 0:
n += ' % s' % i
# else value is greater then 0
# add comma
else:
n += ', % s' % i
# increment count
count += 1
# loop
for i in range(self.count()):
# getting label
text_label = self.model().item(i, 0).text()
# default state
if text_label.find('-') >= 0:
text_label = text_label.split('-')[0]
# shows the selected items
item_new_text_label = text_label + ',' + n
# setting text to combo box
self.setItemText(i, item_new_text_label)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.table = QtWidgets.QTableView()
self.combo_box_list=[]
import pandas as pd
df = pd.read_excel(r"E:\092021-12 2022 1001.xlsx", skiprows=7)
# data = pd.DataFrame([[1, 9, 2], [1, 0, -1], [3, 5, 2], [3, 3, 2], [5, 8, 9],],
# columns=["A", "B", "C"],
# index=["Row 1", "Row 2", "Row 3", "Row 4", "Row 5"],)
df['Allocation Selection'] = ''
df['Allocation']=''
self.model = TableModel(df)
self.table.setModel(self.model)
# combo = CheckableComboBox()
# for i in range(len(dept_list)):
# combo.addItem(dept_list[i])
# combo.setItemChecked(i, False)
for i in range(df.shape[0]):
combo = CheckableComboBox()
self.combo_box_list.append(combo)
# pix = QPersistentModelIndex(index)
# combo.currentIndexChanged[str].connect(lambda txt, pix=pix: self.tableView.model().setData(QModelIndex(pix), txt))
self.table.setIndexWidget(self.model.index(i, df.shape[1] - 2), combo)
self.setCentralWidget(self.table)
self.setGeometry(600, 100, 400, 200)
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
If I understand correctly, you are using QTableView to display a pandas dataframe. In this case it should be quite simple. You need to use the signal that comes from the QComboBox when an item is selected - probably currentIndexChanged(). You need to connect this to a function that will identify which items in your QComboBox are checked, and from this build the string that will go into the dataframe at the right place. Once the dataframe is updated, your QTableView should update to display the new cell of the dataframe. Without knowing more about your exact setup (the checkable combobox) I can't be more specific.
Edit: Here is a working solution. The first step was to make the combobox aware of what row it is in - in this case I just passed the row number as a variable to the combobox on creation. It's a quick and dirty way to do it - it would be better to find the row of the table that the widget is in dynamically. The second step is to define a custom signal which passes the row and the checked item list to a function in the main class which updates the dataframe at that row with the new checked list.
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QApplication, QWidget, QTableWidget, QComboBox, QVBoxLayout
from PyQt5.QtCore import Qt, pyqtSignal
dept_list = ['A','B','C','D','E','F','G','H']
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, data):
super().__init__()
self._data = data
def data(self, index, role):
if role == Qt.DisplayRole or role == Qt.EditRole:
# See below for the nested-list data structure.
# .row() indexes into the outer list,
# .column() indexes into the sub-list
value = self._data.iloc[index.row(), index.column()]
return str(value)
def rowCount(self, index):
# The length of the outer list.
return self._data.shape[0]
def columnCount(self, index):
# The following takes the first sub-list, and returns
# the length (only works if all rows are an equal length)
return self._data.shape[1]
def flags(self, index):
return Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable
def setData(self, index, value, role):
if role == Qt.EditRole:
self._data.iloc[index.row(), index.column()] = value
self._data.iat[index.row(), self._data.shape[1]-1] = value
self.dataChanged.emit(index, index)
return True
def headerData(self, section, orientation, role):
if role == Qt.DisplayRole:
if orientation == Qt.Horizontal:
return str(self._data.columns[section])
if orientation == Qt.Vertical:
return str(self._data.index[section])
class CheckableComboBox(QComboBox):
check_changed = pyqtSignal(int, list)
def __init__(self, row):
super().__init__()
self._changed = False
self.view().pressed.connect(self.handleItemPressed)
self.checked_item=[]
self.row = row
for i in range(len(dept_list)):
self.addItem(dept_list[i])
self.setItemChecked(i, False)
def setItemChecked(self, index, checked=False):
item = self.model().item(index, self.modelColumn()) # QStandardItem object
if checked:
item.setCheckState(Qt.Checked)
else:
item.setCheckState(Qt.Unchecked)
def handleItemPressed(self, index):
item = self.model().itemFromIndex(index)
if item.checkState() == Qt.Checked:
item.setCheckState(Qt.Unchecked)
if item.text() in self.checked_item:
self.checked_item.remove(item.text())
print(self.checked_item)
else:
item.setCheckState(Qt.Checked)
self.checked_item.append(item.text())
print(self.checked_item)
self._changed = True
self.check_changed.emit(self.row, self.checked_item)
self.check_items()
def hidePopup(self):
if not self._changed:
super().hidePopup()
self._changed = False
def item_checked(self, index):
# getting item at index
item = self.model().item(index, 0)
# return true if checked else false
return item.checkState() == Qt.Checked
def check_items(self):
# blank list
checkedItems = []
# traversing the items
for i in range(self.count()):
# if item is checked add it to the list
if self.item_checked(i):
checkedItems.append(i)
# checkedItems.append(self.model().item(i, 0).text())
# call this method
self.update_labels(checkedItems)
# method to update the label
def update_labels(self, item_list):
n = ''
count = 0
# traversing the list
for i in item_list:
# if count value is 0 don't add comma
if count == 0:
n += ' % s' % i
# else value is greater then 0
# add comma
else:
n += ', % s' % i
# increment count
count += 1
# loop
for i in range(self.count()):
# getting label
text_label = self.model().item(i, 0).text()
# default state
if text_label.find('-') >= 0:
text_label = text_label.split('-')[0]
# shows the selected items
item_new_text_label = text_label + ',' + n
# setting text to combo box
self.setItemText(i, item_new_text_label)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.table = QtWidgets.QTableView()
self.combo_box_list=[]
import pandas as pd
#df = pd.read_excel(r"E:\092021-12 2022 1001.xlsx", skiprows=7)
self.df = pd.DataFrame([[1, 9, 2], [1, 0, -1], [3, 5, 2], [3, 3, 2], [5, 8, 9],],
columns=["A", "B", "C"])
self.df['Allocation Selection'] = ''
self.df['Allocation']=''
self.model = TableModel(self.df)
self.table.setModel(self.model)
for i in range(self.df.shape[0]):
combo = CheckableComboBox(i)
combo.check_changed.connect(self.update_df)
self.combo_box_list.append(combo)
self.table.setIndexWidget(self.model.index(i, self.df.shape[1] - 2), combo)
self.setCentralWidget(self.table)
self.setGeometry(600, 100, 400, 200)
def update_df(self, row, checked):
print(row, checked)
self.df.at[row,'Allocation'] = checked
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
Related
I'm using a custom Pandas Model(QAbstractTableModel) which is editable. I'm trying to add rows and then the user should be able to edit them and pass the values to the dataframe. Unfortunately, the example I found from PyQt5: Implement removeRows for pandas table model doesn't work when I add rows.
This is my model class:
class pandasModel(QAbstractTableModel):
def __init__(self, dataframe: pd.DataFrame, parent=None):
QAbstractTableModel.__init__(self, parent)
self._dataframe = dataframe
def rowCount(self, parent=QModelIndex()) -> int:
""" Override method from QAbstractTableModel
Return row count of the pandas DataFrame
"""
if parent == QModelIndex():
return len(self._dataframe)
return 0
def columnCount(self, parent=QModelIndex()) -> int:
"""Override method from QAbstractTableModel
Return column count of the pandas DataFrame
"""
if parent == QModelIndex():
return len(self._dataframe.columns)
return 0
def data(self, index: QModelIndex, role=Qt.ItemDataRole):
"""Override method from QAbstractTableModel
Return data cell from the pandas DataFrame
"""
if not index.isValid():
return None
if index.isValid():
if role == Qt.DisplayRole or role == Qt.EditRole:
try:
value = self._dataframe.iloc[index.row(), index.column()]
return str(value)
value=float(value)
except ValueError:
msg = QMessageBox()
msg.setIcon(QMessageBox.Critical)
msg.setText("Please, use integers or decimals.")
msg.setWindowTitle("Error")
msg.setStandardButtons(QMessageBox.Ok)
msg.exec_()
return 0
def headerData(
self, section: int, orientation: Qt.Orientation, role: Qt.ItemDataRole):
"""Override method from QAbstractTableModel
Return dataframe index as vertical header data and columns as horizontal header data.
"""
if role == Qt.DisplayRole:
if orientation == Qt.Horizontal:
return str(self._dataframe.columns[section])
if orientation == Qt.Vertical:
return str(self._dataframe.index[section])
return None
def setData(self, index, value, role):
if role == Qt.EditRole:
self._dataframe.iloc[index.row(), index.column()] = value
return True
return False
def flags(self, index):
return Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable
def removeRows(self, position, rows, parent=QModelIndex()):
start, end = position, position + rows - 1
if 0 <= start <= end and end < self.rowCount(parent):
self.beginRemoveRows(parent, start, end)
for index in range(start, end + 1):
self._dataframe.drop(index, inplace=True)
self._dataframe.reset_index(drop=True, inplace=True)
self.endRemoveRows()
return True
return False
def insertRows(self, position, rows, parent=QModelIndex()):
start, end = position, position + rows - 1
if 0 <= start <= end:
self.beginInsertRows(parent, start, end)
for index in range(start, end + 1):
default_row = [[None] for _ in range(self._dataframe.shape[1])]
new_df = pd.DataFrame(dict(zip(list(self._dataframe.columns), default_row)))
self._dataframe = pd.concat([self._dataframe, new_df])
self._dataframe = self._dataframe.reset_index(drop=True)
self.endInsertRows()
return True
return False
My global dataframe to be used:
regionalPop = {'Region':[1,1],'Starting_Index': [0,0], 'Ending_Index': [0,0]}
dfRegion=pd.DataFrame(data=regionalPop)
And the main class:
class regionalPlot(QtWidgets.QMainWindow,Ui_regionalWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setupUi(self)
#populating REGIONS table model
global dfRegion
regionalPop = {'Region':[1,1],'Starting_Index': [0,0], 'Ending_Index': [0,0]}
dfRegion=pd.DataFrame(data=regionalPop)
self.model1 = pandasModel(dfRegion)
#sorting enable
self.proxyModel = QSortFilterProxyModel()
self.proxyModel.setSourceModel(self.model1)
#binding model to table
self.tableView_2.setModel(self.proxyModel)
#-------------------------------------
#Button Actions
#add_button pressed
self.actionAdd_Region.triggered.connect(lambda: self.insert_row())
#minus button pressed
self.actionDelete_Region.triggered.connect(lambda: self.delete_row())
#update pressed
self.actionUpdate_Tables.triggered.connect(lambda: self.updateTable())
#ACTIONS
def delete_row(self):
if self.tableView_2.selectionModel().hasSelection():
indexes =[QPersistentModelIndex(index) for index in self.tableView_2.selectionModel().selectedRows()]
for index in indexes:
print('Deleting row %d...' % index.row())
self.model1.removeRow(index.row())
def insert_row(self):
global dfRegion
self.model1.insertRows(self.model1.rowCount(), 1)
print (dfRegion)
def updateTable(self):
#populating table model
self.model1 = pandasModel(dfRegion)
#sorting enable
self.proxyModel = QSortFilterProxyModel()
self.proxyModel.setSourceModel(self.model1)
#binding model to table
self.tableView_2.setModel(self.proxyModel)
And the Ui_regionalWindow:
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_regionalWindow(object):
def setupUi(self, regionalWindow):
regionalWindow.setObjectName("regionalWindow")
regionalWindow.resize(1184, 996)
self.centralwidget = QtWidgets.QWidget(regionalWindow)
self.centralwidget.setObjectName("centralwidget")
self.tableView_2 = QtWidgets.QTableView(self.centralwidget)
self.tableView_2.setGeometry(QtCore.QRect(10, 90, 256, 831))
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.tableView_2.sizePolicy().hasHeightForWidth())
self.tableView_2.setSizePolicy(sizePolicy)
self.tableView_2.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustIgnored)
self.tableView_2.setAlternatingRowColors(True)
self.tableView_2.setObjectName("tableView_2")
self.label_2 = QtWidgets.QLabel(self.centralwidget)
self.label_2.setGeometry(QtCore.QRect(10, 0, 241, 112))
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.label_2.sizePolicy().hasHeightForWidth())
self.label_2.setSizePolicy(sizePolicy)
font = QtGui.QFont()
font.setPointSize(14)
self.label_2.setFont(font)
self.label_2.setObjectName("label_2")
regionalWindow.setCentralWidget(self.centralwidget)
self.toolBar = QtWidgets.QToolBar(regionalWindow)
self.toolBar.setIconSize(QtCore.QSize(40, 40))
self.toolBar.setObjectName("toolBar")
regionalWindow.addToolBar(QtCore.Qt.TopToolBarArea, self.toolBar)
self.actionAdd_Region = QtWidgets.QAction(regionalWindow)
self.actionAdd_Region.setObjectName("actionAdd_Region")
self.actionDelete_Region = QtWidgets.QAction(regionalWindow)
self.actionDelete_Region.setObjectName("actionDelete_Region")
self.actionUpdate_Tables = QtWidgets.QAction(regionalWindow)
self.actionUpdate_Tables.setObjectName("actionUpdate_Tables")
self.toolBar.addAction(self.actionAdd_Region)
self.toolBar.addAction(self.actionDelete_Region)
self.toolBar.addSeparator()
self.toolBar.addAction(self.actionUpdate_Tables)
self.toolBar.addSeparator()
self.toolBar.addSeparator()
self.retranslateUi(regionalWindow)
QtCore.QMetaObject.connectSlotsByName(regionalWindow)
def retranslateUi(self, regionalWindow):
_translate = QtCore.QCoreApplication.translate
regionalWindow.setWindowTitle(_translate("regionalWindow", "Regional Analysis"))
self.label_2.setText(_translate("regionalWindow", "Regions"))
self.toolBar.setWindowTitle(_translate("regionalWindow", "toolBar"))
self.actionAdd_Region.setText(_translate("regionalWindow", "Add Region"))
self.actionAdd_Region.setToolTip(_translate("regionalWindow", "Add region of the signal. Left field is the start and right field is the end of the region of the signal."))
self.actionDelete_Region.setText(_translate("regionalWindow", "Delete Region"))
self.actionDelete_Region.setToolTip(_translate("regionalWindow", "Delete an existing region of the signal."))
self.actionUpdate_Tables.setText(_translate("regionalWindow", "Update Tables"))
self.actionUpdate_Tables.setToolTip(_translate("regionalWindow", "Click to update changes to the tabular data"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
regionalWindow = QtWidgets.QMainWindow()
ui = Ui_regionalWindow()
ui.setupUi(regionalWindow)
regionalWindow.show()
sys.exit(app.exec_())
I'm using three buttons one to add rows, one to remove rows, and one to update the table. Unfortunately, when I add rows to the dataframe it doesn't change anything and it keeps the same rows as defined before the editing, but when I trigger editing the values are changed.
To summarise:
I need to add rows to the dataframe "dfRegion" with this model. Even though I see a visual change in the GUI when I click self.actionAdd_Region, I print the dfRegion and there are no rows added in the dataframe.
I have a QTableView that has a column with True/False values. To change value in this column I use a QDataWidgetMapper checkBox.
I wish to change a value immediately after clicking a checkbox. Usually a change is made after checkbox looses focus (in other words you need to make extra click somewhere else in application).
I have tried to do it like this: checkBox.stateChanged.connect(lambda: data_mapper.submit()), but I don't like that using this method too many signals are emitted: first it emits signals that all mapped items were changed, and after checkbox looses focus - a signal that only one item was changed.
So the goal is to update model immediately after clicking a checkbox and have only one signal emitted.
Code:
from PyQt5 import QtCore, QtGui, QtWidgets
import sys
class Mainwindow(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.table = QtWidgets.QTableView()
self.widget_1 = Widget_1(self)
self.layout_1 = QtWidgets.QHBoxLayout()
self.layout_1.addWidget(self.table)
self.layout_1.addWidget(self.widget_1)
self.setLayout(self.layout_1)
headers = ['State', 'Prorerty_1', 'Prorerty_2']
data = [
[True, '1', '2'],
[False, '3', '4']
]
self.model = ListModel(data, headers)
self.table.setModel(self.model)
self.table.setSelectionBehavior(self.table.SelectRows)
self.table.clicked.connect(self.set_data_mapper)
self.model.dataChanged.connect(lambda value: print(value.row(), value.column(), value.data()))
def set_data_mapper(self):
position = self.table.selectionModel().selectedIndexes()[0].row()
self.widget_1.data_mapper.setModel(self.model)
self.widget_1.data_mapper.addMapping(self.widget_1.checkBox, 0)
self.widget_1.data_mapper.addMapping(self.widget_1.lineEdit, 1)
self.widget_1.data_mapper.setCurrentIndex(position)
self.widget_1.checkBox.stateChanged.connect(lambda: self.widget_1.data_mapper.submit())
class Widget_1(QtWidgets.QWidget):
def __init__(self, parent):
super().__init__()
self.parent = parent
self.layout_1 = QtWidgets.QGridLayout()
self.state = QtWidgets.QLabel('State')
self.checkBox = QtWidgets.QCheckBox()
self.property = QtWidgets.QLabel('Prop.1')
self.lineEdit = QtWidgets.QLineEdit()
self.layout_1.addWidget(self.state, 0, 0)
self.layout_1.addWidget(self.checkBox, 0, 1)
self.layout_1.addWidget(self.property, 1, 0)
self.layout_1.addWidget(self.lineEdit, 1, 1)
self.setLayout(self.layout_1)
self.data_mapper = QtWidgets.QDataWidgetMapper()
class ListModel(QtCore.QAbstractTableModel):
def __init__(self, data_list = [[]], headers = [], parent = None):
super(ListModel, self).__init__()
self.data_list = data_list
self.headers = headers
def rowCount(self, parent):
return len(self.data_list)
def columnCount(self, parent):
return len(self.headers)
def data(self, index, role):
if role == QtCore.Qt.DisplayRole:
row = index.row()
column = index.column()
value = self.data_list[row][column]
return value
if role == QtCore.Qt.EditRole:
row = index.row()
column = index.column()
value = self.data_list[row][column]
return value
if role == QtCore.Qt.FontRole:
if index.column() == 0:
boldfont = QtGui.QFont()
boldfont.setBold(True)
return boldfont
def setData(self, index, value, role = QtCore.Qt.EditRole):
if role == QtCore.Qt.EditRole:
row = index.row()
column = index.column()
self.data_list[row][column] = value
self.dataChanged.emit(index, index)
return True
return False
def headerData(self, section, orientation, role):
if role == QtCore.Qt.DisplayRole:
if orientation == QtCore.Qt.Horizontal:
return self.headers[section]
if __name__ == '__main__':
app = QtWidgets.QApplication([])
application = Mainwindow()
application.show()
sys.exit(app.exec())
By default the QDataWidgetMapper policy is "AutoSubmit" which updates the model if any widget loses focus, so if you want it not to be emitted when it loses focus then you must use the "ManualSubmit" policy. On the other hand, the connection must be done only once since if you do it n times then n signals will be emitted and in your case "set_data_mapper is invoked every time the button is pressed.
class Mainwindow(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.table = QtWidgets.QTableView(
selectionBehavior=QtWidgets.QAbstractItemView.SelectRows
)
self.widget_1 = Widget_1(self)
lay = QtWidgets.QHBoxLayout(self)
lay.addWidget(self.table)
lay.addWidget(self.widget_1)
headers = ["State", "Prorerty_1", "Prorerty_2"]
data = [[True, "1", "2"], [False, "3", "4"]]
self.model = ListModel(data, headers)
self.table.setModel(self.model)
self.table.setSelectionBehavior(self.table.SelectRows)
self.widget_1.data_mapper.setModel(self.model)
self.widget_1.data_mapper.addMapping(self.widget_1.checkBox, 0)
self.widget_1.data_mapper.addMapping(self.widget_1.lineEdit, 1)
self.widget_1.data_mapper.setSubmitPolicy(
QtWidgets.QDataWidgetMapper.ManualSubmit
)
self.widget_1.checkBox.stateChanged.connect(self.widget_1.data_mapper.submit)
self.widget_1.lineEdit.textChanged.connect(self.widget_1.data_mapper.submit)
self.table.clicked.connect(self.update_current_index)
self.model.dataChanged.connect(
lambda value: print(value.row(), value.column(), value.data())
)
def update_current_index(self, index):
position = index.row()
self.widget_1.data_mapper.setCurrentIndex(position)
I have figured out this problem: now only one signal is emitted immediately after change.
First, I used "ManualSubmit" policy for QDataWidgetMapper , as #eyllanesc told.
Second, I wrote my own custom function for both lineEdit and a checkBox instead data_mapper.submit().
Manual connection between mapped widgets and model is done once in 'for position in range(len(self.model.data_list))' loop.
Also, I want to pay attention that inside 'data_mapper_settings' function I used 'clicked' instead 'stateChanged' for checBox and 'textEdited' instead 'textChanged' for lineEdit to avoid emitting extra signals when other row is chosen.
Still I wonder if there's more elegant way to solve this problem
from PyQt5 import QtCore, QtGui, QtWidgets
import sys
class Mainwindow(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.table = QtWidgets.QTableView()
self.widget_1 = Widget_1(self)
self.layout_1 = QtWidgets.QHBoxLayout()
self.layout_1.addWidget(self.table)
self.layout_1.addWidget(self.widget_1)
self.setLayout(self.layout_1)
headers = ['State', 'Prorerty_1', 'Prorerty_2']
data = [
[True, '1', '2'],
[False, '3', '4']
]
self.model = ListModel(data, headers)
self.table.setModel(self.model)
self.table.setSelectionBehavior(self.table.SelectRows)
self.table.clicked.connect(lambda index: self.set_data_mapper(index))
for position in range(len(self.model.data_list)):
self.data_mapper_settings(position)
self.model.dataChanged.connect(lambda index: print(index.row(), index.column(), index.data()))
def set_data_mapper(self, index):
position = index.row()
self.widget_1.data_mapper.setModel(self.model)
self.widget_1.data_mapper.addMapping(self.widget_1.checkBox, 0)
self.widget_1.data_mapper.addMapping(self.widget_1.lineEdit, 1)
self.widget_1.data_mapper.setCurrentIndex(position)
def data_mapper_settings(self, position):
self.widget_1.checkBox.clicked.connect(lambda value: self.submit_checkbox(value, position))
self.widget_1.lineEdit.textEdited.connect(lambda value: self.submit_lineEdit(value, position))
def submit_checkbox(self, value, position):
if position == self.table.selectionModel().selectedIndexes()[0].row():
self.model.setData(self.model.index(position, 0), value)
def submit_lineEdit(self, value, position):
if position == self.table.selectionModel().selectedIndexes()[0].row():
self.model.setData(self.model.index(position, 1), value)
class Widget_1(QtWidgets.QWidget):
def __init__(self, parent):
super().__init__()
self.parent = parent
self.layout_1 = QtWidgets.QGridLayout()
self.state = QtWidgets.QLabel('State')
self.checkBox = QtWidgets.QCheckBox()
self.property = QtWidgets.QLabel('Prop.1')
self.lineEdit = QtWidgets.QLineEdit()
self.layout_1.addWidget(self.state, 0, 0)
self.layout_1.addWidget(self.checkBox, 0, 1)
self.layout_1.addWidget(self.property, 1, 0)
self.layout_1.addWidget(self.lineEdit, 1, 1)
self.setLayout(self.layout_1)
self.data_mapper = QtWidgets.QDataWidgetMapper()
self.data_mapper.setSubmitPolicy(QtWidgets.QDataWidgetMapper.ManualSubmit)
class ListModel(QtCore.QAbstractTableModel):
def __init__(self, data_list = [[]], headers = [], parent = None):
super(ListModel, self).__init__()
self.data_list = data_list
self.headers = headers
def rowCount(self, parent):
return len(self.data_list)
def columnCount(self, parent):
return len(self.headers)
def data(self, index, role):
if role == QtCore.Qt.DisplayRole:
row = index.row()
column = index.column()
value = self.data_list[row][column]
return value
if role == QtCore.Qt.EditRole:
row = index.row()
column = index.column()
value = self.data_list[row][column]
return value
if role == QtCore.Qt.FontRole:
if index.column() == 0:
boldfont = QtGui.QFont()
boldfont.setBold(True)
return boldfont
def setData(self, index, value, role = QtCore.Qt.EditRole):
if role == QtCore.Qt.EditRole:
row = index.row()
column = index.column()
self.data_list[row][column] = value
self.dataChanged.emit(index, index)
return True
return False
def headerData(self, section, orientation, role):
if role == QtCore.Qt.DisplayRole:
if orientation == QtCore.Qt.Horizontal:
return self.headers[section]
def flags(self, index):
return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled | QtCore.Qt.ItemIsUserCheckable
if __name__ == '__main__':
app = QtWidgets.QApplication([])
application = Mainwindow()
application.show()
sys.exit(app.exec())
I have a subclassed QAbstractTableModel
to display the data in the Table View in full size without scrolling I have turned of the Scrollbars
to get ridd of the white space around the Table View I have set the vertical/horizontal table length to a specific value.
Problem is that
I have added a add/deleate row Method to the Model so the Table View expands/shrinks now
to adjust the Table View behavior to display the data in full size and without white space I have set the horizontal Header to
table_view.horizontalHeader().setStretchLastSection(True)
which cuts off the white space in horicontal direction correctly
the same operation for the vertical header cuts of the white space too but does over strech the last row
I tryed to set each row to a default size with
table_view.verticalHeader().setSectionResizeMode(qtw.QHeaderView.Fixed)
table_view.verticalHeader().setDefaultSectionSize(40)
but this turns the white space on again
In short form: Im looking for a way to display the model data in the Table View in Full Size without white Space while beeing able to deleate/insert a row
code example
#!/usr/bin/env python
"""
"""
import sys
import re
from PyQt5 import QtWidgets as qtw
from PyQt5 import QtCore as qtc
from PyQt5.QtCore import Qt
from PyQt5 import QtGui as qtg
class ViewModel(qtc.QAbstractTableModel):
def __init__(self, input_data=None):
super().__init__()
self.input_data = input_data or [["data","data","data","data"],["data","data","data","data"]]
#
def data(self, index, role): # parameter index, role are needed !
"""
"""
if role == qtc.Qt.DisplayRole:
try:
text = self.input_data[index.row()][index.column()]
except IndexError:
text = None
return text
def rowCount(self, index=qtc.QModelIndex()):
return 0 if index.isValid() else len(self.input_data)
def columnCount(self, index):
return len(self.input_data[0])
def insertRows(self, position, rows, parent=qtc.QModelIndex()):
print(position) # -1
position = (position + self.rowCount()) if position < 0 else position
start = position
end = position + rows - 1
if end <= 8:
self.beginInsertRows(parent, start, end)
self.input_data.append([])
self.endInsertRows()
return True
else:
return False
def removeRows(self, position, rows, parent=qtc.QModelIndex()):
position = (position + self.rowCount()) if position < 0 else position
start = position
end = position + rows - 1
if end >= 1:
self.beginRemoveRows(parent, start, end)
del self.input_data[start:end + 1]
self.endRemoveRows()
return True
else:
return False
def headerData(self, section, orientation, role):
if role == qtc.Qt.DisplayRole:
if orientation == qtc.Qt.Horizontal:
return "hight " + str(section+1) + " /mm"
if orientation == qtc.Qt.Vertical:
return "width " + str(section+1)
def flags(self, index):
return qtc.Qt.ItemIsEditable | qtc.Qt.ItemIsSelectable | qtc.Qt.ItemIsEnabled
def setData(self, index, value, role=qtc.Qt.EditRole):
if role == qtc.Qt.EditRole:
try:
row = index.row()
column = index.column()
pattern = '^[\d]+(?:,[\d]+)?$'
if re.fullmatch(pattern, value, flags=0):
print("true")
self.input_data[row][column] = value # float
else:
print("nope")
pass
return True
except ValueError:
print("not a number")
return False
def display_model_data(self):
print(self.input_data)
class MainWindow(qtw.QWidget):
def __init__(self):
super().__init__()
# geometry
self.setGeometry(900, 360, 700, 800)
# View
table_view = qtw.QTableView()
# done # turn scroll bars off
table_view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
table_view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.model = ViewModel()
table_view.setModel(self.model)
table_view.horizontalHeader().setStretchLastSection(True)
# table_view.verticalHeader().setStretchLastSection(True)
table_view.verticalHeader().setSectionResizeMode(qtw.QHeaderView.Fixed)
table_view.verticalHeader().setDefaultSectionSize(24)
table_view.verticalHeader().setStretchLastSection(True)
# verticalHeader->setSectionResizeMode(QHeaderView::Fixed);
# verticalHeader->setDefaultSectionSize(24);
# widgets
self.insert_row_button = qtw.QPushButton("insert row")
self.deleate_row_button = qtw.QPushButton("deleate row")
# layout
layout = qtw.QVBoxLayout()
layout.addWidget(table_view)
layout.addWidget(self.insert_row_button)
layout.addWidget(self.deleate_row_button)
self.setLayout(layout)
self.show()
# function
self.insert_row_button.clicked.connect(lambda: self.model.insertRows(-1, 1))
self.deleate_row_button.clicked.connect(lambda: self.model.removeRows(-1, 1))
if __name__ == '__main__':
app = qtw.QApplication(sys.argv)
w = MainWindow()
sys.exit(app.exec_())
space can't disappear magically. let's say the total table height is 600. if there are two rows in the table, the first row is 40. then, the second one is 600 - 40 = 560 if you don't wan't blank at the bottom of the table. if you set height of each row to 40, the height of the blank space would be 600 - 2 * 40 = 520. you can't require (a total height 600) + (two rows, 40 for each) + (no blank space at the bottom).
So, let me guess, you want (a. no blank space at the bottom) + (b, space is evenly split into row, so that the last row won't look weird.). If that if the case, I've edited your code to below which explains everything:
"""
"""
import sys
import re
from PyQt5 import QtWidgets as qtw
from PyQt5 import QtCore as qtc
from PyQt5.QtCore import Qt
from PyQt5 import QtGui as qtg
class ViewModel(qtc.QAbstractTableModel):
def __init__(self, input_data=None):
super().__init__()
self.input_data = input_data or [["data","data","data","data"],["data","data","data","data"]]
#
def data(self, index, role): # parameter index, role are needed !
"""
"""
if role == qtc.Qt.DisplayRole:
try:
text = self.input_data[index.row()][index.column()]
except IndexError:
text = None
return text
def rowCount(self, index=qtc.QModelIndex()):
return 0 if index.isValid() else len(self.input_data)
def columnCount(self, index):
return len(self.input_data[0])
def insertRows(self, position, rows, parent=qtc.QModelIndex()):
print(position) # -1
position = (position + self.rowCount()) if position < 0 else position
start = position
end = position + rows - 1
if end <= 8:
self.beginInsertRows(parent, start, end)
self.input_data.append([])
self.endInsertRows()
return True
else:
return False
def removeRows(self, position, rows, parent=qtc.QModelIndex()):
position = (position + self.rowCount()) if position < 0 else position
start = position
end = position + rows - 1
if end >= 1:
self.beginRemoveRows(parent, start, end)
del self.input_data[start:end + 1]
self.endRemoveRows()
return True
else:
return False
def headerData(self, section, orientation, role):
if role == qtc.Qt.DisplayRole:
if orientation == qtc.Qt.Horizontal:
return "hight " + str(section+1) + " /mm"
if orientation == qtc.Qt.Vertical:
return "width " + str(section+1)
def flags(self, index):
return qtc.Qt.ItemIsEditable | qtc.Qt.ItemIsSelectable | qtc.Qt.ItemIsEnabled
def setData(self, index, value, role=qtc.Qt.EditRole):
if role == qtc.Qt.EditRole:
try:
row = index.row()
column = index.column()
pattern = '^[\d]+(?:,[\d]+)?$'
if re.fullmatch(pattern, value, flags=0):
print("true")
self.input_data[row][column] = value # float
else:
print("nope")
pass
return True
except ValueError:
print("not a number")
return False
def display_model_data(self):
print(self.input_data)
class NoBlankSpaceAtBottomEnvenlySplitTableView(qtw.QTableView):
def sizeHintForRow(self, row):
row_count = self.model().rowCount()
height = self.viewport().height()
row_height = int(height/row_count)
if row < row_count - 1:
return row_height
else:
return super().sizeHintForRow(row)
class MainWindow(qtw.QWidget):
def __init__(self):
super().__init__()
# geometry
self.setGeometry(900, 360, 700, 800)
# View
# table_view = qtw.QTableView()
table_view = NoBlankSpaceAtBottomEnvenlySplitTableView()
# done # turn scroll bars off
table_view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
table_view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.model = ViewModel()
table_view.setModel(self.model)
table_view.horizontalHeader().setStretchLastSection(True)
table_view.verticalHeader().setStretchLastSection(True)
# table_view.verticalHeader().setSectionResizeMode(qtw.QHeaderView.Fixed)
#table_view.verticalHeader().setDefaultSectionSize(24)
table_view.verticalHeader().setSectionResizeMode(
qtw.QHeaderView.ResizeToContents) # Add this line
table_view.verticalHeader().setStretchLastSection(True)
# verticalHeader->setSectionResizeMode(QHeaderView::Fixed);
# verticalHeader->setDefaultSectionSize(24);
# widgets
self.insert_row_button = qtw.QPushButton("insert row")
self.deleate_row_button = qtw.QPushButton("deleate row")
# layout
layout = qtw.QVBoxLayout()
layout.addWidget(table_view)
layout.addWidget(self.insert_row_button)
layout.addWidget(self.deleate_row_button)
self.setLayout(layout)
self.show()
# function
self.insert_row_button.clicked.connect(lambda: self.model.insertRows(-1, 1))
self.deleate_row_button.clicked.connect(lambda: self.model.removeRows(-1, 1))
if __name__ == '__main__':
app = qtw.QApplication(sys.argv)
w = MainWindow()
sys.exit(app.exec_())
Edit: Table auto adjusts its height according to rows
import sys
import re
from PyQt5 import QtWidgets as qtw
from PyQt5 import QtCore as qtc
from PyQt5.QtCore import Qt, QSize
from PyQt5.QtWidgets import QSizePolicy
from PyQt5 import QtGui as qtg
class ViewModel(qtc.QAbstractTableModel):
def __init__(self, input_data=None):
super().__init__()
self.input_data = input_data or [["data","data","data","data"],["data","data","data","data"]]
#
def data(self, index, role): # parameter index, role are needed !
"""
"""
if role == qtc.Qt.DisplayRole:
try:
text = self.input_data[index.row()][index.column()]
except IndexError:
text = None
return text
def rowCount(self, index=qtc.QModelIndex()):
return 0 if index.isValid() else len(self.input_data)
def columnCount(self, index):
return len(self.input_data[0])
def insertRows(self, position, rows, parent=qtc.QModelIndex()):
print(position) # -1
position = (position + self.rowCount()) if position < 0 else position
start = position
end = position + rows - 1
if end <= 8:
self.beginInsertRows(parent, start, end)
self.input_data.append([])
self.endInsertRows()
return True
else:
return False
def removeRows(self, position, rows, parent=qtc.QModelIndex()):
position = (position + self.rowCount()) if position < 0 else position
start = position
end = position + rows - 1
if end >= 1:
self.beginRemoveRows(parent, start, end)
del self.input_data[start:end + 1]
self.endRemoveRows()
return True
else:
return False
def headerData(self, section, orientation, role):
if role == qtc.Qt.DisplayRole:
if orientation == qtc.Qt.Horizontal:
return "hight " + str(section+1) + " /mm"
if orientation == qtc.Qt.Vertical:
return "width " + str(section+1)
def flags(self, index):
return qtc.Qt.ItemIsEditable | qtc.Qt.ItemIsSelectable | qtc.Qt.ItemIsEnabled
def setData(self, index, value, role=qtc.Qt.EditRole):
if role == qtc.Qt.EditRole:
try:
row = index.row()
column = index.column()
pattern = '^[\d]+(?:,[\d]+)?$'
if re.fullmatch(pattern, value, flags=0):
print("true")
self.input_data[row][column] = value # float
else:
print("nope")
pass
return True
except ValueError:
print("not a number")
return False
def display_model_data(self):
print(self.input_data)
class AutoExpandingTableView(qtw.QTableView):
# def sizeHintForRow(self, row):
# row_count = self.model().rowCount()
# height = self.viewport().height()
# row_height = int(height/row_count)
# if row < row_count - 1:
# return row_height
# else:
# return super().sizeHintForRow(row)
def sizeHint(self):
viewport_size_hint = self.viewportSizeHint()
return QSize(
self.width(),
viewport_size_hint.height()
)
class MainWindow(qtw.QWidget):
def __init__(self):
super().__init__()
# geometry
self.setGeometry(900, 360, 700, 800)
# View
# table_view = qtw.QTableView()
table_view = AutoExpandingTableView()
table_view.setSizePolicy(
QSizePolicy.Expanding,
QSizePolicy.Preferred
)
# done # turn scroll bars off
table_view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
table_view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.model = ViewModel()
table_view.setModel(self.model)
table_view.model().rowsInserted.connect(table_view.adjustSize)
table_view.model().rowsRemoved.connect(table_view.adjustSize)
table_view.horizontalHeader().setStretchLastSection(True)
# table_view.verticalHeader().setStretchLastSection(True)
# table_view.verticalHeader().setSectionResizeMode(qtw.QHeaderView.Fixed)
#table_view.verticalHeader().setDefaultSectionSize(24)
table_view.verticalHeader().setSectionResizeMode(
qtw.QHeaderView.ResizeToContents) # Add this line
# widgets
self.insert_row_button = qtw.QPushButton("insert row")
self.deleate_row_button = qtw.QPushButton("deleate row")
# layout
layout = qtw.QVBoxLayout()
layout.addWidget(table_view)
layout.addStretch()
layout.addWidget(self.insert_row_button)
layout.addWidget(self.deleate_row_button)
self.setLayout(layout)
self.show()
# function
self.insert_row_button.clicked.connect(lambda: self.model.insertRows(-1, 1))
self.deleate_row_button.clicked.connect(lambda: self.model.removeRows(-1, 1))
if __name__ == '__main__':
app = qtw.QApplication(sys.argv)
w = MainWindow()
sys.exit(app.exec_())
The most important aspect to consider is the sizeHint(), that is the recommended size a widget suggests to the layout that contains it.
Item views are tricky, though. They might have headers, their content could change many times during the lifespan of the program, and each item might have different sizes (which the user could interactively modify).
To achieve what you want, you have to use updateGeometry():
Notifies the layout system that this widget has changed and may need to change geometry.
Call this function if the sizeHint() or sizePolicy() have changed.
Note that calling adjustSize() is not suggested for this.
The size hint of an item view then must take into account the (visible) headers and the frame width, since all QAbstractItemView descendants inherit from QFrame.
Finally, to ensure that the size hint is dynamically adjusted and the layout system is notified about it, you should also connect all the correct signals that the model AND the header might send.
Note that, while you can connect all those signals externally, it is usually better to let the class itself take care of it internally.
class ExpandingTableView(qtw.QTableView):
shown = False
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
self.verticalHeader().sectionResized.connect(self.updateGeometry)
self.verticalHeader().sectionCountChanged.connect(self.updateGeometry)
def setVerticalHeader(self, header):
self.verticalHeader().sectionResized.disconnect(self.updateGeometry)
self.verticalHeader().sectionCountChanged.disconnect(self.updateGeometry)
super().setVerticalHeader(header)
header.sectionResized.connect(self.updateGeometry)
header.sectionCountChanged.connect(self.updateGeometry)
def setModel(self, model):
if self.model():
self.model().rowsInserted.disconnect(self.updateGeometry)
self.model().rowsRemoved.disconnect(self.updateGeometry)
super().setModel(model)
if model:
model.rowsInserted.connect(self.updateGeometry)
model.rowsRemoved.connect(self.updateGeometry)
self.updateGeometry()
# optional, if you want to ensure that a minimum height is always respected
def updateGeometry(self):
self.setMinimumHeight(min(self.sizeHint().height(),
self.verticalHeader().defaultSectionSize() * 8))
super().updateGeometry()
def sizeHint(self):
height = 0
if self.horizontalHeader().isVisible():
height += self.horizontalHeader().height()
height += self.verticalHeader().length() + self.frameWidth() * 2
return QSize(super().sizeHint().width(), height)
def showEvent(self, event):
super().showEvent(event)
# when the view is shown the first time it might not have computed the
# correct size hint, let's ensure that we notify the underlying
# layout manager(s)
if not self.shown:
self.shown = True
self.updateGeometry()
I'm using PyQt5 and would like to mark the line with a single click, that is, make it appear in a different color, e.g. in red.
After another click on the same line, the marking should be canceled again.
I have tried different approaches (how to see in my code marked as comments), but didn't find a solution.
class TableViewModel(QtCore.QAbstractTableModel):
def __init__(self, records, parent=None):
super().__init__(parent)
self.datatable = records
self.color_enabled = False
self.color_back = QtCore.Qt.magenta
self.data_changed = pyqtSignal(QtCore.QModelIndex,QtCore.QModelIndex)
def rowCount(self, parent=QtCore.QModelIndex):
return len(self.datatable)
def columnCount(self, parent=QtCore.QModelIndex):
return len(self.datatable[0]) if self.datatable else 0
def data(self, index, role, **kwargs): #role=QtCore.Qt.DisplayRole?
#reason = kwargs.get('r', None)
if index.isValid() and role == QtCore.Qt.DisplayRole:
return str(self.datatable[index.row()][index.column()])
if index.isValid() and role == QtCore.Qt.BackgroundRole and self.color_enabled:
return gui.QBrush(gui.QColor(255, 0, 43)) #red
def row_clicked(self, index, role = QtCore.Qt.BackgroundRole):
# numeric position of dataset
if index is not None:
row = index.row()
column = index.column()
if column == 0:
self.itemnumber = index.sibling(row, column)
self.itemname = index.sibling(row, column + 1)
self.itemsize = index.sibling(row, column + 2)
elif column == 1:
self.itemnumber = index.sibling(row, column - 1)
self.itemname = index.sibling(row, column)
self.itemsize = index.sibling(row, column + 1)
elif column == 2:
self.itemnumber = index.sibling(row, column - 2)
self.itemname = index.sibling(row, column - 1)
self.itemsize = index.sibling(row, column)
return (self.itemnumber, self.itemname, self.itemsize, index) #return objects!!! no real datas
#print("Ausgewählter Artikel: ",self.itemindex.data(), self.itemname.data(), self.itemsize.data())
def flags(self, index):
return QtCore.Qt.ItemIsEnabled
def headerData(self, section, orientation, role):
if role == QtCore.Qt.DisplayRole and orientation == QtCore.Qt.Horizontal:
return header_table_view[section]
return QtCore.QAbstractTableModel.headerData(self, section, orientation, role)
def setData(self, index, value, role=QtCore.Qt.DisplayRole):
if role == QtCore.Qt.DisplayRole:
row = index.row()
column = index.column()
color = gui.QColor(value)
#data = value
#self.datatable[row][column] = data
#self.dataChanged.emit(index, index)
return True
def changedData(self):
self.data_changed.emit(pyqtSignal("DataChanged(QModelIndex,QModelIndex)"), self.createIndex(0, 0), self.createIndex(self.rowCount(0)), self.columnCount(0))
def sort(self, column, order):
#self.emit(pyqtSignal("layoutAboutToBeChanged()"))
#self.layoutAboutToBeChanged.emit()
self.sortCol = column
self.sortOrder = order
try:
self.datatable = sorted(self.datatable, key = operator.itemgetter(column))
self.data_changed.emit(pyqtSignal("layoutChanged()"))
except:
print("keine Sortierung möglich")
#self.datatable.sort(key=itemgetter(column), reverse=order == QtCore.Qt.DescendingOrder)
def insertRow(self, item, index=QtCore.QModelIndex()):
""" Insert a row into the model. """
#self.beginInsertRows(QtCore.QModelIndex(), position, position + rows - 1)
TableViewModel.layoutAboutToBeChanged()
self.append(item)
TableViewModel.layoutChanged()
reply = QtWidgets.QMessageBox.information(self, "Meldung zum Artikelliste", "Artikel erfolgreich hinzugefügt!")
return True
Because you don't provide an MRE I will provide an example created from scratch. I will also assume that when you say line you mean row.
The strategy is to use a role that stores the state (a boolean) of the row, and then change that value when the row is pressed. According to that state a custom delegate will be used to change the backgroundBrush that will be used for painting the items.
import random
from PyQt5 import QtCore, QtGui, QtWidgets
StateRole = QtCore.Qt.UserRole + 1000
class BackgroundColorDelegate(QtWidgets.QStyledItemDelegate):
def initStyleOption(self, option, index):
super().initStyleOption(option, index)
print(index.data(StateRole))
if index.data(StateRole):
option.backgroundBrush = QtGui.QColor("red")
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.model = QtGui.QStandardItemModel()
for i in range(5):
for j in range(4):
value = random.randint(0, 10)
it = QtGui.QStandardItem()
it.setData(value, QtCore.Qt.DisplayRole)
self.model.setItem(i, j, it)
self.tableview = QtWidgets.QTableView()
self.tableview.setModel(self.model)
self.tableview.clicked.connect(self.on_clicked)
delegate = BackgroundColorDelegate(self.tableview)
self.tableview.setItemDelegate(delegate)
self.setCentralWidget(self.tableview)
#QtCore.pyqtSlot(QtCore.QModelIndex)
def on_clicked(self, index):
state = False
for i in range(self.model.columnCount()):
ix = self.model.index(index.row(), i)
value = ix.data(StateRole)
if value is not None:
state = value
break
for i in range(self.model.columnCount()):
ix = self.model.index(index.row(), i)
self.model.setData(ix, not state, StateRole)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
Using PySide 1.2
Grabbing the index of a clicked item in a QTableView returns a different result wether there is a horizontal header or not.
If there is a horizontal header, it's seems to be included in the computing of visualRect. So the first line, first column index is no longer 0, 0 but 1, 0.
Is this intended ? Because it is really confusing and inconvenient because the last line index will be invalid.
To reproduce :
import re
import operator
import os
import sys
# from Qt.QtCore import *
# from Qt.QtGui import *
# from Qt.QtWidgets import *
from PySide.QtCore import *
from PySide.QtGui import *
def main():
app = QApplication(sys.argv)
w = MyWindow()
w.show()
sys.exit(app.exec_())
class CustomTableView(QTableView):
"""
"""
def __init__(self, parent=None):
"""
"""
super(CustomTableView, self).__init__(parent=parent)
def mousePressEvent(self, mouse_event):
"""
"""
view_pos = self.mapFromGlobal(mouse_event.globalPos())
index = self.indexAt(view_pos)
print('mouse press index not cutting header : ' + str((index.row(), index.column())))
view_pos.setY(view_pos.y() - self.horizontalHeader().sizeHint().height())
index = self.indexAt(view_pos)
print('mouse press index cutting header : ' + str((index.row(), index.column())))
super(CustomTableView, self).mousePressEvent(mouse_event)
class MyWindow(QWidget):
def __init__(self, *args):
QWidget.__init__(self, *args)
# create table
self.get_table_data()
table = self.createTable()
# layout
layout = QVBoxLayout()
layout.addWidget(table)
self.setLayout(layout)
def get_table_data(self):
stdouterr = os.popen4("dir c:\\")[1].read()
lines = stdouterr.splitlines()
lines = lines[5:]
lines = lines[:-2]
self.tabledata = [re.split(r"\s+", line, 4)
for line in lines]
def createTable(self):
# create the view
tv = CustomTableView()
# set the table model
header = ['date', 'time', '', 'size', 'filename']
tm = MyTableModel(self.tabledata, header, self)
tv.setModel(tm)
return tv
class MyTableModel(QAbstractTableModel):
def __init__(self, datain, headerdata, parent=None, *args):
""" datain: a list of lists
headerdata: a list of strings
"""
QAbstractTableModel.__init__(self, parent, *args)
self.arraydata = datain
self.headerdata = headerdata
def rowCount(self, parent):
return len(self.arraydata)
def columnCount(self, parent):
return len(self.arraydata[0])
def data(self, index, role):
if not index.isValid():
return None
elif role != Qt.DisplayRole:
return None
return self.arraydata[index.row()][index.column()]
def headerData(self, col, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return self.headerdata[col]
return None
if __name__ == "__main__":
main()
You do not have to use the global position and then pass it to the local position since you will get the position with respect to the upper corner of the QTableView, but indexAt requires the position with respect to the content according to the docs:
PySide.QtGui.QAbstractItemView.indexAt(point)
Parameters: point – PySide.QtCore.QPoint
Return type: PySide.QtCore.QModelIndex
Returns the model index of the item at the viewport coordinates point.
You must use the position of the event: mouse_event.pos()
def mousePressEvent(self, mouse_event):
index = self.indexAt(mouse_event.pos())
print('mouse press index: ' + str((index.row(), index.column())))
super(CustomTableView, self).mousePressEvent(mouse_event)
If you want to use the global position then you must convert it to a local position with respect to the viewport():
def mousePressEvent(self, mouse_event):
view_pos = self.viewport().mapFromGlobal(mouse_event.globalPos())
index = self.indexAt(view_pos)
print('mouse press index : ' + str((index.row(), index.column())))
super(CustomTableView, self).mousePressEvent(mouse_event)