14.2. La Interfaz y Almacén de Datos TreeModel

14.2.1. Introducción

La interfaz TreeModel es implementada por todas las subclases de TreeModel y aporta métodos para:

  • la obtención de las características del conjunto de datos que almacena, tales como el número de columnas y el tipo de datos de éstas.
  • la obtención de un iterador (TreeIter), una referencia temporal que apunta a una fila del modelo.
  • la obtención de información sobre un nodo (o fila), tal como el número de nodos que descienden de él, una lista de éstos nodos hijos, el contenido de sus columnas y un puntero al nodo padre.
  • informar de los cambios que se produzcan elos datos del modelo (TreeModel).

14.2.2. Creación de Objetos TreeStore (árbol) y ListStore (lista)

Las clases base para el almacenamiento de datos ListStore (almacén de datos en lista) y TreeStore (almacén de datos en árbol) permiten definir y gestionar las filas y columnas de datos existentes en un modelo de árbol. Los constructores de ambos objetos precisan que el tipo de cada una de las columnas sea alguno de los siguientes:

  • tipos básicos de Python, tal como: int, str, long, float y object.
  • tipos de PyGTK tales como Button, VBox, gdk.Rectangle, gdk.Pixbuf, etc.
  • tipos GObject (GTypes de GTK+) especificados bien como constantes GObject Type o como cadenas. La mayoría de GTypes son traducidos a un tipo de Python:

    • gobject.TYPE_CHAR o 'gchar'
    • gobject.TYPE_UCHAR o 'guchar'
    • gobject.TYPE_BOOLEAN o 'gboolean'
    • gobject.TYPE_INT o 'gint'
    • gobject.TYPE_UINT o 'guint'
    • gobject.TYPE_LONG o 'glong
    • gobject.TYPE_ULONG o 'gulong
    • gobject.TYPE_INT64 o 'gint64'
    • gobject.TYPE_UINT64 o 'guint64'
    • gobject.TYPE_FLOAT o 'gfloat'
    • gobject.TYPE_DOUBLE o 'gdouble'
    • gobject.TYPE_STRING o 'gchararray'
    • gobject.TYPE_OBJECT o 'GObject

Por ejemplo, para crear un ListStore o TreeStore cuyas columnas contuviesen un gdk.Pixbuf (un tipo de imagen), un entero, una cadena y un tipo booleano habría que hacier algo así:

  liststore = ListStore(gtk.gdk.Pixbuf, int, str, 'gboolean')

  treestore = TreeStore(gtk.gdk.Pixbuf, int, str, 'gboolean')

Una vez que se crea un almacén de datos en Lista (ListStore) o un almacén de datos en árbol (TreeStore) y sus columnas han sido definidas, ya no es posible hacer en ellos cambios o modificaciones. Así mismo es importante tener en cuenta que no existe una relación predefinida entre las columnas de una vista de árbol (TreeView) y las columnas de su correspondiente modelo (TreeModel). Es decir, la quinta columna de datos de un modelo (TreeModel) puede mostrarse en la primera columna de una determinada vista de árbol (TreeView) y en la tercera columna en otra vista diferente. Por tanto, no es necesario preocuparse de cómo se mostrarán los datos a la hora de crear los almacenes de datos (Stores).

Si estos dos tipos de almacenes de datos no se ajustan a las necesidades de una aplicación es posible definir otros almacenes de datos personalizados en Python siempre que éstos implementen la interfaz TreeModel. Retomaremos esta cuestión más adelante en la sección dedicada a GenericTreeModel .

14.2.3. Cómo referirse a las filas de un modelo TreeModel

Antes de que podamos hablar de la gestion de las filas de datos de un almacén TreeStore o ListStore necesitamos una forma de especificar a qué columna nos queremos referir. PyGTK dispone de tres formas de indicar columnas de un modelo TreeModel rows: un camino de árbol, un iterador TreeIter y una referencia TreeRowReference.

14.2.3.1. Caminos de árbol (Tree Paths)

Un camino de árbol es una representación mediante enteros, una cadena o una tupla de la localización de una fila en un almacén de datos. Un valor entero especifica la fila del nivel superior del almacén, empezando por 0. Por ejemplo, un valor de camino de 4 especificaría la quinta fila del almacén de datos. De la misma manera, una representación mediante una cadena resultaría "4" y la representación como tupla (4,). Esto es suficiente para llegar a determinar cualquier fila en un almacén de datos en lista (ListStore), pero en el caso de un árbol (TreeStore) necesitamos poder indicar las filas hijas. Para este caso debemos usar la representación mediante una cadena o una tupla.

