Tal como hablábamos Oscar y yo, no es posible desacoplar totalmente vista y modelo cuando se usa "gtk.TreeView", ya que obliga a que el formato del modelo (colores y demás atributos) también esté en el modelo. A pesar de que rompe la idea de MVC, parece conveniente apostar por TreeView porque nos da gran parte del trabajo ya hecho.
Otro problema de TreeView es que, al menos en el uso habitual, la configuración de las columnas "visibles" (TreeViewColumn, TVC) se realiza en base al modelo (store) disponible, con lo cual hay mucho acoplamiento entre ambos. Y esto va totalmente en contra de nuestra idea de hacer vistas "plugables" que puedan utilizarse con muchos inspectores diferentes.
Afortunadamente, TreeView dispone de un mecanismo que permite rellenar las columnas utilizando un callback en lugar de indicar una columna de un store: set_cell_data_func(). Utilizando esta alternativa y definiendo un metamodelo (que describe el modelo) es posible hacer vistas totalmente genéricas, que se autoconfiguran en base a la información facilitada por cada inspector concreto.
En cualquier caso, TreeView sigue necesitando un store. Hemos optado por hacerlo lo más simple posible: un array de objetos. La clase de esos objetos la define cada inspector, y su metamodelo indica cómo obtener los datos a partir de los objetos contenidos en el store. De modo que es muy flexible y con el acoplamiento más pequeño posible (de los que se nos han ocurrido).
Tal cómo está implementado en el programa que refería, este sistema tiene algunas limitaciones:
- Cada elemento del metamodelo corresponde a una columna de la vista. Por cada TVC, se crea un único 'cellrenderer' (indicado en el metamodelo). Se podría enriquecer el metamodelo para soportar varios cellrenders pero personalmente aquí aplicaría KISS.
- Cada elemento del metamodelo sólo puede afectar a un atributo de la columna correspondiente. Esta puede que sea una limitación inadmisible, habrá que discutirlo. Puede que en este caso esté justificado enriquecer el metamodelo para hacerlo posible.
- El store es una lista (ListStore) lo que implica que, en principio, no sería posible hacer vistas arbóreas. Yo creo que esto no es una limitación perjudicial en lo referente al store.
- Raramente será admisible cargar toda una jerarquía en el modelo. Yo creo que sería más adecuada una solución similar a la de nautilus: sólo se cargan los hijos cuando se expande el árbol. Para lograr eso podríamos crear un nuevo inspector para ese directorio y combinar los nuevos objetos en el store existente. Aquí hay varias alternativas de implementación que habría que probar.
- Al ser el store un array de objetos, las modificaciones en esos objetos no actualizan la vista. Esto sólo pasa con cambios asíncronos, por ejemplo, si cambia la fecha de un fichero cuando ya ha sido representado en la vista, ese cambio no se muestra. Se puede solucionar de una forma sencilla aunque no sé si muy elegante, forzando una modificación del store cuando sea necesaria. A la vez también es una ventaja, puesto que podemos modificar el modelo real sin que eso implique necesariamente que la vista deba recargarse.
Lo que voy a describir a continuación es un posible método para instanciación de inspectores y vistas. Una parte de esto es lo que pretende ser el ejemplo mvc.py.
- Al arrancar TP se registran en una 'factoría abstracta' todos los inspectors y vistas disponibles.
- El 'manager' recibe una petición para gestionar un uri.
- El 'manager' pide a la factoría un inspector que pueda manejar esa uri (puede haber varios). Se instancia el inspector pasándole una referencia al manager.
- El inspector crea un modelo básico, No hace falta calcular todos los datos hasta que sean necesarios (los pida la vista). Es decir, puede ser un modelo creado bajo demanda.
- El 'manager' pide a la factoría una vista que pueda representar ese inspector (puede haber varios). Se instancia la vista pasándole una referencia al inspector.
- La 'vista' se configura utilizando el metamodelo del inspector que se le pasa.
- El 'manager' pide a la vista el 'gui' y lo incrusta en la UI dónde corresponda.
Saludos, y espero comentarios.