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!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With