Dado que un almacén en árbol (TreeStore) puede tener una jerarquía de una profudidad arbitraria, la representación como cadena especifica el camino desde el nivel más alto hasta la fila escogida utilizando enteros separados mediante el carácter ":". De forma similar, la representación mediante una tupla especifica el camino en el árbol empezando desde el vértice hasta la fila como una secuencia de enteros. Como ejemplos correctos de representaciones con cadenas de caminos tenemos: "0:2" (especifica la fila que es la tercera hija de la primera fila) y "4:0:1" (especifica la fila que es la segunda hija del primer hijo de la quinta fila). De forma semejante, los mismos caminos son representados respectivamente por las tuplas (0, 2) y (4, 0, 1).

Un camino de árbol es la única manera en la que se puede hacer corresponder una fila de una vista de árbol (TreeView) a una fila de un modelo (TreeModel) debido a que los caminos de una y otra son iguales. También existen otros problemas con los caminos de árbol:

  • un camino de árbol puede especificar una fila que no existe en el almacén ListStore o TreeStore.
  • un camino de árbol puede apuntar a datos distintos tras insertar o borrar una fila en un ListStore o TreeStore.

PyGTK usa la representación como tupla al devolver caminos, pero acepta cualquiera de las tres formas de representar un camino. Por coherencia se debería usar la representación en forma de tupla.

Se puede obtener un camino de árbol a partir de un iterador TreeIter utilizando el método get_path():

  path = store.get_path(iter)

donde iter es un iterador TreeIter que apunta a una fila en el almacén y path es la tupla que representa el camino a la fila.

14.2.3.2. Iteradores TreeIter

Un iterador TreeIter es un objeto que aporta una referencia transitoria a la fila de un almacén ListStore o TreeStore. Si los contenidos del almacén cambian (generalmente porque se ha añadido o quitado una fila) el iterador TreeIter puede no ser ya válido. Un modelo TreeModel que dé soporte a iteradores persistentes debe establecer la bandera gtk.TREE_MODEL_ITERS_PERSIST. Una aplicación puede comprobar dicha bandera haciendo uso del método get_flags().

Los iteradore TreeIter se crean utilizando alguno de los métodos del modelo TreeModel, que son aplicables tanto a objetos del tipo TreeStore como ListStore:

  treeiter = store.get_iter(path)

donde treeiter apunta a la fila del camino path. La excepción ValueError se activa en el caso de que el camino no sea válido.

  treeiter = store.get_iter_first()

donde treeiter es un iterador TreeIter que apunta a la fila en el camino (0,). treeiter será None si el almacén está vacío.

  treeiter = store.iter_next(iter)

donde treeiter es un iterador TreeIter que apunta a la siguiente fila en el mismo nivel que el iterador TreeIter especificado por iter. treeiter tendrá el valor None si no hay una fila siguiente (iter también es invalidado).

Los siguientes métodos son de utilidad únicamente cuando se vaya a obtener un iterador TreeIter a partir de un almacén de datos de árbol (TreeStore):

  treeiter = treestore.iter_children(parent)

donde treeiter es un iterador TreeIter que apunta a la primera fija que desciende de la fila indicada por el iterador TreeIter indicado por parent. treeiter será None en caso de que no tenga descendientes.

  treeiter = treestore.iter_nth_child(parent, n)

donde treeiter es un iterador TreeIter que señala la fila hija (de índice n) de la fila especificada por el iterador TreeIter parent. parent puede ser None para obtener una fila del nivel superior . treeiter será None si no hay descendientes.

  treeiter = treestore.iter_parent(child)

donde treeiter es un TreeIter que apunta a la fila madre de la fila especificada por el iterador TreeIter child. treeiter será None si no hay descendientes.

Se puede obtener un camino a partir de un iterador TreeIter usando el método get_path() :

  path = store.get_path(iter)

donde iter es un iterador Treeiter que apunta a una fila del almacén, y path es su camino expresado como tupla.

14.2.3.3. Referencias persistentes a filas (TreeRowReferences)

