How to align column of QTableView - python

I have this code:
class ManifestModel(QtSql.QSqlTableModel):
def __init__(self, parent=None, db=QtSql.QSqlDatabase()):
super(ManifestModel, self).__init__(parent, db)
def flags(self, index):
if (index.column() == 4):
return QtCore.Qt.ItemIsEnabled
elif (index.column() == 6):
return QtCore.Qt.ItemIsEnabled
elif (index.column() == 3):
return QtCore.Qt.AlignHCenter
else:
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEditable
When I run it I get an error:
builtins.TypeError: invalid result from ManifestModel.flags(), AlignmentFlag cannot be converted to PyQt5.QtCore.ItemFlags in this context
In the same routine that uses ManifestModel, I have the code:
ui.label = QtWidgets.QLabel(ui.page)
ui.label.setGeometry(QtCore.QRect(308, 0, 131, 20))
ui.label.setAlignment(QtCore.Qt.AlignCenter)
So what do I have to do to change the alignment in a QTableView column?

The aligment is handled in the data() method of the model. So try something like:
class ManifestModel(QtSql.QSqlTableModel):
def data(self, index, role=QtCore.Qt.DisplayRole):
if role == QtCore.Qt.TextAlignmentRole and index.column() == 3:
return QtCore.Qt.AlignHCenter
return super(ManifestModel, self).data(index, role)

Related

Re-ordering QTableView rows that include spanned columns

