I created a QTreeView to show you my problem. Most of the code comes from there : editable tree model. So if you have any question that concerns the model, go check this first link.
Now let me explain the problem : I would like to populate the QTreeView with my database. In the third column, you can see 'Types' of items. I would like the QCombobox to be, by default, equal to those types. Moreover, I want to get a signal and the value, when a QCombobox is changed in my model in order to update the value in my Database. Here is the minimal reproductible exemple :
from PySide6.QtWidgets import QApplication,QComboBox
import os
import sys
import sys
from pathlib import Path
from PySide6.QtCore import (QAbstractItemModel, QItemSelectionModel,
QModelIndex, Qt, Slot)
from PySide6.QtWidgets import (QAbstractItemView, QMainWindow, QTreeView,
QWidget)
from PySide6.QtTest import QAbstractItemModelTester
class MainWindow(QMainWindow):
def __init__(self, parent: QWidget = None):
super().__init__(parent)
self.resize(573, 468)
self.view = QTreeView()
self.view.setAlternatingRowColors(True)
self.view.setSelectionBehavior(QAbstractItemView.SelectItems)
self.view.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel)
self.view.setAnimated(False)
self.view.setAllColumnsShowFocus(True)
self.setCentralWidget(self.view)
menubar = self.menuBar()
file_menu = menubar.addMenu("&File")
self.exit_action = file_menu.addAction("E&xit")
self.exit_action.setShortcut("Ctrl+Q")
self.exit_action.triggered.connect(self.close)
self.setWindowTitle("Editable Tree Model")
headers = ["number", "value",'Type']
self.model = TreeModel(headers, self)
if "-t" in sys.argv:
QAbstractItemModelTester(self.model, self)
self.view.setModel(self.model)
self.view.expandAll()
qindex_widget = self.view.model().index(0,1,QModelIndex())
a = 0
while qindex_widget.isValid() :
combobox = QComboBox()
combobox.addItems(['len','diam','concent'])
qindex_widget = self.view.model().index(a,1,QModelIndex())
self.view.setIndexWidget(qindex_widget,combobox)
a += 1
self.view.model().dataChanged.connect(self.onDataChanged)
for column in range(self.model.columnCount()):
self.view.resizeColumnToContents(column)
def onDataChanged(self) :
print('Data changed')
class TreeItem:
def __init__(self, data: list, parent: 'TreeItem' = None):
self.item_data = data
self.parent_item = parent
self.child_items = []
def child(self, number: int) -> 'TreeItem':
if number < 0 or number >= len(self.child_items):
return None
return self.child_items[number]
def last_child(self):
return self.child_items[-1] if self.child_items else None
def child_count(self) -> int:
return len(self.child_items)
def child_number(self) -> int:
if self.parent_item:
return self.parent_item.child_items.index(self)
return 0
def column_count(self) -> int:
return len(self.item_data)
def data(self, column: int):
if column < 0 or column >= len(self.item_data):
return None
return self.item_data[column]
def insert_children(self, position: int, count: int, columns: int) -> bool:
if position < 0 or position > len(self.child_items):
return False
for row in range(count):
data = [None] * columns
item = TreeItem(data.copy(), self)
self.child_items.insert(position, item)
return True
def insert_columns(self, position: int, columns: int) -> bool:
if position < 0 or position > len(self.item_data):
return False
for column in range(columns):
self.item_data.insert(position, None)
for child in self.child_items:
child.insert_columns(position, columns)
return True
def parent(self):
return self.parent_item
def remove_children(self, position: int, count: int) -> bool:
if position < 0 or position + count > len(self.child_items):
return False
for row in range(count):
self.child_items.pop(position)
return True
def remove_columns(self, position: int, columns: int) -> bool:
if position < 0 or position + columns > len(self.item_data):
return False
for column in range(columns):
self.item_data.pop(position)
for child in self.child_items:
child.remove_columns(position, columns)
return True
def set_data(self, column: int, value):
if column < 0 or column >= len(self.item_data):
return False
self.item_data[column] = value
return True
def __repr__(self) -> str:
result = f"<treeitem.TreeItem at 0x{id(self):x}"
for d in self.item_data:
result += f' "{d}"' if d else " <None>"
result += f", {len(self.child_items)} children>"
return result
class TreeModel(QAbstractItemModel):
def __init__(self, headers: list, parent=None):
super().__init__(parent)
self.root_data = headers
self.root_item = TreeItem(self.root_data.copy())
self.setup_model_data(self.root_item)
def columnCount(self, parent: QModelIndex = None) -> int:
return self.root_item.column_count()
def data(self, index: QModelIndex, role: int = None):
if not index.isValid():
return None
if role != Qt.DisplayRole and role != Qt.EditRole:
return None
item: TreeItem = self.get_item(index)
return item.data(index.column())
def flags(self, index: QModelIndex) -> Qt.ItemFlags:
if not index.isValid():
return Qt.NoItemFlags
return Qt.ItemIsEditable | QAbstractItemModel.flags(self, index)
def get_item(self, index: QModelIndex = QModelIndex()) -> TreeItem:
if index.isValid():
item: TreeItem = index.internalPointer()
if item:
return item
return self.root_item
def headerData(self, section: int, orientation: Qt.Orientation,
role: int = Qt.DisplayRole):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return self.root_item.data(section)
return None
def index(self, row: int, column: int, parent: QModelIndex = QModelIndex()) -> QModelIndex:
if parent.isValid() and parent.column() != 0:
return QModelIndex()
parent_item: TreeItem = self.get_item(parent)
if not parent_item:
return QModelIndex()
child_item: TreeItem = parent_item.child(row)
if child_item:
return self.createIndex(row, column, child_item)
return QModelIndex()
def parent(self, index: QModelIndex = QModelIndex()) -> QModelIndex:
if not index.isValid():
return QModelIndex()
child_item: TreeItem = self.get_item(index)
if child_item:
parent_item: TreeItem = child_item.parent()
else:
parent_item = None
if parent_item == self.root_item or not parent_item:
return QModelIndex()
return self.createIndex(parent_item.child_number(), 0, parent_item)
def rowCount(self, parent: QModelIndex = QModelIndex()) -> int:
if parent.isValid() and parent.column() > 0:
return 0
parent_item: TreeItem = self.get_item(parent)
if not parent_item:
return 0
return parent_item.child_count()
def setData(self, index: QModelIndex, value, role: int) -> bool:
if role != Qt.EditRole:
return False
item: TreeItem = self.get_item(index)
result: bool = item.set_data(index.column(), value)
if result:
self.dataChanged.emit(index, index, [Qt.DisplayRole, Qt.EditRole])
return result
def setHeaderData(self, section: int, orientation: Qt.Orientation, value,
role: int = None) -> bool:
if role != Qt.EditRole or orientation != Qt.Horizontal:
return False
result: bool = self.root_item.set_data(section, value)
if result:
self.headerDataChanged.emit(orientation, section, section)
return result
def setup_model_data(self, parent: TreeItem):
parents = [parent]
Data = [[0,1,'diam'],[1,0.6,'len'],[2,1.9,'conc'],[3,4.5,'diam'],[4,0.022,'len']]
for item in Data :
parent : TreeItem = parents[0]
col_count = self.root_item.column_count()
parent.insert_children(parent.child_count(), 1, col_count)
column_data = [item[0],item[1],item[2]]
for column in range(len(column_data)):
child = parent.last_child()
child.set_data(column, column_data[column])
def _repr_recursion(self, item: TreeItem, indent: int = 0) -> str:
result = " " * indent + repr(item) + "\n"
for child in item.child_items:
result += self._repr_recursion(child, indent + 2)
return result
def __repr__(self) -> str:
return self._repr_recursion(self.root_item)
if __name__=="__main__" :
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
I read different topics on stackoverflow, but I didn't figure out how to do this.
Do you have any ideas ? Thanks
Edit :
Ok, I found the answers to my question. I created a customDelegate to modify the data as I want to. Here is the code to do so :
class CustomDelegate(QStyledItemDelegate):
def __init__(self, parent=None):
super().__init__(parent)
def createEditor(self, parent, option, index : QModelIndex):
if index.column() == 0 :
editor = QLineEdit(parent)
return editor
if index.column() == 2 :
combo = QComboBox(parent)
combo.addItems(["Lenght","Diametre","Coax"])
editor = combo
return editor
if index.column() > 4 and index.column() :
editor = CustomDoubleSpinbox(parent)
editor.setDecimals(3)
return editor
Usually, modfying a data is done thanks to a QLineEdit or a QDoubleSpinBox but as you can see, you can choose for each column what king of editor you want. I hope it helped you as it helped me ! Bye