Una referencia del tipo TreeRowReference es una referencia persistente a una fila de datos en un almacén. Mientras que el camino (es decir, su localización) de una fila puede cambiar a medida que se añden o quitan filas al almacén, una referencia del tipo TreeRowReference apuntará a la misma fila de datos en tanto exista.

Nota

Las referencias TreeRowReference únicamente están disponibles a partir de la versión 2.4 de PyGTK.

Se puede crear una referencia del tipo TreeRowReference utilizando su constructor:

  treerowref = TreeRowReference(model, path)

donde model es el modelo TreeModel que contiene la fila y path es el camino de la fila que hay que referenciar. Si path no es un camino válido para el modelo model entonces se devuelve None.

14.2.4. Adición de filas

14.2.4.1. Adición de filas a un almacén de datos del tipo ListStore

Una vez que se ha creado un almacén del tipo ListStore se han de añadir filas utilizando alguno de los métodos siguientes:

  iter = append(row=None)
  iter = prepend(row=None)
  iter = insert(position, row=None)
  iter = insert_before(sibling, row=None)
  iter = insert_after(sibling, row=None)

Cada uno de estos métodos insertan una fila en una posición implícita o especificada en el almacén ListStore. Los métodos append() y prepend() usan posiciones implícitas: tras la última fila y antes de la primera fila, respectivamente. El método insert() requiere un entero (el parámetro position) que especifica la localización en la que se insertará la fila. Los otros dos métodos necesitan un iterador TreeIter (sibling) que hace referencia a una fila en el almacén ListStore ante o tras la cual se insertará la fila.

El parámetro row especifica los datos que deberían ser insertados en la fila tras su creación. Si row es None o no se especifica, se crea una fila vacía. Si row se especifica debe ser una tupla o una lista que contenga tantos elementos como el número de columnas que posea el almacén ListStore. Los elementos también deben coincidir con los tipos de las correspondientes columnas del almacén ListStore.

Todos los métodos devuelven un iterador TreeIter que apunta a la fila recién insertada. El siguiente fragmento de código ilustra la creación de un almacén ListStore y la adición de filas de datos en él:

  ...
  liststore = gtk.ListStore(int, str, gtk.gdk.Color)
  liststore.append([0,'red',colormap.alloc_color('red')])
  liststore.append([1,'green',colormap.alloc_color('green')])
  iter = liststore.insert(1, (2,'blue',colormap.alloc_color('blue')) )
  iter = liststore.insert_after(iter, [3,'yellow',colormap.alloc_color('blue')])
  ...

14.2.4.2. Adición de filas a un almacén de datos del tipo TreeStore

La adición de filas a un almacén del tipo TreeStore es semejante a la operación sobre el tipo ListStore, salvo que también se debe especificar una fila madre (usando un iterador TreeIter) a la que añadir la nueva fila. Los métodos para el amacén TreeStore son:

  iter = append(parent, row=None)
  iter = prepend(parent, row=None)
  iter = insert(parent, position, row=None)
  iter = insert_before(parent, sibling, row=None)
  iter = insert_after(parent, sibling, row=None)

Si parent es None, la fila se añadirá a las filas del nivel superior.

Cada uno de estos métodos inserta una fila en una posición implícita o especifica da en el almacén TreeStore. Los métodos append() y prepend() usan posiciones implícitas: tras la última fila hija y antes de la primera fila hija, respectivamente. El método insert() requiere un entero (el parámetro position), que especifica el lugar en el que se insertará la fila hija. Los otros dos métodos necesitan un iterador TreeIter (sibling), que hace referencia a una fila hija en el almacén TreeStore ante o tras la que se insertará la fila.

El parámetro row especifica qué datos se deben insertar en la fila tras su creación. Si row es None o no se especifica, entonces se crea una fila vacía. Si row se especifica, debe ser una tupla o una lista que contenga tantos elementos como el número de columnas que posea el almacén TreeStore. Los elementos también deben coincidir en sus tipos con los de las correspondientes columnas del almacén TreeStore.

