La reordenación de las filas de un TreeView
(y de las filas del modelo de árbol subyacente) se activa usando el método
set_reorderable() que se mencionó previamente. El método
set_reorderable() fija la propiedad "reorderable" al valor
especificado y permite o impide arrastrar y soltar en las filas del
TreeView. Cuando la propiedad "reorderable"
es TRUE es posible arrastar internamente filas del
TreeView y soltarlas en una nueva posición. Esta acción
provoca que las filas del TreeModel subyacente se reorganicen
para coincidir con la nueva situación. La reordenación mediante arrastrar y soltar de filas
funciona únicamente con almacenes no ordenados.
Si se quiere controlar el arrastar y soltar o tratar con el arrastrar y soltar desde fuentes externas de datos es necesario habilitar y controlar el arrastar y soltar con los siguientes métodos:
treeview.enable_model_drag_source(start_button_mask,targets,actions) treeview.enable_model_drag_dest(targets,actions)
Estos métodos permiten utilizar filas como fuente de arrastre y como lugar para soltar
respectivamente. start_button_mask es una máscara de modificación
(véase referencia de constantes gtk.gtk
Constants en el Manual de Referencia de PyGTK
) que especifica los botones o teclas que deben ser pulsadas para iniciar la operación
de arrastre. targets es una lista de
tuplas de 3 elementos que describen la información del objetivo que puede ser recibido o dado.
Para que tenga éxito el arrastar o soltar, por lo menos uno de los objetivos debe coincidir en
la fuente o destino del arrastre (p.e. el objetivo "STRING"). Cada tupla de 3 elementos
del objetivo contiene el nombre del objetivo, banderas (una combinación de
gtk.TARGET_SAME_APP y
gtk.TARGET_SAME_WIDGET o ninguno) y un identificador entero único.
actions describe cuál debería ser el resultado de la operación:
| Copiar los datos. |
| Mover los datos, es decir, primero copiarlos, luego borrarlos de la fuente
utilizando el objetivo DELETE del protocolo de selecciones de las X. |
| Añadir un enlace a los datos. Nótese que esto solamente es de utilidad si la fuente y el destino coinciden en el significado. |
| Acción especial que informa a la fuente que el destino va a hacer algo que el destino no comprende. |
| Pide al usuario qué hacer con los datos. |
Por ejemplo para definir un destino de un arrastrar y soltar:
treeview.enable_model_drag_dest([('text/plain', 0, 0)],
gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_MOVE)
Entonces habrá que gestionar la señal del control Widget
"drag-data-received" para recibir los datos recibidos - tal vez sustituyendo los datos
de la fila en la que se soltó el contenido. La signatura de la retrollamada de la señal
"drag-data-received" es:
def callback(widget,drag_context,x,y,selection_data,info,timestamp)
donde widget es el
TreeView, drag_context es un
DragContext que contiene el contexto de la selección,
x e y son la posición en dónde ocurrió
el soltar, selection_data es la
SelectionData que contiene los datos,
info es un entero identificador del tipo,
timestamp es la hora en la que sucedió el soltar. La fila
puede ser identificada llamando al método:
drop_info = treeview.get_dest_row_at_pos(x,y)
donde (x,
y) es la posición pasada a la función de retrollamada
y drop_info es una tupla de dos elementos que contiene el
camino de una fila y una constante de posición que indica donde se produce el soltar
respecto a la fila: gtk.TREE_VIEW_DROP_BEFORE,
gtk.TREE_VIEW_DROP_AFTER,
gtk.TREE_VIEW_DROP_INTO_OR_BEFORE o
gtk.TREE_VIEW_DROP_INTO_OR_AFTER. La función de retrollamada
podría ser algo parecido a:
treeview.enable_model_drag_dest([('text/plain', 0, 0)],
gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_MOVE)
treeview.connect("drag-data-received", drag_data_received_cb)
...
...
def drag_data_received_cb(treeview, context, x, y, selection, info, timestamp):
drop_info = treeview.get_dest_row_at_pos(x, y)
if drop_info:
model = treeview.get_model()
path, position = drop_info
data = selection.data
# do something with the data and the model
...
return
...
Si una fila se usa como fuente para arrastar debe manejar la señal de
Widget "drag-data-get" que llena una selección con los datos
que se devolverán al destino de arrastar y soltar con una función de retrollamada
con la signatura:
def callback(widget,drag_context,selection_data,info,timestamp)
Los parámetros de callback son similares a los de la
función de retrollamada de "drag-data-received". Puesto que a la retrollamada no se le
pasa un camino de árbol o una forma sencilla de obtener información sobre la
fila que está siendo arrastrada, asumiremos que la fila que está siendo arrastrada está
seleccionada y que el modo de selección es gtk.SELECTION_SINGLE o
gtk.SELECTION_BROWSE de modo que podemos tener la fila obteniendo
la TreeSelection, el modelo y el iterador
TreeIter que apunta a la fila. Por ejemplo,
se podría pasar texto así::
...
treestore = gtk.TreeStore(str, str)
...
treeview.enable_model_drag_source(gtk.gdk.BUTTON1_MASK,
[('text/plain', 0, 0)],
gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_MOVE)
treeview.connect("drag-data-get", drag_data_get_cb)
...
...
def drag_data_get_cb(treeview, context, selection, info, timestamp):
treeselection = treeview.get_selection()
model, iter = treeselection.get_selected()
text = model.get_value(iter, 1)
selection.set('text/plain', 8, text)
return
...
Un TreeView puede ser desactivado como fuente o destino
para arrastrar y soltar utilizando los métodos:
treeview.unset_rows_drag_source() treeview.unset_rows_drag_dest()
Es necesario un ejemplo para unir las piezas de código descritas más arriba. Este
ejemplo (treeviewdnd.py) es una lista en la
que se pueden arrastrar y soltar URLs. También se pueden reordenar las URLs de la lista
arrastrando y soltando en el interior del TreeView. Un par de
botones permiten limpiar la listar y eliminar un elemento seleccionado.
1 #!/usr/bin/env python
2
3 # example treeviewdnd.py
4
5 import pygtk
6 pygtk.require('2.0')
7 import gtk
8
9 class TreeViewDnDExample:
10
11 TARGETS = [
12 ('MY_TREE_MODEL_ROW', gtk.TARGET_SAME_WIDGET, 0),
13 ('text/plain', 0, 1),
14 ('TEXT', 0, 2),
15 ('STRING', 0, 3),
16 ]
17 # close the window and quit
18 def delete_event(self, widget, event, data=None):
19 gtk.main_quit()
20 return gtk.FALSE
21
22 def clear_selected(self, button):
23 selection = self.treeview.get_selection()
24 model, iter = selection.get_selected()
25 if iter:
26 model.remove(iter)
27 return
28
29 def __init__(self):
30 # Create a new window
31 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
32
33 self.window.set_title("URL Cache")
34
35 self.window.set_size_request(200, 200)
36
37 self.window.connect("delete_event", self.delete_event)
38
39 self.scrolledwindow = gtk.ScrolledWindow()
40 self.vbox = gtk.VBox()
41 self.hbox = gtk.HButtonBox()
42 self.vbox.pack_start(self.scrolledwindow, True)
43 self.vbox.pack_start(self.hbox, False)
44 self.b0 = gtk.Button('Clear All')
45 self.b1 = gtk.Button('Clear Selected')
46 self.hbox.pack_start(self.b0)
47 self.hbox.pack_start(self.b1)
48
49 # create a liststore with one string column to use as the model
50 self.liststore = gtk.ListStore(str)
51
52 # create the TreeView using liststore
53 self.treeview = gtk.TreeView(self.liststore)
54
55 # create a CellRenderer to render the data
56 self.cell = gtk.CellRendererText()
57
58 # create the TreeViewColumns to display the data
59 self.tvcolumn = gtk.TreeViewColumn('URL', self.cell, text=0)
60
61 # add columns to treeview
62 self.treeview.append_column(self.tvcolumn)
63 self.b0.connect_object('clicked', gtk.ListStore.clear, self.liststore)
64 self.b1.connect('clicked', self.clear_selected)
65 # make treeview searchable
66 self.treeview.set_search_column(0)
67
68 # Allow sorting on the column
69 self.tvcolumn.set_sort_column_id(0)
70
71 # Allow enable drag and drop of rows including row move
72 self.treeview.enable_model_drag_source( gtk.gdk.BUTTON1_MASK,
73 self.TARGETS,
74 gtk.gdk.ACTION_DEFAULT|
75 gtk.gdk.ACTION_MOVE)
76 self.treeview.enable_model_drag_dest(self.TARGETS,
77 gtk.gdk.ACTION_DEFAULT)
78
79 self.treeview.connect("drag_data_get", self.drag_data_get_data)
80 self.treeview.connect("drag_data_received",
81 self.drag_data_received_data)
82
83 self.scrolledwindow.add(self.treeview)
84 self.window.add(self.vbox)
85 self.window.show_all()
86
87 def drag_data_get_data(self, treeview, context, selection, target_id,
88 etime):
89 treeselection = treeview.get_selection()
90 model, iter = treeselection.get_selected()
91 data = model.get_value(iter, 0)
92 selection.set(selection.target, 8, data)
93
94 def drag_data_received_data(self, treeview, context, x, y, selection,
95 info, etime):
96 model = treeview.get_model()
97 data = selection.data
98 drop_info = treeview.get_dest_row_at_pos(x, y)
99 if drop_info:
100 path, position = drop_info
101 iter = model.get_iter(path)
102 if (position == gtk.TREE_VIEW_DROP_BEFORE
103 or position == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE):
104 model.insert_before(iter, [data])
105 else:
106 model.insert_after(iter, [data])
107 else:
108 model.append([data])
109 if context.action == gtk.gdk.ACTION_MOVE:
110 context.finish(True, True, etime)
111 return
112
113 def main():
114 gtk.main()
115
116 if __name__ == "__main__":
117 treeviewdndex = TreeViewDnDExample()
118 main()
El resultado de la ejecución del programa de ejemplo treeviewdnd.py se ilustra en Figura 14.8, “Ejemplo de Arrastrar y Soltar en TreeView”:
La clave para permitir tanto arrastrar y soltar externo como la reorganización
interna de filas es la organización de los objetivos (el atributo
TARGETS de la línea 11). Se crea y usa un objetivo específico de la aplicación
(MY_TREE_MODEL_ROW) para indicar un arrastar y soltar dentro del
TreeView estableciendo la bandera
gtk.TARGET_SAME_WIDGET. Estableciendo éste como el primer objetivo
para el destino del arrastre hará que se intente hacerlo coincidir primero con los
objetivos del origen de arrastre. Después, las acciones de fuente de arrastre deben incluir
gtk.gdk.ACTION_MOVE y gtk.gdk.ACTION_DEFAULT (véanse
las líneas 72-75). Cuando el destino recibe los datos de la fuente, si la acción
DragContext es gtk.gdk.ACTION_MOVE,
entonces se indica a la fuente que borre los datos (en este caso la fila) llamando al
método del DragContext finish()
(véanse las líneas 109-110). Un TreeView proporciona un
conjunto de funciones internas que estamos aprovechando para arrastrar, soltar y
eliminar los datos.