14.10. TreeModelSort y TreeModelFilter

Los objetos TreeModelSort y TreeModelFilter son modelos de árbol que se interponen entre el TreeModel de base (bien un TreeStore o un ListStore) y el TreeView que proporciona un modelo modificado mientras retiene la estructura original del modelo base. Estos modelos interpuestos implementan las interfaces TreeModel y TreeSortable pero no proporcionan ningún método para insertar o eliminar filas del modelo. Estas se deben insertar o eliminar del almacén subyacente. TreeModelSort proporciona un modelo cuyas filas están siempre ordenadas, mientras que TreeModelFilter proporciona un modelo que contiene un subconjunto de las filas del modelo base.

Si se desea, estos modelos pueden encadenarse en una sucesión arbitraria; es decir, un TreeModelFilter podría tener un TreeModelSort hijo, que podría tener otro TreeModelFilter hijo, y así sucesivamente. Mientras haya un almacén TreeStore o ListStore en el extrremo final de la cadena, todo debería funcionar. En PyGTK 2.0 y 2.2 los objetos TreeModelSort y TreeModelFilter no soportan el protocolo de mapeado de Python para TreeModel.

14.10.1. TreeModelSort (Modelo de Árbol Ordenado)

TreeModelSort mantiene un modelo ordenado del modelo hijo especificado en su constructor. El uso principal de un TreeModelSort el el de proporcionar múltiples vistas de un modelo que puedan ser ordenadas de forma distinta. Si se tienen múltiples vistas del mismo modelo entonces cualquier actividad de ordenación se ve reflejada en todas las vistas. Al usar un TreeModelSort el almacén base permanecen en su estado original, mientras que los modelos ordenados absorben toda la actividad de ordenación. Para crear un TreeModelSort se ha de usar el constructor:

  treemodelsort = gtk.TreeModelSort(child_model)

donde child_model es un TreeModel. La mayoría de los métodos de un TreeModelSort tienen que ver con la conversión de los caminos de árbol e iteradores TreeIter desde el modelo hijo al modelo ordenado y viceversa:

  sorted_path = treemodelsort.convert_child_path_to_path(child_path)
  child_path = treemodelsort.convert_path_to_child_path(sorted_path)

Estos métodos de conversión de caminos devuelven None si el camino dado no puede ser convertido en un camino del modelo ordenado o el modelo hijo respectivamente. Los métodos de conversión de los iteradores TreeIter son:

  sorted_iter = treemodelsort.convert_child_iter_to_iter(sorted_iter, child_iter)
 child_iter = treemodelsort.convert_iter_to_child_iter(child_iter, sorted_iter)

Los métodos de conversión de iteradores TreeIter duplican el argumento convertido (es tanto el valor de retorno como el primer argumento) debido a cuestiones de compatiblidad con versiones anteriores; se deben fijar el primer argumento como None y simplemente utilizar el valor devuelto. Por ejemplo:

  sorted_iter = treemodelsort.convert_child_iter_to_iter(None, child_iter)
  child_iter = treemodelsort.convert_iter_to_child_iter(None, sorted_iter)

Al igual que los métodos de conversión de caminos, estos métodos devuelven None si el TreeIter dado no puede ser convertido.

Se puede obtener el TreeModel hijo usando el método get_model().

Un ejemplo sencillo que utiliza un objeto TreeModelSort es treemodelsort.py. Figura 14.9, “Ejemplo de TreeModelSort” ilustra el resultado de ejecutar el programa y añadir seis filas:

Figura 14.9. Ejemplo de TreeModelSort

Ejemplo de TreeModelSort

En cada una de las columnas de las ventanas se puede hacer click para cambiar el orden de los elementos de forma independiente al de otras ventanas. Cuando se pulsa el botón "Add a Row" se añade una nueva fila al ListStore base y la nueva fila se muestra en cada TreeView como la fila seleccionada.

14.10.2. TreeModelFilter (Modelo de árbol filtrado)

Nota

El objeto TreeModelFilter está disponible a partir de la versión 2.4 de PyGTK y posteriores.

Un objeto TreeModelFilter proporciona varias formas de modificar la visualización del TreeModel de base, y permiten:

  • mostrar un subconjunto de las filas del modelo hijo en base a datos booleanos en una "columna visible", o en función del valor de retorno de una "función visible", que toma el modelo hijo, un iterador TreeIter que apunta a la fila del modelo hijo, y datos de usuario. En ambos casos si el valor booleano es TRUE la fila se mostrará; de otro modo, la fila quedará oculta.
  • usar un nodo raiz virtual para proporcionar una vista de un subárbol de los desdendientes de una fila en el modelo hijo. Esto únicamente tiene sentido si el almacén subyacente es del tipo TreeStore.
  • sintetizar las columnas y datos de un modelo en base a los datos del modelo hijo. Por ejemplo, se puede proporcionar una columna cuyos datos son calculados a partir de los datos de varias columnas del modelo hijo.

Un objeto TreeModelFilter se crea usando el método de TreeModel:

  treemodelfilter = treemodel.filter_new(root=None)

donde root es un camino de árbol en treemodel que especifica la raiz virtual del modelo o None si se va a usar el nodo raíz de treemodel.

Al establecer una "raíz virtual" cuando se crea el TreeModelFilter, se puede limitar la vista del modelo a las filas hijas de la fila "raíz" en la jerarquía del modelo hijo. Esto, naturalmente, solamente es útil cuando el modelo hijo está basado en un TreeStore. Por ejemplo, si se desea proporcionar una vista parcial del contenido de una unidad de CDROM, independiente del resto de elementos del ordenador.