Todos los métodos devuelven un iterador TreeIter que apunta a la recién creada fila. El siguiente fragmento de código ilustra la creación de un TreeStore y la adición de filas de datos al mismo:

  ...
  folderpb = gtk.gdk.pixbuf_from_file('folder.xpm')
  filepb = gtk.gdk.pixbuf_from_file('file.xpm')
  treestore = gtk.TreeStore(int, str, gtk.gdk.Pixbuf)
  iter0 = treestore.append(None, [1,'(0,)',folderpb] )
  treestore.insert(iter0, 0, [11,'(0,0)',filepb])
  treestore.append(iter0, [12,'(0,1)',filepb])
  iter1 = treestore.insert_after(None, iter0, [2,'(1,)',folderpb])
  treestore.insert(iter1, 0, [22,'(1,1)',filepb])
  treestore.prepend(iter1, [21,'(1,0)',filepb])
  ...

14.2.4.3. Almacenes de datos de gran tamaño

Cuando los almacenes ListStore o TreeStore contienen un gran número de filas de datos la adición de nuevas filas puede llegar a ser muy lenta. Hay un par de cosas que se pueden hacer para mitigar este problema:

  • En el caso de añadir un gran número de filas, desconectar el modelo (TreeModel) de su vista (TreeView) usando el método set_model() con el parámetro model puesto a None evitar la actualización de la vista (TreeView) con cada inserción.
  • Igualmente útil puede ser desactivar la ordenación al insertar un gran número de filas. Para ello se usa el método set_default_sort_func() con el parámetro sort_func puesto a None.
  • Limitar el número de referencias del tipo TreeRowReference en uso también es importante, puesto que se actualiza su camino con cada adición o emilinación de filas.
  • Fijar la propiedad de la vista (TreeView) "fixed-height-mode" a TRUE, de forma que todas las filas tengan la misma altura y se evite el cálculo individual de la altura de cada fila. Esta opción solamente está disponible a partir de la versión 2.4 de PyGTK.

14.2.5. Eliminación de Filas

14.2.5.1. Eliminación de filas de un almacén ListStore

Es posible eliminar una fila de datos de un almacén ListStore usando el método remove() :

  treeiter = liststore.remove(iter)

donde iter es un iterador TreeIter que apunta a la fila que se ha de eliminar. El iterador devuelto TreeIter (treeiter) apunta a la siguiente fila o no es válido si iter apunta a la última fila.

El método clear() elimina todas las filas del almacén ListStore:

  liststore.clear()

14.2.5.2. Eliminación de filas de un almacén TreeStore

Los métodos que sirven para eliminar filas de un almacén TreeStore son similares a los métodos equivalentes del almacén ListStore:

  result = treestore.remove(iter)
  treestore.clear()

donde result es TRUE si la fila ha sido eliminada e iter apunta a la siguiente fila válida. En otro caso, result es FALSE e iter es invalidado.

14.2.6. Gestión de los datos de las filas

14.2.6.1. Establecemiento y obtención de los valores de los datos

Los métodos para acceder a los valores de los datos en los almacenes ListStore y TreeStore tienen el mismo formato. Todas las manipulaciones de datos en almacenes usan un iterador TreeIter que especifica la fila con la que se trabaja. Una vez obtenido, dicho iterador (TreeIter) puede usarse para obtener los valores de la columna de una fila utilizando el método get_value() :

  value = store.get_value(iter, column)

donde iter es un iterador TreeIter que apunta a una fila, column es un número de columna en el almacén store, y, value es el valor guardado en la posición fila-columna señalada.

Para obtener los valores de varias columnas en una única llamada se debe usar el método get() :

  values = store.get(iter, column, ...)

donde iter es un iterador TreeIter que apunta a una fila, column es un número de columna en el almacén store, y, ... representa cero o más números de columna adicionales y values es una tupla que contiene los valores obtenidos. Por ejemplo, para obtener los valores de las columnas 0 y 2:

  val0, val2 = store.get(iter, 0, 2)

Nota

El método get() solamente está disponible a partir de la versión 2.4 de PyGTK.

Para establecer el valor de una columna se emplea el método set_value() :

  store.set_value(iter, column, value)

donde iter (un iterador TreeIter) y column (un entero) especifican la posición fila-columna en el almacén store y column es el número de columna cuyo valor value debe ser fijado. value debe ser del mismo tipo de dato que la columna del almacén store.

En el caso de que se desee establecer de una sola vez el valor de más de una columna en una fila se debe usar el método set() :

  store.set(iter, ...)

