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:
| gtk.gdk.ACTION_DEFAULT, gtk.gdk.ACTION_COPY, | Copiar los datos. |
| gtk.gdk.ACTION_MOVE | Mover los datos, es decir, primero copiarlos, luego borrarlos de la fuente utilizando el objetivo DELETE del protocolo de selecciones de las X. |
| gtk.gdk.ACTION_LINK | 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. |
| gtk.gdk.ACTION_PRIVATE | Acción especial que informa a la fuente que el destino va a hacer algo que el destino no comprende. |
| gtk.gdk.ACTION_ASK | 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.