2013-10-09 5 views
5

Ho una GUI che consiste in un numero di cursori e invece di aggiornare manualmente i cursori quando i dati sottostanti cambiano, vorrei memorizzare i dati in una sottoclasse di QAbstractListModel e le posizioni del cursore si aggiornano automaticamente. Il mio sottoclasse si presenta così:Creazione di un'interfaccia modello/visualizzazione con cursori usando PyQt

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 

Come faccio a collegare questo modello per i cursori nella mia GUI in modo che quando i dati nel modello è cambiato, i cursori cambiano, e viceversa?

Edit: Ecco un mockup dell'interfaccia di base che ho lavorato su: enter image description here

Edit: Non ho ancora stato in grado di farlo funzionare. Qui è la mia classe del modello:

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 

Qui è la classe Delegate:

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) 

E qui è il codice di impostazione:

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() 

Purtroppo ricevo il seguente errore quando toFirst() si chiama:

editor.setValue(index.model().data(index, QtCore.Qt.EditRole)) 
AttributeError: 'NoneType' object has no attribute 'data' 

Qualsiasi aiuto sarebbe apprezzato.

+0

Puoi fornire un esempio di come vorresti che fosse questa interfaccia (ad esempio in QtDesigner)? Inoltre, c'è un numero variabile di punti di dati (e quindi di cursori) o è fisso? –

+0

@LegoStormtroopr Ho aggiunto il mockup su cui ho lavorato in QtDesigner. Il numero di cursori verrà corretto nell'applicazione, ce ne sono solo molti. Francamente, mi piacerebbe essere in grado di memorizzare tutte le impostazioni dei widget nell'applicazione in un modello, e fare in modo che le combobox, ecc. Si aggiornino anche quando il modello viene cambiato, ma ottenere il funzionamento di tutti i cursori sarebbe un buon inizio e si spera lasciatemi estrapolare il metodo agli altri widget. – Bitrex

+0

Sfortunatamente, QAbstractListModel viene utilizzato al meglio come dati di elenco presentati in un modulo elenco. Potrebbe essere più semplice creare alcuni widget personalizzati (ad esempio raggruppare i 6 cursori a sinistra e un altro gruppo per l'8 a destra con le combo), quindi promuoverli in QtDesigner e scrivere codice personalizzato per le classi promosse? –

risposta

5

Quindi non ho usato QDataWidgetMapper. Sembra interessante, ma sembra più utile quando vuoi avere più widget aggiornati a una particolare riga in un modello (e essere in grado di passare facilmente da una riga all'altra), piuttosto che ogni riga di un modello corrispondente al valore di un widget (che penso sia ciò che cercate).

Quindi questa è la mia implementazione piuttosto approssimativa. Speriamo che tu possa estenderlo alla tua applicazione (potrebbe essere necessario aggiungere un po 'più di errori e forse la possibilità di collegare più slider a una singola riga del modello e possibilmente estendere ad altri tipi di widget)

Quando si trascina il cursore, il modello viene aggiornato con il nuovo valore dei cursori. Ho anche aggiunto una casella di testo in cui è possibile digitare un numero e fare clic sul pulsante, che imposterà il modello su un valore specifico. Noterai che il cursore si aggiornerà a questo valore!

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_()) 

Spero che questo aiuti!

+0

Molto bello. Grazie! – Bitrex

+0

Bel esempio, sto imparando lo sviluppo di MVC con Qt. Ma forse la connessione dovrebbe essere fatta all'interno della classe 'MainWindow', e al di fuori di' MyModel'? Ad esempio, 'self.slider1.valueChanged.connect (...)'? Grazie in anticipo per il vostro feedback. –

+0

@ Joël forse, immagino dipenda dal fatto che tu pensi che il tuo modello possa essere usato con una Vista Qt o meno. Se lo sei, quindi mantenere il codice del modello pulito è probabilmente buono. Ma se stai usando un modello per l'archiviazione e non implementando una vista Qt (come ho fatto io), allora probabilmente non importa! Se preferisci mantenere il codice del modello pulito perché si adatta meglio al tuo design, allora fallo! –