donde iter especifica la fila del almacén y ... es uno o más pares de número de columna - valor que indican la columna y el valor que se va a fijar. Por ejemplo, la siguiente llamada:

  store.set(iter, 0, 'Foo', 5, 'Bar', 1, 123)

establece el valor de la primera columna como 'Foo', el de la sexta columna como 'Bar' y el de la segunda columna como 123, en la fila del almacén store especificada por el iterador iter.

14.2.6.2. Reorganización de filas en almacenes ListStore

Se pueden mover individualmente las filas de un almacén ListStore usando alguno de los siguientes métodos disponibles desde la versión 2.2 de PyGTK:

  liststore.swap(a, b)
  liststore.move_after(iter, position)
  liststore.move_before(iter, position)

swap() permuta la posición de las filas a las que hacen referencia los iteradores TreeIter a y b. move_after() y move_before() mueven la fila señalada por el iterador (TreeIter) iter a una posición anterior o posterior a la fila indicada por el iterador (TreeIter) position. Si position tiene el valor None, move_after() situará la fila al principio principio del almacén, mientras que move_before() lo hará al final del mismo.

En caso de que se desee reorganizar completamente las filas de datos de un almacén ListStore entonces es preferible usar el siguiente método:

  liststore.reorder(new_order)

donde new_order es una lista de enteros que especifican el nuevo orden de filas de la siguiente manera:

  new_order[nuevaposición] = antiguaposición

Por ejemplo, si liststore contuviese cuatro filas:

  'one'
  'two'
  'three'
  'four'

La llamada al método:

  liststore.reorder([2, 1, 3, 0])

produciría el siguiente orden:

  'three'
  'two'
  'four'
  'one'

Nota

Estos métodos únicamente reorganizarán almacenes ListStore no ordenados.

En el caso de querer reorganizar las filas de datos en la versión 2.0 de PyGTK es necesario recurrir a la eliminación e inserción de filas utilizando los métodos descritos en las secciones Adición de filas y Eliminación de filas.

14.2.6.3. Reorganización de filas en almacenes TreeStore

Los métodos que se usan para reorganizar las filas de un almacén TreeStore son semejantes a los utilizados con los almacenes del tipo ListStore, exceptuando que los primeros únicamente afectan a las filas hijas de una determinada fila madre. Es decir, no es posible, por ejemplo, intercambiar filas con filas madre distintas.:

  treestore.swap(a, b)
  treestore.move_after(iter, position)
  treestore.move_before(iter, position)

swap() intercambia las posiciones de las filas hija indicadas por los iteradores (TreeIter) a y b. a y b deben compartir su fila madre. move_after() y move_before() mueven la fila indicada por el iterador (TreeIter) iter a una posición posterior o anterior a la fila señalada por el iterador (TreeIter) position. iter y position deben compartir su fila madre. Si position tiene el valor None, move_after() situará la fila al principio del almacén, mientras que el método move_before() lo hará al final del mismo.

El método reorder() precisa un parámetro adicional que especifique la fila madre cuyas filas hijas serán reordenadas:

  treestore.reorder(parent, new_order)

donde new_order es una lista de enteros que especifican el nuevo orden de filas hijas de la fila madre especificada por el iterador (TreeIter) parent de la siguiente manera:

  new_order[nuevaposición] = antiguaposición

Por ejemplo, si treestore contuviese estas cuatro filas:

  'parent'
      'one'
      'two'
      'three'
      'four'

La llamada al método:

  treestore.reorder(parent, [2, 1, 3, 0])

produciría el siguiente orden:

  'parent'
      'three'
      'two'
      'four'
      'one'

Nota

Estos métodos únicamente reorganizarán almacenes TreeStore no ordenados.

14.2.6.4. Gestión de múltiples filas

Uno de los aspectos más problemáticos de la manipulación de almacenes ListStore y TreeStore es el trabajo con múltiples filas, como por ejemplo, mover varias filas de una fila madre a otra, o eliminar un conjunto de filas en función de determinados criterios. La dificultad surge de la necesidad de utilizar un iterador TreeIter que puede no ser ya válido como resultado de la operación que se realice. Es posible comprobar si los iteradores de unos almacenes ListStore y TreeStore con persistentes utilizando el método get_flags() y contrastando la existencia de la bandera gtk.TREE_MODEL_ITERS_PERSIST. Sin embargo, las clases apilables del tipo TreeModelFilter y TreeModelSort no poseen iteradores TreeIter persistentes.

