Uso básico
Sobre la arquitectura: Model/View
-fuente de información-
La idea de esta arquitectura es separar la visualización de la manipulación de la información. Para ello, se dispone de un "visor" con interfaces claras y predefinidas. A ese visor le daremos un objeto (el modelo) que implementa dicha interfaz y que es el que trata con la información.
Una ventaja de esta arquitectura es que dado un Modelo, podemos disponer de diferentes vistas de la misma información sin tener que tocar nada (las vistas están ya implementadas y símplemente tienen que ser configuradas).
El modelo
El modelo accede a la información usando una serie de índice "índices del modelo" (referencias temporales a trozos de información; ya que la estructura puede cambiar y los índices pueden dejar de ser válidos con el tiempo). Esto permite aislar la representación de la información de la forma en la que se accede a ella. Las vistas usan estos índices cuando pretenden mostrar información. Para obtener un índice asociado a un elemento de información se necesitan: el índice del padre, la fila y la columna.
QModelIndex() representa el elemento raíz, es decir, el elemento del que cuelga el resto de la estructura. En una estructura tipo tabla, siempre podemos referenciar cualquier elemento diciendo que QModelIndex() es el padre y dando también la fila y la columna. En una estructura tipo árbol como la que estamos viendo, es más complejo. Los elementos que cuelgan del elemento raíz si usarán como padre QModelIndex. Sin embargo, los elementos que cuelgan de otro padre, tienen que hacer referencia al índice del mismo.
Por último, recordar que tanto filas como columnas comienzan a contar desde 0.
Modelo mínimo
Las funciones mínimas a implementar en un modelo son:
- rowCount(): devuelve el número de filas dado un padre. Si el padre es válido, entonces "rowCount" devuelve el número de hijos que tiene el padre. (NOTA: si el modelo para una tabla, "rowCount()" deberá devolver 0 cuando el padre es válido).
- data(): returns an item of data corresponding to a specified model index.
Si el modelo es jerárquico (como ocurre en TreeView) es obligatorio además: (para mantener las relaciones entre padres e hijos)
- index():
- parent(): devuelve el padre de un elemento (el elemento viene indicado por su índice).
No obligatorio pero muy recomendable es:
- headerData(): permite que TreeView y TableView muestren las cabeceras de la tabla/árbol.
En el ejemplo que viene con PyQt4: simpletreemodel.pyw(se adjunto a continuación el código), se puede observar lo siguiente:
- Las dos líneas siguientes. En ellas, se define cual es el "visor" y se le pasa como parámetro el "modelo" que es quien trata con la fuente de información. (Más info sobre QTreeView)
view = QtGui.QTreeView() view.setModel(model)
- El "modelo" tiene una interfaz predefinida. Ello se tiene porque hereda de la clase: QtCore.QAbstractItemModel. Podemos encontrar más información sobre esa clase: QAbstractItemModel
class TreeModel(QtCore.QAbstractItemModel): ......
- Analizamos class TreeModel(QtCore.QAbstractItemModel)En el ejemplo básico, el modelo *TreeModel*, reimplementa una serie de funciones básicas:
- columnCount(self, parent): ??
- data(self, index, role): ??
- flags(self, index): no entiendo bien el propósito.
- headerData(self, section, orientation, role): (No entiendo bien el propósito)
- index(self, row, column, parent): devuelve un índice para un elemento de datos cuando es referenciado por el índice del padre y la fila y columna respecto a él. (Más info aquí).
- parent(self, index): obligatorio. Devuelve el padre dado el índice.
- rowCount(self, parent): cuenta el número de filas.
*setupModelData(self, lines, parent): no es obligatoria. Lo que hace es generar todos los elementos identificando cuáles son los padres y cuáles los hijos.
simpletreemodel.pyw
############################################################################ ## ## Copyright (C) 2005-2005 Trolltech AS. All rights reserved. ## ## This file is part of the example classes of the Qt Toolkit. ## ## This file may be used under the terms of the GNU General Public ## License version 2.0 as published by the Free Software Foundation ## and appearing in the file LICENSE.GPL included in the packaging of ## this file. Please review the following information to ensure GNU ## General Public Licensing requirements will be met: ## http://www.trolltech.com/products/qt/opensource.html ## ## If you are unsure which license is appropriate for your use, please ## review the following information: ## http://www.trolltech.com/products/qt/licensing.html or contact the ## sales department at sales@trolltech.com. ## ## This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE ## WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. ## ############################################################################ from PyQt4 import QtCore, QtGui import simpletreemodel_rc class TreeItem(object): def __init__(self, data, parent=None): self.parentItem = parent self.itemData = data self.childItems = [] def appendChild(self, item): self.childItems.append(item) def child(self, row): return self.childItems[row] def childCount(self): return len(self.childItems) def columnCount(self): return len(self.itemData) def data(self, column): try: return self.itemData[column] except IndexError: return None def parent(self): return self.parentItem def row(self): if self.parentItem: return self.parentItem.childItems.index(self) return 0 class TreeModel(QtCore.QAbstractItemModel): def __init__(self, data, parent=None): super(TreeModel, self).__init__(parent) self.rootItem = TreeItem(("Title", "Summary")) self.setupModelData(data.split('\n'), self.rootItem) def columnCount(self, parent): if parent.isValid(): return parent.internalPointer().columnCount() else: return self.rootItem.columnCount() def data(self, index, role): if not index.isValid(): return None if role != QtCore.Qt.DisplayRole: return None item = index.internalPointer() return item.data(index.column()) def flags(self, index): if not index.isValid(): return QtCore.Qt.NoItemFlags return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable def headerData(self, section, orientation, role): if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole: return self.rootItem.data(section) return None def index(self, row, column, parent): if not self.hasIndex(row, column, parent): return QtCore.QModelIndex() if not parent.isValid(): parentItem = self.rootItem else: parentItem = parent.internalPointer() childItem = parentItem.child(row) if childItem: return self.createIndex(row, column, childItem) else: return QtCore.QModelIndex() def parent(self, index): if not index.isValid(): return QtCore.QModelIndex() childItem = index.internalPointer() parentItem = childItem.parent() if parentItem == self.rootItem: return QtCore.QModelIndex() return self.createIndex(parentItem.row(), 0, parentItem) def rowCount(self, parent): if parent.column() > 0: return 0 if not parent.isValid(): parentItem = self.rootItem else: parentItem = parent.internalPointer() return parentItem.childCount() def setupModelData(self, lines, parent): parents = [parent] indentations = [0] number = 0 while number < len(lines): position = 0 while position < len(lines[number]): if lines[number][position] != ' ': break position += 1 lineData = lines[number][position:].trimmed() if lineData: # Read the column data from the rest of the line. columnData = [s for s in lineData.split('\t') if s] if position > indentations[-1]: # The last child of the current parent is now the new # parent unless the current parent has no children. if parents[-1].childCount() > 0: parents.append(parents[-1].child(parents[-1].childCount() - 1)) indentations.append(position) else: while position < indentations[-1] and len(parents) > 0: parents.pop() indentations.pop() # Append a new item to the current parent's list of children. parents[-1].appendChild(TreeItem(columnData, parents[-1])) number += 1 if __name__ == '__main__': import sys app = QtGui.QApplication(sys.argv) f = QtCore.QFile(':/default.txt') f.open(QtCore.QIODevice.ReadOnly) model = TreeModel(f.readAll()) f.close() view = QtGui.QTreeView() view.setModel(model) view.setWindowTitle("Simple Tree Model") view.show() sys.exit(app.exec_())
Explicaciones más detalladas
def index(self, row, column, parent): obligatorio para estructuras jerárquicas.
Lo primero a destacar es:
- row: es un entero que indica la fila.
- column: es un entero que indica la columna.
- parent: es del tipo QModelIndex. Es decir, es "model index" del padre desde el que row y column nos llevan a este objeto (hijo suyo). En una tabla sería el objeto raíz, pero en un TreeView es el "padre" del que cualga el elemento considerado.
def index(self, row, column, parent): # El modelo en sí: "self" tiene que tener un "model index". Si no lo tiene, # se le asigna el del elemento raíz del árbol que siempre será: QtCore.QModelIndex() if not self.hasIndex(row, column, parent): return QtCore.QModelIndex() # "parent" es del "QModelIndex". isValid() devuelve verdadero si es del tipo "model index" # Si es del tipo "model index", obtenemos el "internalPointer" porque es lo que necesita # posteriormente la función "createIndex". # http://doc.trolltech.com/4.6/itemviews-simpletreemodel.html dice: # "If an invalid model index is specified as the parent, it is up to the model to return an index that corresponds to a top-level item in the model." if not parent.isValid(): parentItem = self.rootItem else: # En otras palabras, si tenemos un "model index", obtenemos el objeto padre (parentItem) # a partir del "model index" del padre (parent). parentItem = parent.internalPointer() # Podemos obtener el objeto hijo ahora que disponemos del objeto padre (parentItem). childItem = parentItem.child(row) if childItem: # Si se ha obtenido el hijo, creamos el índice de la forma estándar: return self.createIndex(row, column, childItem) else: #Si no hay ningún hijo, entonces el índice es el del objeto raíz: return QtCore.QModelIndex()
La función createIndex es una función privada (nunca la implementaremos nosotros). Su uso es obligatorio para crear los índices. Crea índices usando como parámetros:
- Fila
- Columna
- "Internal pointer" del objeto del objeto del que creamos el índice. Eso significa que damos directamente el "hijo del padre" (childItem). Es el motivo por el que se ha tenido que calcular.
El parámetro "parent" es del tipo QModelIndex. Ese tipo contiene las funciones:
- isValid(): verdadero si se trata del tipo "model index".
- internalPointer(): Returns a void * pointer used by the model to associate the index with the internal data structure. See also QAbstractItemModel::createIndex().
def rowCount(self, parent): obligatorio
La funciín rowCount devuelve el número de filas dado un padre.
El "parent" es del tipo model index: QModelIndex. Si el padre es válido, entonces "rowCount" devuelve el número de hijos que tiene el padre. (NOTA: si el modelo para una tabla, "rowCount()" deberá devolver 0 cuando el padre es válido).
def rowCount(self, parent): # Si se trata de una tabla devuelve cero (ver el comentario debajo del código) if parent.column() > 0: return 0 # Obtenemos el elemento padre (parentItem). if not parent.isValid(): # Si parent no es del tipo ModelIndex, supondremos que el elemento padre es el raíz. parentItem = self.rootItem else: # Si el padre es del tipo QModelIndex: parentItem = parent.internalPointer() # Ahora que disponemos del "elementoPadre", contamos los hijos que tiene. return parentItem.childCount()
Cabe destacar la llamada parent.column(). La función column dentro de QModelIndex, devuelve la columna a la que el índice de este modelo se refiere. El propósito de ese IF es por tanto detectar si el modelo es para una Tabla o para un Tree. Si se trata de una tabla, devuelve 0, tal y como exige la función rowCount
def data(self, index, role): obligatorio
La función data devuelve la información almacenada dados:
- model index
- role dado al elemento
Note: If you do not have a value to return, return an invalid QVariant instead of returning 0.
def data(self, index, role): # Si el índice no es válido, no devuelve ningún valor. if not index.isValid(): return None # Si el role no es QtCore.Qt.DisplayRole, no se devuelve ningún valor. if role != QtCore.Qt.DisplayRole: return None # Obtenemos el objeto elemento a partir del índice. item = index.internalPointer() # index.column(): nos da la columna a la que hace referencia el índice. # item.data(_column): obtenemos el contenido que dicho elemento tiene en cierta columna. return item.data(index.column())
def parent(self, index): obligatorio
La función parent devuelve el padre de un elemento (el elemento viene indicado por su "model index"). Si el modelo no tiene padre, se devuelve un "invalid QModelIndex". Realmente devuelve el índice del padreno el objeto padre.
Una convención usada en modelos que exponen estructuras jerárquicas es que sólo elementos en la primera columna tienen hijos. Por ese motivo, cuando se reimplementa esta función en una subclase, la columna devuelta de un QModelIndex sería 0.
Cuando se reimplementa esta función nunca llamar a funciones de QModelIndex tales como parent(), ya que llamarán a su vez a nuestra implementación produciendo una recursión infinita.
def parent(self, index): # Si el índice no es válido, damos el elemento raíz. if not index.isValid(): return QtCore.QModelIndex() # Obtenemos el objeto del tipo TreeItem a partir del índice. childItem = index.internalPointer() # Obtenemos el padre usando la función parent() definida en la clase TreeItem definida con anterioridad. parentItem = childItem.parent() # En cierto modo es como si creasen los índices. if parentItem == self.rootItem: # Si el elemento es el raíz devuelve el elemento raíz. return QtCore.QModelIndex() # En caso contrario, crea el índice usando createIndex return self.createIndex(parentItem.row(), 0, parentItem)
def headerData(self, section, orientation, role): recomendable
La función headerData devuelve datos dados: role y sección en la cabecera con una orientación especificada.
Para cabeceras horizontales, el número de sección corresponde con el número de columna. Para las verticales, el número de sección representa la fila.
def headerData(self, section, orientation, role): if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole: return self.rootItem.data(section) return None
Lo siguiente cuando se toque el tema del orden
When using a QSortFilterProxyModel, its indexes have their own internal pointer. It is not advisable to access this internal pointer outside of the model. Use the data() function instead.