Los modos de visibilidad son mútuamente excluyentes y únicamente se pueden fijar una vez. Es decir, una vez que se ha establecido una función o columna de visibilidad no puede ser cambiada y el modo alternativo no puede establecerse. El modo de visibilidad más simple extrae un valor booleano de una columna en el modelo hijo para determinar si la fila debería mostrarse. La columna de visibilidad se detemina usando:

  treemodelfilter.set_visible_column(column)

donde column es el número de columna en el TreeModel hijo, del que extraer los valores booleanos. Por ejemplo, el siguiente fragmento de código usa los valores de la tercera columna para determinar la visibilidad de las filas:

  ...
  treestore = gtk.TreeStore(str, str, "gboolean")
  ...
  modelfilter = treestore.filter_new()
  modelfilter.set_visible_column(2)
  ...

De esta manera, se mostrará cualquier fila de treestore que tenga un valor TRUE en la tercera columna.

Si se tiene criterios de visibilidad más complicados, una función de visibilidad debería proporcionar suficientes posibilidades:

  treemodelfilter.set_visible_func(func, data=None)

donde func es la función llamada para cada una de las filas del modelo hijo y determinar si se debe mostrar y data son datos de usuario pasados a func. func debería devolver TRUE si la fila debe ser mostrada. La signatura de func es:

  def func(model, iter, user_data)

donde model es el TreeModel hijo, iter es un TreeIter que apunta a una fila en model y user_data son los datos data entregados.

Si se hacen cambios en los criterios de visibilidad se debería hacer la llamada:

  treemodelfilter.refilter()

para forzar un refiltrado de las filas del modelo hijo.

Por ejemplo, el siguiente fragmento de código ilustra el uso de un TreeModelFilter que muestra filas en base a una comparación entre el valor de la tercera columna y los contenidos de los datos de usuario:

  ...
  def match_type(model, iter, udata):
      value = model.get_value(iter, 2)
      return value in udata
  ...
  show_vals = ['OPEN', 'NEW', 'RESO']
  liststore = gtk.ListStore(str, str, str)
  ...
  modelfilter = liststore.filter_new()
  modelfilter.set_visible_func(match_type, show_vals)
  ...

El programa treemodelfilter.py ilustra el uso del método set_visible_func(). Figura 14.10, “Ejemplo de Visibilidad en TreeModelFilter” muestra el resultado de la ejecución del programa.

Figura 14.10. Ejemplo de Visibilidad en TreeModelFilter

Ejemplo de Visibilidad en TreeModelFilter

Conmutando los botones de la parte inferior se cambian los contenidos del TreeView para mostrar únicamente las filas que coinciden con uno de los botones activos.

Una función de modificación proporciona otro nivel de control sobre la visualización en el TreeView hasta el punto en el que es posible sintetizar una o más columnas (incluso todas), que son representadas por el TreeModelFilter. Todavía es preciso usar un modelo hijo que sea un almacén TreeStore o ListStore para determinar el número de filas y la jerarquía, pero las columnas pueden ser las que se especifiquen en el método:

  treemodelfilter.set_modify_func(types, func, data=None)

donde types es una secuencia (lista or tupla) que especifica los tipos de las columnas que se representan, func es una función llamada para devolver el valor para una fila y columna y data es un argumento que pasar a func. La signatura de func es:

  def func(model, iter, column, user_data)

donde model es el TreeModelFilter, iter es un TreeIter que apunta a una fila del modelo, column es el número de la columna para el que se precisa un valor y user_data es el parámetro data. func debe devolver un valor que coincida con el tipo de column.

Una función de modificación es útil cuando se quiere proporcionar una columna de datos que necesita ser generada utilizando los datos de las columnas del modelo hijo. Por ejemplo, si se tuviese una columna que contiene fechas de nacimiento y se quisiese mostrar una columna de edades, una función de modificación podría generar la información de edad usando la fecha de nacimiento y la fecha actual. Otro ejemplo sería el decidir qué imagen mostrar en base al análisis de los datos (por ejemplo, el nombre de un archivo) de una columna. Este efecto también se puede conseguir utilizando el método TreeViewColumn set_cell_data_func().

Generalmente, dentro de la función de modificación, se tendrá que convertir el TreeModelFilter TreeIter a un iterador TreeIter del modelo hijo haciendo uso de:

  child_iter = treemodelfilter.convert_iter_to_child_iter(filter_iter)

Naturalmente, también es necesario obtener el modelo hijo usando:

  child_model = treemodelfilter.get_model()

Estos métodos dan acceso a la fila del modelo hijo y a sus valores para generar el valor de la fila y columna especificadas del TreeModelFilter. También hay un método para convertir un TreeIter hijo a un TreeIter de un modelo filtrado y métodos para convertir caminos del modelo filtrado a y desde caminos de los árboles hijo:

  filter_iter = treemodelfilter.convert_child_iter_to_iter(child_iter)

  child_path = treemodelfilter.convert_path_to_child_path(filter_path)
  filter_path = treemodelfilter.convert_child_path_to_path(child_path)

Naturalmente, es posible combinar los modos de visibilidad y la función de modificación para filtrar y sintetizar columnas. Para obtener incluso un mayor control sobre la vista sería necesario utilizar un TreeModel personalizado.