I have a working drag and drop example below for reordering rows of the same column length for a qtableview using PyQt5 (with help from this StackOverflow question here). However I am looking to perform the same operation on a qtableview table where one or two rows have merged cells spanning the total number of columns (like the second row in the picture below).
How would be the best way to go about this? Should I remove the merge (clearSpans) at the point of drag/drop and then do a remerge based on the cell value (though when I tried this it did not work), or is there a way to drag/drop reorder with the cell merging intact?
Here's the code which works for row data of equal columns, but fails when a row is merged
from PyQt5.QtGui import QBrush
from PyQt5.QtWidgets import *
from PyQt5.QtCore import QAbstractTableModel, Qt, QModelIndex
class myModel(QAbstractTableModel):
def __init__(self, data, parent=None, *args):
super().__init__(parent, *args)
self._data = data or []
self._headers = ['Type', 'result', 'count']
def rowCount(self, index=None):
return len(self._data)
def columnCount(self, index=None):
return len(self._headers)
def headerData(self, section, orientation, role=Qt.DisplayRole):
if role == Qt.DisplayRole:
if orientation == Qt.Horizontal:
if section < 0 or section >= len(self._headers):
return ""
else:
return self._headers[section]
return None
def data(self, index, role=None):
if role == Qt.TextAlignmentRole:
return Qt.AlignHCenter
if role == Qt.ForegroundRole:
return QBrush(Qt.black)
if role == Qt.BackgroundRole:
if (self.index(index.row(), 0).data().startswith('second')):
return QBrush(Qt.green)
else:
if (self.index(index.row(), 1).data()) == 'abc':
return QBrush(Qt.yellow)
if (self.index(index.row(), 1).data()) == 'def':
return QBrush(Qt.blue)
if (self.index(index.row(), 1).data()) == 'ghi':
return QBrush(Qt.magenta)
if role in (Qt.DisplayRole, Qt.EditRole):
return self._data[index.row()][index.column()]
def flags(self, index: QModelIndex) -> Qt.ItemFlags:
return Qt.ItemIsDropEnabled | Qt.ItemIsEnabled | Qt.ItemIsEditable | Qt.ItemIsSelectable | Qt.ItemIsDragEnabled
def supportedDropActions(self) -> bool:
return Qt.MoveAction | Qt.CopyAction
def relocateRow(self, row_source, row_target) -> None:
row_a, row_b = max(row_source, row_target), min(row_source, row_target)
self.beginMoveRows(QModelIndex(), row_a, row_a, QModelIndex(), row_b)
self._data.insert(row_target, self._data.pop(row_source))
self.endMoveRows()
class myTableView(QTableView):
def __init__(self, parent):
super().__init__(parent)
self.verticalHeader().hide()
self.setSelectionBehavior(self.SelectRows)
self.setSelectionMode(self.SingleSelection)
self.setDragDropMode(self.InternalMove)
self.setDragDropOverwriteMode(False)
def dropEvent(self, event):
if (event.source() is not self or
(event.dropAction() != Qt.MoveAction and
self.dragDropMode() != QAbstractItemView.InternalMove)):
super().dropEvent(event)
selection = self.selectedIndexes()
#self.clearSpans()
from_index = selection[0].row() if selection else -1
to_index = self.indexAt(event.pos()).row()
if (0 <= from_index < self.model().rowCount() and
0 <= to_index < self.model().rowCount() and
from_index != to_index):
self.model().relocateRow(from_index, to_index)
event.accept()
super().dropEvent(event)
class sample_data(QMainWindow):
def __init__(self):
super().__init__()
tv = myTableView(self)
tv.setModel(myModel([
["first", 'abc', 123],
["second"],
["third", 'def', 456],
["fourth", 'ghi', 789],
]))
self.setCentralWidget(tv)
tv.setSpan(1, 0, 1, 3)
self.show()
if __name__ == '__main__':
app = QApplication([])
test = sample_data()
raise SystemExit(app.exec_())
The sections of the vertical header can be made movable, so there's no need to implement this functionality yourself. It obviously means the vertical header will be visible, but that can be mitigated by making the sections blank, which will result in a relatively narrow header:
Note that moving sections around (rather than rows) is purely visual - the underlying model is never modified. That shouldn't really matter in practice, though, since the header provides methods to translate from logical to visual indices. And it does bring some additional benefits - for example, it's very easy to return to a previous state (i.e. by using the header's saveState and restoreState methods).
Below is a working demo based on your example. The rows can be re-ordered by dragging and dropping the section headers, or by pressing Alt+Up / Alt+Down when a row is selected. The vertical header can be toggled by pressing F6. The logical rows can be printed by pressing F7.
UPDATE:
I also added support for moving sections around by dragging and dropping the rows themselves.
from PyQt5.QtGui import QBrush
from PyQt5.QtWidgets import *
from PyQt5.QtCore import QAbstractTableModel, Qt, QModelIndex
class myModel(QAbstractTableModel):
def __init__(self, data, parent=None, *args):
super().__init__(parent, *args)
self._data = data or []
self._headers = ['Type', 'result', 'count']
def rowCount(self, index=None):
return len(self._data)
def columnCount(self, index=None):
return len(self._headers)
def headerData(self, section, orientation, role=Qt.DisplayRole):
if role == Qt.DisplayRole:
if orientation == Qt.Horizontal:
if section < 0 or section >= len(self._headers):
return ""
else:
return self._headers[section]
else:
return ''
return None
def data(self, index, role=None):
if role == Qt.TextAlignmentRole:
return Qt.AlignHCenter
if role == Qt.ForegroundRole:
return QBrush(Qt.black)
if role == Qt.BackgroundRole:
if (self.index(index.row(), 0).data().startswith('second')):
return QBrush(Qt.green)
else:
if (self.index(index.row(), 1).data()) == 'abc':
return QBrush(Qt.yellow)
if (self.index(index.row(), 1).data()) == 'def':
return QBrush(Qt.blue)
if (self.index(index.row(), 1).data()) == 'ghi':
return QBrush(Qt.magenta)
if role in (Qt.DisplayRole, Qt.EditRole):
return self._data[index.row()][index.column()]
def flags(self, index: QModelIndex) -> Qt.ItemFlags:
return Qt.ItemIsDropEnabled | Qt.ItemIsEnabled | Qt.ItemIsEditable | Qt.ItemIsSelectable | Qt.ItemIsDragEnabled
def supportedDropActions(self) -> bool:
return Qt.MoveAction | Qt.CopyAction
class myTableView(QTableView):
def __init__(self, parent):
super().__init__(parent)
header = self.verticalHeader()
header.setSectionsMovable(True)
header.setSectionResizeMode(QHeaderView.Fixed)
header.setFixedWidth(10)
QShortcut('F7', self, self.getLogicalRows)
QShortcut('F6', self, self.toggleVerticalHeader)
QShortcut('Alt+Up', self, lambda: self.moveRow(True))
QShortcut('Alt+Down', self, lambda: self.moveRow(False))
self.setSelectionBehavior(self.SelectRows)
self.setSelectionMode(self.SingleSelection)
self.setDragDropMode(self.InternalMove)
self.setDragDropOverwriteMode(False)
def dropEvent(self, event):
if (event.source() is not self or
(event.dropAction() != Qt.MoveAction and
self.dragDropMode() != QAbstractItemView.InternalMove)):
super().dropEvent(event)
selection = self.selectedIndexes()
from_index = selection[0].row() if selection else -1
to_index = self.indexAt(event.pos()).row()
if (0 <= from_index < self.model().rowCount() and
0 <= to_index < self.model().rowCount() and
from_index != to_index):
header = self.verticalHeader()
from_index = header.visualIndex(from_index)
to_index = header.visualIndex(to_index)
header.moveSection(from_index, to_index)
event.accept()
super().dropEvent(event)
def toggleVerticalHeader(self):
self.verticalHeader().setHidden(self.verticalHeader().isVisible())
def moveRow(self, up=True):
selection = self.selectedIndexes()
if selection:
header = self.verticalHeader()
row = header.visualIndex(selection[0].row())
if up and row > 0:
header.moveSection(row, row - 1)
elif not up and row < header.count() - 1:
header.moveSection(row, row + 1)
def getLogicalRows(self):
header = self.verticalHeader()
for vrow in range(header.count()):
lrow = header.logicalIndex(vrow)
index = self.model().index(lrow, 0)
print(index.data())
class sample_data(QMainWindow):
def __init__(self):
super().__init__()
tv = myTableView(self)
tv.setModel(myModel([
["first", 'abc', 123],
["second"],
["third", 'def', 456],
["fourth", 'ghi', 789],
]))
self.setCentralWidget(tv)
tv.setSpan(1, 0, 1, 3)
if __name__ == '__main__':
app = QApplication(['Test'])
test = sample_data()
test.setGeometry(600, 100, 350, 185)
test.show()
app.exec_()

removeRow in QTableView/QAbstractTableModel by CheckBox

I've created a table view where I'm trying to drop rows by clicking the checkbox embedded in the table view. I get to the point where it indexes the correct row when the checkbox is deselected, but the model refuse to remove the row and instead returns 'False'. Any thoughts? Still new to tableviews after switching from tablewidgets
class pandasModel(QAbstractTableModel):
def __init__(self, data, check_col=[0, 0]):
QAbstractTableModel.__init__(self)
self._data = data
self._check = check_col
cs = 0
if check_col[1]: cs = 2
self._checked = [[cs for i in range(self.columnCount())] for j in range(self.rowCount())]
def rowCount(self, parent=QModelIndex):
return self._data.shape[0]
def columnCount(self, parent=QModelIndex):
return self._data.shape[1]
def data(self, index, role):
if index.isValid():
data = self._data.iloc[index.row(), index.column()]
if role == Qt.DisplayRole:
if isinstance(data, float):
return f'{data:,.2f}'
elif isinstance(data, (int, np.int64)):
return f'{data:,}'
else:
return str(data)
elif role == Qt.TextAlignmentRole:
if isinstance(data, (float, int, np.int64)):
return Qt.AlignVCenter + Qt.AlignRight
elif role == Qt.CheckStateRole:
if self._check[0] > 0:
if self._check[0] == index.column():
return self._checked[index.row()][index.column()]
return None
def flags(self, index):
if not index.isValid(): return
return Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsUserCheckable | Qt.ItemIsEditable
def setData(self, index, value, role):
if not index.isValid() or role != Qt.CheckStateRole: return False
self._checked[index.row()][index.column()] = value
if value == 0:
print('checked')
self.removeRow(index.row()) #THIS IS WHERE IT RETURNS FALSE
self.dataChanged.emit(index, index)
return True
def headerData(self, col, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return self._data.columns[col]
return None

How can I check if the value passed in a PyQt TableModelView is an integer and not for example a letter/symbol?

I'm relatively new to Python and especially PyQt and model-view programming. I want to have it so that someone can only enter integers in my table and not letters/symbols etc. Here's the model I've made so far for my tableView widget:
class PixelTableModel(QtCore.QAbstractTableModel):
def __init__(self):
super(PixelTableModel, self).__init__()
self.pixel_coordinate = [[None, None, None, None]]
def rowCount(self, parent):
return 1
def columnCount(self, parent):
return 4
def flags(self, index):
return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
def setData(self, index, value, role = QtCore.Qt.EditRole):
if role == QtCore.Qt.EditRole:
row = index.row()
column = index.column()
self.pixel_coordinate[row][column] = value
print(self.pixel_coordinate) #testing
return True
return False
def data(self, index, role):
if role == QtCore.Qt.DisplayRole:
row = index.row()
column = index.column()
value = self.pixel_coordinate[row][column]
return value
def headerData(self, section, orientation, role): # section = row column, orientation = vertical/horizontal
if role == QtCore.Qt.DisplayRole:
if orientation == QtCore.Qt.Horizontal:
dict = {0: "Xstart", 1: "Ystart", 2: "Xmax", 3: "Ymax"}
for key in dict:
if section == key:
return dict[key]
else:
return "Pixel coordinate"
It seems to work except obviously for the part where it still can take letters/symbols as input in the tableView. I've tried a couple of things in the setData() method but can't seem to get it work, always get some type of error or it won't even change the box at all. Thanks to anyone that can help me with this. Also sorry for bad English.
For anyone still interested, after going through it again fixed it with simple try except block:
def setData(self, index, value, role = QtCore.Qt.EditRole):
if role == QtCore.Qt.EditRole:
try:
row = index.row()
column = index.column()
string_to_int = int(value)
self.pixel_coordinate[row][column] = value
print(self.pixel_coordinate) #testing
return True
except ValueError:
print("Not a number")
return False
return False

PyQt4 QAbstractItemModel cuts of floats at 2 decimal places

I have an abstract item model. which is displayed as a tree.
The purpose is to store a set of preferences.
The code for the qt object is:
class QPreferenceModel(QAbstractItemModel):
'Convention states only items with column index 0 can have children'
#report_thread_error
def __init__(self, pref_struct, parent=None):
super(QPreferenceModel, self).__init__(parent)
self.rootPref = pref_struct
#report_thread_error
def index2Pref(self, index=QModelIndex()):
'''Internal helper method'''
if index.isValid():
item = index.internalPointer()
if item:
return item
return self.rootPref
#-----------
# Overloaded ItemModel Read Functions
#report_thread_error
def rowCount(self, parent=QModelIndex()):
parentPref = self.index2Pref(parent)
return parentPref.qt_row_count()
#report_thread_error
def columnCount(self, parent=QModelIndex()):
parentPref = self.index2Pref(parent)
return parentPref.qt_col_count()
#report_thread_error
def data(self, index, role=Qt.DisplayRule):
'''Returns the data stored under the given role
for the item referred to by the index.'''
if not index.isValid():
return QVariant()
if role != Qt.DisplayRole and role != Qt.EditRole:
return QVariant()
nodePref = self.index2Pref(index)
data = nodePref.qt_get_data(index.column())
var = QVariant(data)
print('---')
print('data = %r' % data)
print('type(data) = %r' % type(data))
#if isinstance(data, float):
#var = var.toDouble()[0]
print('var= %r' % var)
return var
#report_thread_error
def index(self, row, col, parent=QModelIndex()):
'''Returns the index of the item in the model specified
by the given row, column and parent index.'''
if parent.isValid() and parent.column() != 0:
return QModelIndex()
parentPref = self.index2Pref(parent)
childPref = parentPref.qt_get_child(row)
if childPref:
return self.createIndex(row, col, childPref)
else:
return QModelIndex()
#report_thread_error
def parent(self, index=None):
'''Returns the parent of the model item with the given index.
If the item has no parent, an invalid QModelIndex is returned.'''
if index is None: # Overload with QObject.parent()
return QObject.parent(self)
if not index.isValid():
return QModelIndex()
nodePref = self.index2Pref(index)
parentPref = nodePref.qt_get_parent()
if parentPref == self.rootPref:
return QModelIndex()
return self.createIndex(parentPref.qt_parents_index_of_me(), 0, parentPref)
#-----------
# Overloaded ItemModel Write Functions
#report_thread_error
def flags(self, index):
'Returns the item flags for the given index.'
if index.column() == 0:
# The First Column is just a label and unchangable
return Qt.ItemIsEnabled | Qt.ItemIsSelectable
if not index.isValid():
return Qt.ItemFlag(0)
childPref = self.index2Pref(index)
if childPref:
if childPref.qt_is_editable():
return Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable
return Qt.ItemFlag(0)
#report_thread_error
def setData(self, index, data, role=Qt.EditRole):
'Sets the role data for the item at index to value.'
if role != Qt.EditRole:
return False
leafPref = self.index2Pref(index)
result = leafPref.qt_set_leaf_data(data)
if result is True:
self.dataChanged.emit(index, index)
return result
#report_thread_error
def headerData(self, section, orientation, role=Qt.DisplayRole):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
if section == 0:
return QVariant('Config Key')
if section == 1:
return QVariant('Config Value')
return QVariant()
I assume the problem is probably in setData or data
def data(self, index, role=Qt.DisplayRule):
'''Returns the data stored under the given role
for the item referred to by the index.'''
if not index.isValid():
return QVariant()
if role != Qt.DisplayRole and role != Qt.EditRole:
return QVariant()
nodePref = self.index2Pref(index)
data = nodePref.qt_get_data(index.column())
var = QVariant(data)
print('---')
print('data = %r' % data)
print('type(data) = %r' % type(data))
#if isinstance(data, float):
#var = var.toDouble()[0]
print('var= %r' % var)
return var
def setData(self, index, data, role=Qt.EditRole):
'Sets the role data for the item at index to value.'
if role != Qt.EditRole:
return False
leafPref = self.index2Pref(index)
result = leafPref.qt_set_leaf_data(data)
if result is True:
self.dataChanged.emit(index, index)
return result
I was messing around with data to see if turning the QVariant into a double would do something. I also tried replacing Qt.DisplayRole and Qt.EditRole with Qt.UserRole.
I'm not actually sure what is controlling the display and edit precision of the float. The type is correct, but whenever I want to enter something like .0002 Qt prevents me from typing after .00
You could use a custom item delegate to adjust the precision of floats, but it might be overkill for this particular use-case.
A simpler solution would be to just return the values as strings - then you can format the displayed values in whatever way you like.

How do I create a tree view (with checkbox) inside a combo box - PyQt

I'm using PYQT for developing an application. my requirement is to insert a tree view with checkbox inside a combobox items. I would like to know how to achieve this?
I have the following code but this does not work.
class CheckboxInsideListbox(QWidget):
def __init__(self, parent = None):
super(CheckboxInsideListbox, self).__init__(parent)
self.setGeometry(250,250,300,300)
self.MainUI()
def MainUI(self):
#stb_label = QLabel("Select STB\'s")
stb_combobox = QComboBox()
length = 10
cb_layout = QVBoxLayout(stb_combobox)
for i in range(length):
c = QCheckBox("STB %i" % i)
cb_layout.addWidget(c)
main_layout = QVBoxLayout()
main_layout.addWidget(stb_combobox)
main_layout.addLayout(cb_layout)
self.setLayout(main_layout)
Please let me know if I'm missing anything here.
You should create a model that support Qt.CheckStateRole in data and SetData methods and the flag Qt.ItemIsUserCheckable in the flags method.
I Paste you here an example i am using in a project, this is a QSortFilterProxyModel generic implementation to use in any model but you can use the same ideas in your model implementation, obviously i am using internal structures in this subclass you have not directly in PyQt and are attached to my internal implementation (self.booleanSet and self.readOnlySet).
def flags(self, index):
if not index.isValid():
return Qt.ItemIsEnabled
if index.column() in self.booleanSet:
return Qt.ItemIsUserCheckable | Qt.ItemIsSelectable | Qt.ItemIsEnabled
elif index.column() in self.readOnlySet:
return Qt.ItemIsSelectable | Qt.ItemIsEnabled
else:
return QSortFilterProxyModel.flags(self, index)
def data(self, index, role):
if not index.isValid():
return QVariant()
if index.column() in self.booleanSet and role in (Qt.CheckStateRole, Qt.DisplayRole):
if role == Qt.CheckStateRole:
value = QVariant(Qt.Checked) if index.data(Qt.EditRole).toBool() else QVariant(Qt.Unchecked)
return value
else: #if role == Qt.DisplayRole:
return QVariant()
else:
return QSortFilterProxyModel.data(self, index, role)
def setData(self, index, data, role):
if not index.isValid():
return False
if index.column() in self.booleanSet and role == Qt.CheckStateRole:
value = QVariant(True) if data.toInt()[0] == Qt.Checked else QVariant(False)
return QSortFilterProxyModel.setData(self, index, value, Qt.EditRole)
else:
return QSortFilterProxyModel.setData(self, index, data, role)
If you really mean to apply a layout to a layout, try adding your widget to your cb_layout. Otherwise get rid of your sublayout.

Categories