Si aceptamos que los iteradores TreeIter no son persistentes, ¿cómo movemos todas las filas hijas de una dada a otra?. Para ello tenemos que:

  • iterar sobre la descendencia de la fila madre
  • obtener los datos de cada fila
  • eliminar cada fila hija
  • insertar una nueva fila con los datos de la antigua fila en la lista de la nueva fila madre

No podemos confiar en que el método remove() devuelva un iterador TreeIter correcto, por lo que simplemente solicitaremos el iterador del primer descendiente hasta que devuelva None. Una función posible para mover filas hijas sería:

  def move_child_rows(treestore, from_parent, to_parent):
    n_columns = treestore.get_n_columns()
    iter = treestore.iter_children(from_parent)
    while iter:
      values = treestore.get(iter, *range(n_columns))
      treestore.remove(iter)
      treestore.append(to_parent, values)
      iter = treestore.iter_children(from_parent)
    return

La función anterior abarca el caso sencillo en el que se mueven todas las filas hijas de una única fila madre, pero ¿qué ocurre si se desea elminar todas las filas del almacén TreeStore en función de determinado criterio, por ejemplo, el valor de la primera columna?. A primera vista, se podría pensar en la posibilidad de usar el método foreach(), de manera que se itere sobre todas las filas y entonces se eliminarían las que cumpliesen el criterio elegido:

  store.foreach(func, user_data)

donde func es una función que es llamada por cada fila y tiene la siguiente signatura:

  def func(model, path, iter, user_data):

donde model es el almacén de datos TreeModel, path es el camino de una fila en el modelo model, iter es un iterador TreeIter que apunta al camino path y user_data son los datos aportados. Si func devuelve TRUE entonces el método foreach() dejará de iterar y retornará.

El problema con este enfoque es que al cambiar los contenidos del almacén mientras se produce la iteración del método foreach() puede dar lugar a resultados impredecibles. El uso del método foreach() para crear y guardar referencias persistentes TreeRowReferences de las filas que van a ser eliminadas, para posteriormente eliminar estas referencias tras la finalización del método foreach() podría ser una estrategia adecuada, salvo que no funcionaría en las versiones 2.0 y 2.2 de PyGTK, donde no están disponibles las referencias persistentes del tipo TreeRowReference.

Una estrategia fiable que abarca todas las variantes de PyGTK consiste en llamar al método foreach() para reunir los caminos de las filas que se van a eliminar y luego proceder a eliminar dichas filas en orden inverso, de forma que se preserve la validez de los caminos en el árbol. El siguiente fragmento de código ejemplifica esta estrategia:

  ...
  # esta función selecciona una fila si el valor de la primera columna es >= que el valor de comparación
  # data es una tupla que contiene el valor de comparación y una lista para guardar los caminos
  def match_value_cb(model, path, iter, data):
    if model.get_value(iter, 0) >= data[0]:
      data[1].append(path)
    return False     # mantiene la iteración de foreach

  pathlist = []
  treestore.foreach(match_value_cb, (10, pathlist))

  # foreach funciona en orden de llegada (FIFO)
  pathlist.reverse()
  for path in pathlist:
    treestore.remove(treestore.get_iter(path))
  ...

En el caso de que se quisiese buscar en un almacén TreeStore la primera fila que cumpliese determinado criterio, probablemente sería preferible llevar a cabo personalmente la iteración con algo similar a:

   treestore = TreeStore(str)
   ...
   def match_func(model, iter, data):
       column, key = data # data es una tupla que contiene número de columna, clave (key)
       value = model.get_value(iter, column)
       return value == key
   def search(model, iter, func, data):
       while iter:
           if func(model, iter, data):
               return iter
           result = search(model, model.iter_children(iter), func, data)
           if result: return result
           iter = model.iter_next(iter)
       return None
   ...
   match_iter = search(treestore, treestore.iter_children(None), 
                       match_func, (0, 'foo'))

La función de búsqueda search() itera recursivamente sobre la fila (especificada por el iterador iter) y sus descendientes y sus fijas hijas en orden de llegada, en búsqueda de una fila que tenga una columna cuyo valor coincida con la cadena clave dada. La búsqueda termina al encontrarse una fila.

14.2.7. Soporte del protocolo de Python

