I can find some examples of how to use standard model with standard view.
http://doc.qt.io/qt-5/modelview.html
http://doc.qt.io/qt-5/qtwidgets-itemviews-simplewidgetmapper-example.html
but I just can not find ONE example of how to use QAbstractModel make my own model and to use it on my own view/widget.
After I update the model the stand view will update but not my own view.
Full code:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from PyQt5.QtWidgets import (QWidget, QLabel, QDataWidgetMapper,
QLineEdit, QApplication, QGridLayout)
from PyQt5.QtCore import QAbstractListModel, Qt
from PyQt5.QtWidgets import QListView
class Window(QWidget):
def __init__(self, model, parent=None):
super(Window, self).__init__(parent)
self.model = model
# Set up the widgets.
nameLabel = QLabel("Na&me:")
nameEdit = QLineEdit()
# Set up the mapper.
self.mapper = QDataWidgetMapper(self)
self.mapper.setModel(self.model)
self.mapper.addMapping(nameEdit, 0)
layout = QGridLayout()
layout.addWidget(nameLabel, 0, 0, 1, 1)
layout.addWidget(nameEdit, 0, 1, 1, 1)
self.setLayout(layout)
self.mapper.toFirst()
class MyModel(QAbstractListModel):
def __init__(self, status=[], parent=None):
super().__init__(parent)
self.status = status
def rowCount(self, index_parent=None, *args, **kwargs):
return len(self.status)
def data(self, index, role=Qt.DisplayRole, parent=None):
if not index.isValid():
return None
row = index.row()
if row == 0:
print(index)
if role == Qt.DisplayRole:
return self.status[row]
elif role == Qt.EditRole: # if it's editing mode, return value for editing
return self.status[row]
def flags(self, index):
return Qt.ItemIsEnabled | Qt.ItemIsEditable
def setData(self, index, value='', role=Qt.EditRole):
row = index.row()
if role == Qt.EditRole:
self.status[row] = value
self.dataChanged.emit(index, index) # inform the other view to request new data
return True
else:
return False
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
myModel_on_mywindow = MyModel([1, 2, 3])
mywindow = Window(myModel_on_mywindow)
mywindow.setWindowTitle('myModel_on_mywindow')
mywindow.show()
myModel_on_mywindow.status[0] = 2
myModel_on_qlistview = MyModel([1, 2, 3])
qlistview = QListView()
qlistview.show()
qlistview.setModel(myModel_on_qlistview)
qlistview.setWindowTitle('myModel_on_qlistview')
myModel_on_qlistview.status[0] = 2
sys.exit(app.exec_())
Your custom model needs to override some functions of the abstract model, depending on which one you subclass. This is a small example for subclassing the QAbstractListModel. You can read more about this in the Qt documentation: QAbstractListModel Details
class MyModel(QAbstractListModel):
def __init__(parent=None):
QAbstractListModel.__init__(parent)
self.content = [] # holds the data, you want to present
def rowCount(index):
return len(self.content)
# This defines what the view should present at given index
def data(index, role):
if index.isValid() and role == Qt.DisplayRole):
dataElement = self.content[index.row()]
if index.colum() == 0:
return dataElement.someBoolField
if index.colum() == 1:
return dataElement.someIntField
# ...
}
return QVariant() # this is like returning nothing
}
# If your items should be editable. Automatically called, when user changes the item.
def setData(index, value, role):
if index.isValid():
dataElement = self.content[index.row()].get()
if index.column() == 0:
return dataElement.tryToSetBoolField(value.toBool())
if index.column() == 1:
return dataElement.tryToSetIntField(value.toInt())
# ...
}
return True
}
};
# connecting your view with your model
model = MyModel()
myView = QTreeView() # or something else
myView.setModel(model)
EDIT
To update your view when using a custom Model in combination with a QDataMapper, call setCurrentIndex(changedIndex) or its wrappers after changing the model, like so in your case:
myModel_on_mywindow.status[0] = 2
myModel.mapper.toFirst()
The Qt documentation on QDataMapper mentions this in the detailed description:
The navigational functions toFirst(), toNext(), toPrevious(), toLast() and setCurrentIndex() can be used to navigate in the model and update the widgets with contents from the model.
Related
I'm using a subclassed QAbstractTableModel with dataclasses as items. Each dataclass contains a field "field1" with a list, which I'd like to display in a listview and have it automatically change whenever I edit or add an item in the listview.
To do that I set a custom delegate to the QDataWidgetMapper which will retrieve and set the values from that dataclass. This works the way I want it to.
My problem is that I want to add additional items to that listview with the press of a button and have the QDataWidgetMapper add them automatically to the model.
This is what I have so far:
import sys
import dataclasses
from typing import List, Any
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
#dataclasses.dataclass()
class StorageItem:
field1: List[str] = dataclasses.field(default_factory=list)
class StorageModel(QAbstractTableModel):
def __init__(self, parent=None):
super().__init__(parent)
test = StorageItem()
test.field1 = ['Item °1', 'Item °2']
self._data: List[StorageItem] = [test]
def data(self, index: QModelIndex, role: int = ...) -> Any:
if not index.isValid():
return
item = self._data[index.row()]
col = index.column()
if role in {Qt.DisplayRole, Qt.EditRole}:
if col == 0:
return item.field1
else:
return None
def setData(self, index: QModelIndex, value, role: int = ...) -> bool:
if not index.isValid() or role != Qt.EditRole:
return False
item = self._data[index.row()]
col = index.column()
if col == 0:
item.field1 = value
self.dataChanged.emit(index, index)
print(self._data)
return True
def flags(self, index: QModelIndex) -> Qt.ItemFlags:
return Qt.ItemFlags(
Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable
)
def rowCount(self, parent=None) -> int:
return len(self._data)
def columnCount(self, parent=None) -> int:
return len(dataclasses.fields(StorageItem))
class TestDelegate(QStyledItemDelegate):
def __init__(self, parent=None):
super().__init__(parent)
def setEditorData(self, editor: QWidget, index: QModelIndex) -> None:
if isinstance(editor, QListView):
data = index.model().data(index, Qt.DisplayRole)
editor.model().setStringList(data)
else:
super().setEditorData(editor, index)
def setModelData(
self, editor: QWidget,
model: QAbstractItemModel,
index: QModelIndex
) -> None:
if isinstance(editor, QListView):
data = editor.model().stringList()
model.setData(index, data, Qt.EditRole)
else:
super().setModelData(editor, model, index)
class CustomListView(QListView):
item_added = pyqtSignal(name='itemAdded')
def __init__(self, parent=None):
super().__init__(parent)
self.setModel(QStringListModel())
def add_item(self, item: str):
str_list = self.model().stringList()
str_list.append(item)
self.model().setStringList(str_list)
self.item_added.emit()
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
cent_widget = QWidget()
self.setCentralWidget(cent_widget)
# Vertical Layout
v_layout = QVBoxLayout()
v_layout.setContentsMargins(10, 10, 10, 10)
# Listview
self.listview = CustomListView()
v_layout.addWidget(self.listview)
# Button
self.btn = QPushButton('Add')
self.btn.clicked.connect(lambda: self.listview.add_item('New Item'))
v_layout.addWidget(self.btn)
cent_widget.setLayout(v_layout)
# Set Mapping
self.mapper = QDataWidgetMapper()
self.mapper.setItemDelegate(TestDelegate())
self.mapper.setSubmitPolicy(QDataWidgetMapper.AutoSubmit)
self.mapper.setModel(StorageModel())
self.mapper.addMapping(self.listview, 0)
self.mapper.toFirst()
self.listview.itemAdded.connect(self.mapper.submit)
def main():
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
if __name__ == '__main__':
main()
Currently, I'm using the signal itemAdded from inside the custom ListView to manually submit the QDataWidgetMapper.
Is there a way to do this within CustomListView, without using a custom signal?
Somehow the delegate knows when data in the listview has been edited. How can I trigger that same mechanism when new items are added?
TL; DR; It can not.
The submitPolicy QDataWidgetMapper::AutoSubmit indicates that the model will be updated when focus is lost. The model is also updated when the commitData or closeEditor signal of the delegate is invoked, which happens by default when some specific keys are pressed.
A better implementation would be to create a signal that is emitted every time a change is made in the QListView model and connect it to submit, not just the method of adding elements. Also it is better to use a custom qproperty.
class CustomListView(QListView):
items_changed = pyqtSignal(name="itemsChanged")
def __init__(self, parent=None):
super().__init__(parent)
self.setModel(QStringListModel())
self.model().rowsInserted.connect(self.items_changed)
self.model().rowsRemoved.connect(self.items_changed)
self.model().dataChanged.connect(self.items_changed)
self.model().layoutChanged.connect(self.items_changed)
def add_item(self, item: str):
self.items += [item]
#pyqtProperty(list, notify=items_changed)
def items(self):
return self.model().stringList()
#items.setter
def items(self, data):
if len(data) == len(self.items) and all(
x == y for x, y in zip(data, self.items)
):
return
self.model().setStringList(data)
self.items_changed.emit()
# Set Mapping
self.mapper = QDataWidgetMapper()
self.mapper.setModel(StorageModel())
self.mapper.addMapping(self.listview, 0, b"items")
self.mapper.toFirst()
self.listview.items_changed.connect(self.mapper.submit)
What I'm trying to do, is to create a subclass of QTableView that will accept a standardized model (which is the result of a mysql query), and populate it. With it, I'd like to define a custom context menu which allows me to add or remove a row. This approach allows me to define the model and subclass once, and then call them both to populate them with a number of different queries without duplicating code.
The add_row and remove_row QActions would essentially call the model methods insertRows and removeRows.
Here's a functioning example (apart from the mariadb module which is separate):
from PySide2 import QtWidgets, QtCore, Qt, QtGui
import maria_db
import numpy
app = QtWidgets.QApplication([])
#Define model for tablePersonnel
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, db,query,tableName,id):
super(TableModel, self).__init__()
self.db=db
self.query=query
self.tableName=tableName
self.id=id
self.pullData()
def pullData(self):
self.datasheet=self.db.RunQuery(self.query)
self.rows = numpy.array(self.datasheet.fetchall())
self.headers = numpy.array(self.datasheet.column_names)
self.idIndex=int(numpy.where(self.headers==self.id)[0])
def data(self, index, role):
if role == QtCore.Qt.DisplayRole:
return str(self.rows[index.row()][index.column()])
def rowCount(self, parent):
return len(self.rows)
def columnCount(self, parent):
return len(self.headers)
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
return self.headers[section]
def flags(self,index):
if index.column()!=self.idIndex:
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsSelectable
else:
return QtCore.Qt.ItemIsSelectable
def setData(self, index,value,role=QtCore.Qt.EditRole):
if index.isValid():
sel_id=self.rows[index.row()][0]
sel_column=self.headers[index.column()]
if self.db.UpdateCell(self.tableName,sel_id,self.id,sel_column,value)==True:
self.rows[self.idFinder(self.rows,sel_id)][1]=value
self.dataChanged.emit(index,index)
return True
def insertRows(self):
print("inserting row!!!")
def removeRows(self):
pass
#find index of selected ID
def idFinder(self,data, search):
for i in range(len(data)):
for j in range(len(data[i])):
if data[i][j] == search:
return i
else:
return False
class EditTable(QtWidgets.QTableView):
def __init__(self):
super(EditTable, self).__init__()
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.context_menu)
def context_menu(self):
context=QtWidgets.QMenu()
add_row=context.addAction("Add row")
add_row.triggered.connect(lambda: self.personnelTableModel.insertRows())
rem_row=context.addAction("Remove row")
rem_row.triggered.connect(lambda: self.personnelTableModel.removeRows())
cursor=QtGui.QCursor()
context.exec_(cursor.pos())
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
widget = QtWidgets.QWidget()
layout = QtWidgets.QVBoxLayout()
widget.setLayout(layout)
button = QtWidgets.QPushButton("Push me")
self.db = maria_db.ConnectDb("localhost", "root", "1234", "test")
self.personnelTableModel = TableModel(self.db,"SELECT * FROM personnel","personnel","personnel_id")
table = EditTable()
table.setModel(self.personnelTableModel)
layout.addWidget(table)
layout.addWidget(button)
self.setCentralWidget(widget)
window = MainWindow()
window.show()
app.exec_()
So my problem is that when I call add_row.triggered.connect(lambda: self.personnelTableModel.insertRows()), I get a 'EditTable' object has no attribute 'personnelTableModel' which essentially means that there is no method insertRows in self. How can I refer to the model assigned to the object that is constructed using the subclass of the QTableView EditTable, through the subclass itself? Am I going about this the wrong way?
What is personnelTableModel for EditTable? the model, and how can I access the model from the view? Using the model() method. On the other hand, a lambda method is not necessary.
def context_menu(self):
context=QtWidgets.QMenu()
add_row=context.addAction("Add row")
add_row.triggered.connect(self.model().insertRows)
rem_row=context.addAction("Remove row")
rem_row.triggered.connect(self.model().removeRows)
cursor=QtGui.QCursor()
context.exec_(cursor.pos())
Having trouble trying to figure out how to catch a row in a QTableView that's being edited has been canceled. For example, if I am editing a newly inserted row in a QTableView and the ESC, up/down arrows keys have been pressed, I need to remove the row because (in my mind) has been cancelled. Also holds true if the user clicks away from the row. I can't really post any code as I have no idea how to implement something like this. Any ideas?
I have a an example of, what I believe, is what you want (at least for key pressed issue). From there you can do something similar for the clicking issue.
My solution uses a custom QItemDelegate that overrides the eventFilter method. Also it uses a naive model (because you want to use QTableView), the use of the layoutChanged signal on the model is due to functionality of the example, read the docs for more suitable add/delete data features according to your needs.
Hope it helps.
The sample ui:
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'test.ui',
# licensing of 'test.ui' applies.
#
# Created: Wed Nov 7 16:10:12 2018
# by: pyside2-uic running on PySide2 5.11.0
#
# WARNING! All changes made in this file will be lost!
from PySide2 import QtCore, QtGui, QtWidgets
class Ui_Test(object):
def setupUi(self, Test):
Test.setObjectName("Test")
Test.resize(538, 234)
self.horizontalLayout = QtWidgets.QHBoxLayout(Test)
self.horizontalLayout.setObjectName("horizontalLayout")
self.gridLayout = QtWidgets.QGridLayout()
self.gridLayout.setObjectName("gridLayout")
self.tableView = QtWidgets.QTableView(Test)
self.tableView.setObjectName("tableView")
self.gridLayout.addWidget(self.tableView, 0, 0, 1, 1)
self.addRow = QtWidgets.QPushButton(Test)
self.addRow.setObjectName("addRow")
self.gridLayout.addWidget(self.addRow, 0, 1, 1, 1)
self.horizontalLayout.addLayout(self.gridLayout)
self.retranslateUi(Test)
QtCore.QMetaObject.connectSlotsByName(Test)
def retranslateUi(self, Test):
Test.setWindowTitle(QtWidgets.QApplication.translate("Test", "Dialog", None, -1))
self.addRow.setText(QtWidgets.QApplication.translate("Test", "add row", None, -1))
The actual classes involved (I use PySide2):
from PySide2 import QtWidgets, QtCore, QtGui
from _test import Ui_Test
class MyDialog(QtWidgets.QDialog):
def __init__(self, parent = None):
super(MyDialog, self).__init__(parent = parent)
self.ui = Ui_Test()
self.ui.setupUi(self)
self._model = MyModel([["first row 1 col", "first row 2"],["second row 1", "second row 2"]])
self.ui.tableView.setModel(self._model)
self.ui.addRow.clicked.connect(self._model.addRow)
self.ui.tableView.setItemDelegate(MyDelegate(self.ui.tableView))
# this is crucial: we need to be sure that the selection is single on the view
self.ui.tableView.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectItems)
self.ui.tableView.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
class MyModel(QtCore.QAbstractTableModel):
def __init__(self, table_data = None, parent = None):
super(MyModel, self).__init__(parent = parent)
if not table_data: self._data = []
self._data = table_data
def rowCount(self, parent = None):
return len(self._data)
def columnCount(self, parent = None):
return 2
def addRow(self):
self._data.append(["new item", "new item"])
self.layoutChanged.emit()
def removeRow(self, row):
if 0 <= row < self.rowCount():
del self._data[row]
self.layoutChanged.emit()
def data(self, index, role = QtCore.Qt.DisplayRole):
if index.isValid():
if role == QtCore.Qt.DisplayRole:
row = index.row()
col = index.column()
return self._data[row][col]
def setData(self, index, value, role = QtCore.Qt.EditRole):
if index.isValid():
if role == QtCore.Qt.EditRole:
row = index.row()
col = index.column()
self._data[row][col] = str(value)
return True
else:
return False
else:
return False
def flags(self, index):
return QtCore.Qt.ItemIsSelectable|QtCore.Qt.ItemIsEditable|QtCore.Qt.ItemIsEnabled
class MyDelegate(QtWidgets.QItemDelegate):
def __init__(self, parent = None):
super(MyDelegate, self).__init__(parent)
self.view = parent
def eventFilter(self, editor, event):
# there is a lot of checking in order to identify the desired situation
# and avoid errors
if isinstance(event, QtGui.QKeyEvent):
if event.type() == QtCore.QEvent.KeyPress:
if event.key() == QtCore.Qt.Key_Escape:
# we should have a list here of length one (due to selection restrictions on the view)
index = self.view.selectedIndexes()
if index:
if index[0].isValid():
row = index[0].row()
self.view.model().removeRow(row)
return super(MyDelegate, self).eventFilter(editor, event)
if __name__ == '__main__':
app = QtWidgets.QApplication()
diag = MyDialog()
diag.show()
app.exec_()
I'm attempting to override QTreeView to handle adjusting parents and children if the checkbox is modified. I'm not able to emit a signal however, and I'm not sure if it's because I'm trying to subclass QtGui and not QtWidgets.
Here is the code that will trigger the error:
class QStandardItem(QtGui.QStandardItem):
someSignal = QtCore.Signal()
def __init__(self, *args, **kwargs):
QtGui.QStandardItem.__init__(self, *args, **kwargs)
self.someSignal.emit()
>>> QStandardItem()
# AttributeError: 'PySide2.QtCore.Signal' object has no attribute 'emit' #
Here's my current code just for reference:
class QStandardItem(QtGui.QStandardItem):
checkStateChanged = QtCore.Signal(object)
def __init__(self, *args, **kwargs):
QtGui.QStandardItem.__init__(self, *args, **kwargs)
def setData(self, data, role):
if role == QtCore.Qt.CheckStateRole:
self.checkStateChanged.emit(self)
QtGui.QStandardItem.setData(self, data, role)
class QTreeView(QtWidgets.QTreeView):
def __init__(self, *args, **kwargs):
QtWidgets.QTreeView.__init__(self, *args, **kwargs)
#I need to know when to trigger this as it edits other nodes
def checkStateChanged(self, model_index):
selected_item = self.model().itemFromIndex(model_index)
check_state = selected_item.checkState()
#Handle child nodes
for i in range(selected_item.rowCount()):
child_item = selected_item.child(i)
if child_item.isCheckable():
child_item.setCheckState(check_state)
#Handle parent nodes
parent_item = selected_item.parent()
check_states = {QtCore.Qt.Checked: 0,
QtCore.Qt.PartiallyChecked: 1,
QtCore.Qt.Unchecked: 2}
counts = [0, 0, 0]
if parent_item is not None:
for i in range(parent_item.rowCount()):
child_item = parent_item.child(i)
if child_item.isCheckable():
counts[check_states[child_item.checkState()]] += 1
if counts[0] and not counts[1] and not counts[2]:
parent_item.setCheckState(QtCore.Qt.Checked)
elif not counts[0] and not counts[1] and counts[2]:
parent_item.setCheckState(QtCore.Qt.Unchecked)
else:
parent_item.setCheckState(QtCore.Qt.PartiallyChecked)
As you have pointed out only the classes that inherit from QObject can emit signals, QStandardItem is not a QObject and therefore generates that problem. The appropriate option is to use QStandardItemModel, for this we overwrite the setData() method and establish a logic to verify if the state has changed and then the QStandardItem is issued using the itemFromIndex() method that returns a QStandardItem given a QModelIndex.
Example:
from PySide2 import QtCore, QtGui, QtWidgets
class StandardItemModel(QtGui.QStandardItemModel):
checkStateChanged = QtCore.Signal(QtGui.QStandardItem)
def setData(self, index, value, role=QtCore.Qt.EditRole):
if role == QtCore.Qt.CheckStateRole:
last_value = self.data(index, role)
val = super(StandardItemModel, self).setData(index, value, role)
if role == QtCore.Qt.CheckStateRole and val:
current_value = self.data(index, role)
if last_value != current_value:
it = self.itemFromIndex(index)
self.checkStateChanged.emit(it)
return val
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
w = QtWidgets.QTreeView()
model = StandardItemModel(w)
w.setModel(model)
model.checkStateChanged.connect(self.foo_slot)
for i in range(4):
top_level = QtGui.QStandardItem("{}".format(i))
top_level.setCheckable(True)
model.appendRow(top_level)
for j in range(5):
it = QtGui.QStandardItem("{}-{}".format(i, j))
it.setCheckable(True)
top_level.appendRow(it)
w.expandAll()
self.setCentralWidget(w)
#QtCore.Slot(QtGui.QStandardItem)
def foo_slot(self, item):
print(item.text(), item.checkState())
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
not 100% sure but for Python in order to define your own signal you have to use:
QtCore.pyqtSignal()
Also you might need to also inherint QtCore.QObject in order to use signals
Edit: Just saw your using pyside so scrap the first solution but inherting QObject is your solution than
I have a GUI that consists of a number of sliders, and instead of updating the sliders manually when the underlying data changes, I'd like to store the data in a subclass of QAbstractListModel and have the slider positions update automatically. My subclass looks like this:
from PyQt4 import QtCore
class myDataModel(QtCore.QAbstractListModel):
def __init__(self, initData, parent=None):
super(myDataModel, self).__init__(parent)
self.__data = initData
def data(self, index, role=QtCore.Qt.DisplayRole):
if not index.isValid():
return None
if index.row() > len(self.__data):
return None
if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
return self.__data[index.row()]
return None
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self.__data)
def setData(self, index, value, role=QtCore.Qt.EditRole):
if not index.isValid() or role != QtCore.Qt.EditRole:
return False
self.__data[index.row()] = value
self.dataChanged.emit(index, index)
return True
How can I connect this model to the sliders in my GUI so that when the data in the model is changed, the sliders change, and vice versa?
Edit: Here is a mockup of the basic interface I have been working on:
Edit: I still haven't been able to get this to work. Here is my model class:
class dataModel(QtCore.QAbstractListModel):
def __init__(self, initData, parent=None):
super(dataModel, self).__init__(parent)
self.__data = initData
def data(self, index, role=QtCore.Qt.DisplayRole):
if not index.isValid():
return None
if index.row() > len(self.__data):
return None
if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
return self.__data[index.row()]
return None
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self.__data)
def setData(self, index, value, role=QtCore.Qt.EditRole):
if not index.isValid() or role != QtCore.Qt.EditRole:
return False
self.__data[index.row()] = value
self.dataChanged.emit(index, index)
return True
Here is the Delegate class:
class sliderDelegate(QtGui.QItemDelegate):
'''
classdocs
'''
def __init__(self, parent=None):
'''
Constructor
'''
super(sliderDelegate, self).__init__(parent)
def setEditorData(self, editor, index):
editor.setValue(index.model().data(index, QtCore.Qt.EditRole))
def setModelData(self, editor, model, index):
model.setData(index, editor.value(), QtCore.Qt.EditRole)
And here is the setup code:
self._model = dataModel([0 for i in xrange(20)])
self._parameterMapper = QtGui.QDataWidgetMapper(mainWindowInstance)
self._parameterMapper.setModel(self._model)
self._parameterMapper.setItemDelegate(sliderDelegate(mainWindowInstance))
self._parameterMapper.addMapping(self._mainWindowInstance.ui.mySlider, 0)
self._parameterMapper.toFirst()
Unfortunately I get the following error when toFirst() is called:
editor.setValue(index.model().data(index, QtCore.Qt.EditRole))
AttributeError: 'NoneType' object has no attribute 'data'
Any help would be appreciated.
So I haven't used QDataWidgetMapper. It does look interesting, but looks more useful for when you want to have multiple widgets updated to a particular row in a model (and be able to switch between rows easily), rather than each row of a model corresponding to the value of a widget (which I think is what you are after).
So this is my rather rough implementation. Hopefully you'll be able to extend it to your application (might need a bit more error checking added, and maybe the ability to link multiple sliders to a single model row, and possibly then extending to other types of widgets)
When you drag the slider, the model is updated to the sliders new value. I've also added a text box where you can type in a number, and click the button, which will set the model to a specific value. You will notice the slider will update to this value!
import sys
from PyQt4 import QtGui
from PyQt4 import QtCore
class MainWindow(QtGui.QWidget):
def __init__(self):
super(MainWindow, self).__init__()
main_layout = QtGui.QVBoxLayout()
# Create the model
self.model = MyModel()
# Create a slider and link it to the model
self.slider1 = QtGui.QSlider()
self.model.add_slider(self.slider1)
main_layout.addWidget(self.slider1)
# Add a lineEdit and button to force update the model
# Note that the LineEdit is not linked to the model, so won't update with the slider
self.edit = QtGui.QLineEdit()
button = QtGui.QPushButton('update model')
button.clicked.connect(self.on_clicked)
main_layout.addWidget(self.edit)
main_layout.addWidget(button)
self.setLayout(main_layout)
def on_clicked(self):
self.model.update_model(int(self.edit.text()),self.slider1)
class MyModel(QtGui.QStandardItemModel):
def __init__(self,*args,**kwargs):
super(MyModel,self).__init__(*args,**kwargs)
self._slider_list = {}
self.itemChanged.connect(self.on_item_changed)
def add_slider(self,slider):
if slider in self._slider_list:
raise Exception('You cannot link a slider to the model twice')
item = QtGui.QStandardItem(str(slider.value()))
self._slider_list[slider] = item
self.appendRow(item)
slider.valueChanged.connect(lambda value: self.update_model(value,slider))
def update_model(self,value,slider):
if str(value) != self._slider_list[slider].text():
self._slider_list[slider].setText(str(value))
print 'update_model: %d'%value
def on_item_changed(self,item):
slider = self._slider_list.keys()[self._slider_list.values().index(item)]
if slider.value() != int(item.text()):
slider.setValue(int(item.text()))
print 'on_item_changed: %s'%item.text()
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
Hope that helps!