Las clases que implementan la interfaz TreeModel (TreeStore y ListStore y en PyGTK 2.4 también TreeModelSort y TreeModelFilter) también soportan los protocolos de Python de mapeado e iteración (protocolos mapping e iterator). El protocolo de iterador permite usar la función de Python iter() sobre un TreeModel o crear un iterador que sirva para iterar sobre todas las filas superiores del TreeModel. Una característica más útil es la de iterar utilizando la orden for o la comprensión de listas. Por ejemplo:

  ...
  liststore = gtk.ListStore(str, str)
  ...
  # añadimos algunas filas al almacén liststore
  ...
  # bucle for
  for row in liststore:
      # procesado de cada fila
  ...
  # comprensión de lista que devuelve una lista de los valores de la primera columna
  values = [ r[0] for r in liststore ]
  ...

Otras partes del protocolo de mapeado que están soportadas son el uso de del para eliminar un fila del modelo y la obtención de un TreeModelRow de PyGTK del modelo utilizando un valor de clave que sea un camino o in iterador TreeIter. Por ejemplo, las siguientes instrucciones devuelven la primera fila de un modelo TreeModel y la instrucción final borra la primera fila hija de la primera fila:

  row = model[0]
  row = model['0']
  row = model["0"]
  row = model[(0,)]
  i = model.get_iter(0)
  row = model[i]
  del model[(0,0)]

Además, se pueden fijar los valores en una fila existente de forma parecida a esto:

  ...
  liststore = gtk.ListStore(str, int, object)
  ...
  liststore[0] = ['Button', 23, gtk.Button('Label')]

Los objetos TreeModelRow de PyGTK soportan los protocolos de Python de secuencia e iterador. Se puede obtener un iterador para recorrer los valores de columna en una fila o utilizar la instrucción for así como la comprensión de listas. Un TreeModelRow utiliza el número de columna como índice para extraer un valor. Por ejemplo:

  ...
  liststore = gtk.ListStore(str, int)
  liststore.append(['Random string', 514])
  ...
  row = liststore[0]
  value1 = row[1]
  value0 = liststore['0'][0]
  for value in row:
      print value
  val0, val1 = row
  ...

Haciendo uso del ejemplo de la sección anterior para iterar sobre un almacén TreeStore y localizar una fila que contenga un valor concreto, el código quedaría:

   treestore = TreeStore(str)
   ...
   def match_func(row, data):
       column, key = data # data es una tupla que contiene número de columna, clave (key)
       return row[column] == key
   ...
   def search(rows, func, data):
       if not rows: return None
       for row in rows:
           if func(row, data):
               return row
           result = search(row.iterchildren(), func, data)
           if result: return result
       return None
   ...
   match_row = search(treestore, match_func, (0, 'foo'))

También se puede fijar el valor de una columna utilizando:

  treestore[(1,0,1)][1] = 'abc'

Un TreeModelRow también soporta la instrucción del y la conversión a listas y tuplas utilizando las funciones de Python list() and tuple(). Tal como se ilustra en el ejemplo superior, un objeto TreeModelRow posee el método iterchildren(), que devuelve un iterador para recorrer las filas hijas del objeto TreeModelRow.

14.2.8. Señales de TreeModel

Las aplicaciones pueden seguir los cambios de un modelo TreeModel conectándose a las señales que son emitidas por un modelo TreeModel: "row-changed" (fila modificada), "row-deleted" (fila eliminada), "row-inserted" (fila insertada), "row-has-child-toggled" (cambio de fila tiene descendencia) y "rows-reordered" (filas reordenadas). Estas señales son utilizadas por las vistas TreeView para seguir los cambios en su modelo TreeModel.

Si una aplicación se conecta a estas señales es posible que se produzcan grupos de señales al llamar algunos métodos. Por ejemplo, una llamada para añadir la primera fila a una fila madre:

  treestore.append(parent, ['qwe', 'asd', 123])

causará la emisión de las siguientes señales:

  • "row-inserted" en donde la fila insertada estará vacía.
  • "row-has-child-toggled" puesto que la fila madre ( parent) no tenía previamente ninguna fila hija.
  • "row-changed" para la fila insertada al establecer el valor 'qwe' en la primera columna.
  • "row-changed" para la fila insertada al establecer el valor 'asd en la segunda columna.
  • "row-changed" para la fila insertada al establecer el valor 123 en la tercera columna.

Nótese que no es posible obtener el orden de las fila en la retrollamada de "rows-reordered" puesto que el nuevo orden de las filas se pasa como un puntero opaco a un vector de enteros.

Consulte el Manual de Referencia de PyGTK para saber más sobre las señales de TreeModel.

14.2.9. Ordenación de filas de modelos TreeModel

14.2.9.1. La interfaz TreeSortable

Los objetos ListStore y TreeStore implementan la interfaz TreeSortable que les aporta métodos para controlar la ordenación de las filas de un modelo TreeModel. El elemento clave de la interfaz es "sort column ID", que es un valor entero arbitrario que está referido a una función de comparación de orden y a unos datos asociados de usuario. Dicho identificador de orden de columna debe ser mayor o igual a cero, y se crea utilizando el siguiente método:

  treesortable.set_sort_func(sort_column_id, sort_func, user_data=None)

donde sort_column_id es un valor entero asignado por el programador, sort_func es una función o método utilizado para comparar filas y user_data son los datos contextuales. sort_func tiene la siguiente signatura:

  def sort_func_function(model, iter1, iter2, data)
  def sort_func_method(self, model, iter1, iter2, data)

donde model el el modelo TreeModel que contiene las filas señaladas por los iteradores TreeIter iter1 e iter2 y data es user_data. sort_func debería devolver: -1 si la fila correspondiente a iter1 debe preceder a la fila correspondiente a iter2; 0, si las filas son iguales; y, 1 si la fila correspondiente a iter2 debe preceder a la indicada por iter1. La función de comparación de orden debería presuponer siempre que el orden de clasificación es ascendente (gtk.SORT_ASCENDING) puesto que el orden de clasificación será tenido en cuenta por las implementaciones de TreeSortable.

Puede usarse la misma función de comparación de orden para varios identificadores de orden de columna, haciendo variar los datos de información contextual contenidos en user_data. Por ejemplo, los datos user_data especificados en el método set_sort_func() podrían consistir en los índices de las columnas de donde se extraerían los datos de clasificación.

Una vez que se ha creado un identificador de orden de columna (sort column ID) en un almacén, es posible usarla para ordenar los datos llamando al método:

  treesortable.set_sort_column_id(sort_column_id, order)

donde order es el orden de clasificación, bien gtk.SORT_ASCENDING (ascendente) o gtk.SORT_DESCENDING (descendente).

Un identificador de orden de columna (sort_column_id) de valor igual a -1 indica que el almacén debería utilizar la función de comparación por defecto que se establece a través del método:

  treesortable.set_default_sort_func(sort_func, user_data=None)

Es posible comprobar si un almacén tiene una función de comparación por defecto haciendo uso del método:

  result = treesortable.has_default_sort_func()

que devuelve TRUEsi se ha establecido una función por defecto para las comparaciones.

Una vez que se ha establecido un identificador de orden de columna para un modelo TreeModel que implementa la interfaz TreeSortable este modelo ya no puede volver al estado original no ordenado. Es posible cambiar su función de clasificación o utilizar la función por defecto, pero ya no es posible establecer que ese modelo TreeModel carezca de dicha función.

14.2.9.2. Clasificación en almacenes ListStore y TreeStore

Cuando se crea un objeto del tipo ListStore o TreeStore automáticamente se establecen identificadores de orden de columna que se corresponden con el número de índice de las columnas. Por ejemplo, un almacén ListStore con tres columnas tendrían tres identificadores de orden de columna (0, 1, 2) generados automáticamente. Estos identificadores están asociados a una función de comparación interna que maneja los tipos fundamentales:

  • 'gboolean'
  • str
  • int
  • long
  • float

Inicialmente, tanto los almacenes ListStore como los TreeStore se fijan con un identificador de orden de columna igual a -2, que indica que no se utiliza una función de ordenación y que el almacén no está ordenado. Una vez que se fija un identificador de clasificación de columna en un ListStore o TreeStore ya no es posible volver a establecerlo al valor -2.

En caso de que se desee mantener los identificadores de orden de columna por defecto se debe establecer su valor fuera del rango del número de columnas, tal como 1000 o más. entonces es posible cambiar entre las funciones de ordenación por defecto y las de la aplicación según sea necesario.