Как перетащить и отсортировать GtkTreeView в GTK3?
Я переношу liblarch, библиотеку для обработки ориентированных ациклических графов, от PyGTK (GTK2) до интроспекции PyGObject (GTK3). Я столкнулся с проблемой с GtkTreeView.
Приложение, использующее liblarch, должно сортировать GtkTreeView по столбцу, но в то же время пользователь может перетаскивать строки, перемещать строку под другой строкой. Для этого мне пришлось вручную обрабатывать dnd_data_get()
и dnd_data_receive()
, что вполне нормально.
Существует минимальная настройка для GtkTreeView, которая работает под PyGTK. Строки сортируются, и пользователь может перемещать строки вокруг.
#!/usr/bin/python
# -*- coding: utf-8 -*-
import gtk
window = gtk.Window()
window.set_size_request(300, 200)
window.connect('delete_event', lambda w,e: gtk.main_quit())
# Define Liblarch Tree
store = gtk.TreeStore(str, str)
store.insert(None, -1, ["A", "Task A"])
store.insert(None, -1, ["B", "Task B"])
store.insert(None, -1, ["C", "Task C"])
d_parent = store.insert(None, -1, ["D", "Task D"])
store.insert(d_parent, -1, ["E", "Task E"])
# Define TreeView in similar way as it happens in GTG/Liblarch_gtk
tv = gtk.TreeView()
col = gtk.TreeViewColumn()
col.set_title("Title")
render_text = gtk.CellRendererText()
col.pack_start(render_text, expand=True)
col.add_attribute(render_text, 'markup', 1)
col.set_resizable(True)
col.set_expand(True)
col.set_sort_column_id(0)
tv.append_column(col)
tv.set_property("expander-column", col)
treemodel = store
def _sort_func(model, iter1, iter2):
""" Sort two iterators by function which gets node objects.
This is a simple wrapper which prepares node objects and then
call comparing function. In other case return default value -1
"""
node_a = model.get_value(iter1, 0)
node_b = model.get_value(iter2, 0)
if node_a and node_b:
sort = cmp(node_a, node_b)
else:
sort = -1
return sort
treemodel.set_sort_func(1, _sort_func)
tv.set_model(treemodel)
def on_child_toggled(treemodel2, path, iter, param=None):
""" Expand row """
if not tv.row_expanded(path):
tv.expand_row(path, True)
treemodel.connect('row-has-child-toggled', on_child_toggled)
tv.set_search_column(1)
tv.set_property("enable-tree-lines", False)
tv.set_rules_hint(False)
#### Drag and drop stuff
dnd_internal_target = ''
dnd_external_targets = {}
def on_drag_fail(widget, dc, result):
print "Failed dragging", widget, dc, result
def __init_dnd():
""" Initialize Drag'n'Drop support
Firstly build list of DND targets:
* name
* scope - just the same widget / same application
* id
Enable DND by calling enable_model_drag_dest(),
enable_model-drag_source()
It didnt use support from gtk.Widget(drag_source_set(),
drag_dest_set()). To know difference, look in PyGTK FAQ:
http://faq.pygtk.org/index.py?file=faq13.033.htp&req=show
"""
#defer_select = False
if dnd_internal_target == '':
error = 'Cannot initialize DND without a valid name\n'
error += 'Use set_dnd_name() first'
raise Exception(error)
dnd_targets = [(dnd_internal_target, gtk.TARGET_SAME_WIDGET, 0)]
for target in dnd_external_targets:
name = dnd_external_targets[target][0]
dnd_targets.append((name, gtk.TARGET_SAME_APP, target))
tv.enable_model_drag_source( gtk.gdk.BUTTON1_MASK,
dnd_targets, gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_MOVE)
tv.enable_model_drag_dest(\
dnd_targets, gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_MOVE)
def on_drag_data_get(treeview, context, selection, info, timestamp):
""" Extract data from the source of the DnD operation.
Serialize iterators of selected tasks in format
<iter>,<iter>,...,<iter> and set it as parameter of DND """
print "on_drag_data_get(", treeview, context, selection, info, timestamp
treeselection = treeview.get_selection()
model, paths = treeselection.get_selected_rows()
iters = [model.get_iter(path) for path in paths]
iter_str = ','.join([model.get_string_from_iter(iter) for iter in iters])
selection.set(dnd_internal_target, 0, iter_str)
print "Sending", iter_str
def on_drag_data_received(treeview, context, x, y, selection, info,\
timestamp):
""" Handle a drop situation.
First of all, we need to get id of node which should accept
all draged nodes as their new children. If there is no node,
drop to root node.
Deserialize iterators of dragged nodes (see self.on_drag_data_get())
Info parameter determines which target was used:
* info == 0 => internal DND within this TreeView
* info > 0 => external DND
In case of internal DND we just use Tree.move_node().
In case of external DND we call function associated with that DND
set by self.set_dnd_external()
"""
print "on_drag_data_received", treeview, context, x, y, selection, info, timestamp
model = treeview.get_model()
destination_iter = None
destination_tid = None
drop_info = treeview.get_dest_row_at_pos(x, y)
if drop_info:
path, position = drop_info
destination_iter = model.get_iter(path)
if destination_iter:
destination_tid = model.get_value(destination_iter, 0)
# Get dragged iter as a TaskTreeModel iter
# If there is no selected task (empty selection.data),
# explictly skip handling it (set to empty list)
if selection.data == '':
iters = []
else:
iters = selection.data.split(',')
dragged_iters = []
for iter in iters:
print "Info", info
if info == 0:
try:
dragged_iters.append(model.get_iter_from_string(iter))
except ValueError:
#I hate to silently fail but we have no choice.
#It means that the iter is not good.
#Thanks shitty gtk API for not allowing us to test the string
print "Shitty iter", iter
dragged_iter = None
elif info in dnd_external_targets and destination_tid:
f = dnd_external_targets[info][1]
src_model = context.get_source_widget().get_model()
dragged_iters.append(src_model.get_iter_from_string(iter))
for dragged_iter in dragged_iters:
if info == 0:
if dragged_iter and model.iter_is_valid(dragged_iter):
dragged_tid = model.get_value(dragged_iter, 0)
try:
row = []
for i in range(model.get_n_columns()):
row.append(model.get_value(dragged_iter, i))
#tree.move_node(dragged_tid, new_parent_id=destination_tid)
print "move_after(%s, %s) ~ (%s, %s)" % (dragged_iter, destination_iter, dragged_tid, destination_tid)
#model.move_after(dragged_iter, destination_iter)
model.insert(destination_iter, -1, row)
model.remove(dragged_iter)
except Exception, e:
print 'Problem with dragging: %s' % e
elif info in dnd_external_targets and destination_tid:
source = src_model.get_value(dragged_iter,0)
# Handle external Drag'n'Drop
f(source, destination_tid)
dnd_internal_target = 'gtg/task-iter-str'
__init_dnd()
tv.connect('drag_data_get', on_drag_data_get)
tv.connect('drag_data_received', on_drag_data_received)
tv.connect('drag_failed', on_drag_fail)
window.add(tv)
window.show_all()
tv.expand_all()
gtk.main()
# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
Я поместил этот script в PyGObject (GTK3). Мой код:
#!/usr/bin/python
# -*- coding: utf-8 -*-
from gi.repository import Gtk, Gdk
window = Gtk.Window()
window.set_size_request(300, 200)
window.connect('delete_event', lambda w,e: Gtk.main_quit())
# Define Liblarch Tree
store = Gtk.TreeStore(str, str)
store.insert(None, -1, ["A", "Task A"])
store.insert(None, -1, ["B", "Task B"])
store.insert(None, -1, ["C", "Task C"])
d_parent = store.insert(None, -1, ["D", "Task D"])
store.insert(d_parent, -1, ["E", "Task E"])
# Define TreeView in similar way as it happens in GTG/Liblarch_gtk
tv = Gtk.TreeView()
col = Gtk.TreeViewColumn()
col.set_title("Title")
render_text = Gtk.CellRendererText()
col.pack_start(render_text, expand=True)
col.add_attribute(render_text, 'markup', 1)
col.set_resizable(True)
col.set_expand(True)
col.set_sort_column_id(0)
tv.append_column(col)
tv.set_property("expander-column", col)
treemodel = store
def _sort_func(model, iter1, iter2):
""" Sort two iterators by function which gets node objects.
This is a simple wrapper which prepares node objects and then
call comparing function. In other case return default value -1
"""
node_a = model.get_value(iter1, 0)
node_b = model.get_value(iter2, 0)
if node_a and node_b:
sort = cmp(node_a, node_b)
else:
sort = -1
return sort
treemodel.set_sort_func(1, _sort_func)
tv.set_model(treemodel)
def on_child_toggled(treemodel2, path, iter, param=None):
""" Expand row """
if not tv.row_expanded(path):
tv.expand_row(path, True)
treemodel.connect('row-has-child-toggled', on_child_toggled)
tv.set_search_column(1)
tv.set_property("enable-tree-lines", False)
tv.set_rules_hint(False)
#### Drag and drop stuff
dnd_internal_target = ''
dnd_external_targets = {}
def on_drag_fail(widget, dc, result):
print "Failed dragging", widget, dc, result
def __init_dnd():
""" Initialize Drag'n'Drop support
Firstly build list of DND targets:
* name
* scope - just the same widget / same application
* id
Enable DND by calling enable_model_drag_dest(),
enable_model-drag_source()
It didnt use support from Gtk.Widget(drag_source_set(),
drag_dest_set()). To know difference, look in PyGTK FAQ:
http://faq.pygtk.org/index.py?file=faq13.033.htp&req=show
"""
#defer_select = False
if dnd_internal_target == '':
error = 'Cannot initialize DND without a valid name\n'
error += 'Use set_dnd_name() first'
raise Exception(error)
dnd_targets = [(dnd_internal_target, Gtk.TargetFlags.SAME_WIDGET, 0)]
for target in dnd_external_targets:
name = dnd_external_targets[target][0]
dnd_targets.append((name, Gtk.TARGET_SAME_APP, target))
tv.enable_model_drag_source( Gdk.ModifierType.BUTTON1_MASK,
dnd_targets, Gdk.DragAction.DEFAULT | Gdk.DragAction.MOVE)
tv.enable_model_drag_dest(\
dnd_targets, Gdk.DragAction.DEFAULT | Gdk.DragAction.MOVE)
def on_drag_data_get(treeview, context, selection, info, timestamp):
""" Extract data from the source of the DnD operation.
Serialize iterators of selected tasks in format
<iter>,<iter>,...,<iter> and set it as parameter of DND """
print "on_drag_data_get(", treeview, context, selection, info, timestamp
treeselection = treeview.get_selection()
model, paths = treeselection.get_selected_rows()
iters = [model.get_iter(path) for path in paths]
iter_str = ','.join([model.get_string_from_iter(iter) for iter in iters])
selection.set(dnd_internal_target, 0, iter_str)
print "Sending", iter_str
def on_drag_data_received(treeview, context, x, y, selection, info,\
timestamp):
""" Handle a drop situation.
First of all, we need to get id of node which should accept
all draged nodes as their new children. If there is no node,
drop to root node.
Deserialize iterators of dragged nodes (see self.on_drag_data_get())
Info parameter determines which target was used:
* info == 0 => internal DND within this TreeView
* info > 0 => external DND
In case of internal DND we just use Tree.move_node().
In case of external DND we call function associated with that DND
set by self.set_dnd_external()
"""
print "on_drag_data_received", treeview, context, x, y, selection, info, timestamp
model = treeview.get_model()
destination_iter = None
destination_tid = None
drop_info = treeview.get_dest_row_at_pos(x, y)
if drop_info:
path, position = drop_info
destination_iter = model.get_iter(path)
if destination_iter:
destination_tid = model.get_value(destination_iter, 0)
# Get dragged iter as a TaskTreeModel iter
# If there is no selected task (empty selection.data),
# explictly skip handling it (set to empty list)
if selection.data == '':
iters = []
else:
iters = selection.data.split(',')
dragged_iters = []
for iter in iters:
print "Info", info
if info == 0:
try:
dragged_iters.append(model.get_iter_from_string(iter))
except ValueError:
#I hate to silently fail but we have no choice.
#It means that the iter is not good.
#Thanks shitty Gtk API for not allowing us to test the string
print "Shitty iter", iter
dragged_iter = None
elif info in dnd_external_targets and destination_tid:
f = dnd_external_targets[info][1]
src_model = context.get_source_widget().get_model()
dragged_iters.append(src_model.get_iter_from_string(iter))
for dragged_iter in dragged_iters:
if info == 0:
if dragged_iter and model.iter_is_valid(dragged_iter):
dragged_tid = model.get_value(dragged_iter, 0)
try:
row = []
for i in range(model.get_n_columns()):
row.append(model.get_value(dragged_iter, i))
#tree.move_node(dragged_tid, new_parent_id=destination_tid)
print "move_after(%s, %s) ~ (%s, %s)" % (dragged_iter, destination_iter, dragged_tid, destination_tid)
#model.move_after(dragged_iter, destination_iter)
model.insert(destination_iter, -1, row)
model.remove(dragged_iter)
except Exception, e:
print 'Problem with dragging: %s' % e
elif info in dnd_external_targets and destination_tid:
source = src_model.get_value(dragged_iter,0)
# Handle external Drag'n'Drop
f(source, destination_tid)
dnd_internal_target = 'gtg/task-iter-str'
__init_dnd()
tv.connect('drag_data_get', on_drag_data_get)
tv.connect('drag_data_received', on_drag_data_received)
tv.connect('drag_failed', on_drag_fail)
window.add(tv)
window.show_all()
tv.expand_all()
Gtk.main()
# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
Я не могу правильно обрабатывать dnd_data_receive()
, где либо он не вызван, либо не получены данные. Он всегда терпит неудачу при следующем обратном вызове + его параметрах:
Failed dragging <TreeView object at 0xeb4370 (GtkTreeView at 0xf742a0)> <gtk.gdk.X11DragContext object at 0xf351e0 (GdkX11DragContext at 0xf96ca0)> <enum GTK_DRAG_RESULT_NO_TARGET of type GtkDragResult>
Мой вопрос: Как перенести первый script в PyGObject (GTK3), чтобы можно было сортировать GtkTreeView и в то же время строки можно перетаскивать и перетаскивать? -and-drop для правильной обработки перетаскивания?
Ответы
Ответ 1
Во-первых, ошибка, которую вы получаете, связана с версией PyGObject. Я воспроизвожу похожие данные об ошибках, прежде чем переустанавливать свой ноутбук с последней бета-версией Ubuntu 13.04. Но после обновления, обратный вызов ошибки изменится на что-то вроде
on_drag_data_get( <TreeView object at 0x1765870 (GtkTreeView at 0x19120a0)> <gtk.gdk.X11DragContext object at 0x1765aa0 (GdkX11DragContext at 0x1988820)> <GtkSelectionData at 0x7fffb106b760> 0 21962912
Traceback (most recent call last):
File "dnd_gtk3_org.py", line 116, in on_drag_data_get
selection.set(dnd_internal_target, 0, iter_str)
File "/usr/lib/python2.7/dist-packages/gi/types.py", line 113, in function
return info.invoke(*args, **kwargs)
TypeError: argument type: Expected Gdk.Atom, but got str
on_drag_data_received <TreeView object at 0x1765870 (GtkTreeView at 0x19120a0)> <gtk.gdk.X11DragContext object at 0x1765be0 (GdkX11DragContext at 0x1988940)> 45 77 <GtkSelectionData at 0x7fffb106b6e0> 0 21962912
Traceback (most recent call last):
File "dnd_gtk3_org.py", line 151, in on_drag_data_received
if selection.data == '':
AttributeError: 'SelectionData' object has no attribute 'data'
Есть только две небольшие проблемы:
- первым параметром SelectionData.set() может быть только Gtk.gdk.Atom, но не строка, которая указывает, что как в pygtk.
- SelectionData не имеет атрибутов 'data', но вместо этого имеет метод get_data().
Ниже приведен фрагмент рабочего кода
#!/usr/bin/python
# -*- coding: utf-8 -*-
from gi.repository import Gtk, Gdk
window = Gtk.Window()
window.set_size_request(300, 200)
window.connect('delete_event', Gtk.main_quit)
# Define Liblarch Tree
store = Gtk.TreeStore(str, str)
store.insert(None, -1, ["A", "Task A"])
store.insert(None, -1, ["B", "Task B"])
store.insert(None, -1, ["C", "Task C"])
d_parent = store.insert(None, -1, ["D", "Task D"])
store.insert(d_parent, -1, ["E", "Task E"])
# Define TreeView in similar way as it happens in GTG/Liblarch_gtk
tv = Gtk.TreeView()
col = Gtk.TreeViewColumn()
col.set_title("Title")
render_text = Gtk.CellRendererText()
col.pack_start(render_text, expand=True)
col.add_attribute(render_text, 'markup', 1)
col.set_resizable(True)
col.set_expand(True)
col.set_sort_column_id(0)
tv.append_column(col)
tv.set_property("expander-column", col)
treemodel = store
def _sort_func(model, iter1, iter2):
""" Sort two iterators by function which gets node objects.
This is a simple wrapper which prepares node objects and then
call comparing function. In other case return default value -1
"""
node_a = model.get_value(iter1, 0)
node_b = model.get_value(iter2, 0)
if node_a and node_b:
sort = cmp(node_a, node_b)
else:
sort = -1
return sort
treemodel.set_sort_func(1, _sort_func)
tv.set_model(treemodel)
def on_child_toggled(treemodel2, path, iter, param=None):
""" Expand row """
if not tv.row_expanded(path):
tv.expand_row(path, True)
treemodel.connect('row-has-child-toggled', on_child_toggled)
tv.set_search_column(1)
tv.set_property("enable-tree-lines", False)
tv.set_rules_hint(False)
#### Drag and drop stuff
dnd_internal_target = ''
dnd_external_targets = {}
def on_drag_fail(widget, dc, result):
print "Failed dragging", widget, dc, result
def __init_dnd():
""" Initialize Drag'n'Drop support
Firstly build list of DND targets:
* name
* scope - just the same widget / same application
* id
Enable DND by calling enable_model_drag_dest(),
enable_model-drag_source()
It didnt use support from Gtk.Widget(drag_source_set(),
drag_dest_set()). To know difference, look in PyGTK FAQ:
http://faq.pygtk.org/index.py?file=faq13.033.htp&req=show
"""
#defer_select = False
if dnd_internal_target == '':
error = 'Cannot initialize DND without a valid name\n'
error += 'Use set_dnd_name() first'
raise Exception(error)
dnd_targets = [(dnd_internal_target, Gtk.TargetFlags.SAME_WIDGET, 0)]
for target in dnd_external_targets:
name = dnd_external_targets[target][0]
dnd_targets.append((name, Gtk.TARGET_SAME_APP, target))
tv.enable_model_drag_source( Gdk.ModifierType.BUTTON1_MASK,
dnd_targets, Gdk.DragAction.DEFAULT | Gdk.DragAction.MOVE)
tv.enable_model_drag_dest(\
dnd_targets, Gdk.DragAction.DEFAULT | Gdk.DragAction.MOVE)
def on_drag_data_get(treeview, context, selection, info, timestamp):
""" Extract data from the source of the DnD operation.
Serialize iterators of selected tasks in format
<iter>,<iter>,...,<iter> and set it as parameter of DND """
print "on_drag_data_get(", treeview, context, selection, info, timestamp
treeselection = treeview.get_selection()
model, paths = treeselection.get_selected_rows()
iters = [model.get_iter(path) for path in paths]
iter_str = ','.join([model.get_string_from_iter(iter) for iter in iters])
selection.set(selection.get_target(), 0, iter_str)
print "Sending", iter_str
def on_drag_data_received(treeview, context, x, y, selection, info,\
timestamp):
""" Handle a drop situation.
First of all, we need to get id of node which should accept
all draged nodes as their new children. If there is no node,
drop to root node.
Deserialize iterators of dragged nodes (see self.on_drag_data_get())
Info parameter determines which target was used:
* info == 0 => internal DND within this TreeView
* info > 0 => external DND
In case of internal DND we just use Tree.move_node().
In case of external DND we call function associated with that DND
set by self.set_dnd_external()
"""
print "on_drag_data_received", treeview, context, x, y, selection, info, timestamp
model = treeview.get_model()
destination_iter = None
destination_tid = None
drop_info = treeview.get_dest_row_at_pos(x, y)
if drop_info:
path, position = drop_info
destination_iter = model.get_iter(path)
if destination_iter:
destination_tid = model.get_value(destination_iter, 0)
# Get dragged iter as a TaskTreeModel iter
# If there is no selected task (empty selection.data),
# explictly skip handling it (set to empty list)
data = selection.get_data()
if data == '':
iters = []
else:
iters = data.split(',')
dragged_iters = []
for iter in iters:
print "Info", info
if info == 0:
try:
dragged_iters.append(model.get_iter_from_string(iter))
except ValueError:
#I hate to silently fail but we have no choice.
#It means that the iter is not good.
#Thanks shitty Gtk API for not allowing us to test the string
print "Shitty iter", iter
dragged_iter = None
elif info in dnd_external_targets and destination_tid:
f = dnd_external_targets[info][1]
src_model = context.get_source_widget().get_model()
dragged_iters.append(src_model.get_iter_from_string(iter))
for dragged_iter in dragged_iters:
if info == 0:
if dragged_iter and model.iter_is_valid(dragged_iter):
dragged_tid = model.get_value(dragged_iter, 0)
try:
row = []
for i in range(model.get_n_columns()):
row.append(model.get_value(dragged_iter, i))
#tree.move_node(dragged_tid, new_parent_id=destination_tid)
print "move_after(%s, %s) ~ (%s, %s)" % (dragged_iter, destination_iter, dragged_tid, destination_tid)
#model.move_after(dragged_iter, destination_iter)
model.insert(destination_iter, -1, row)
model.remove(dragged_iter)
except Exception, e:
print 'Problem with dragging: %s' % e
elif info in dnd_external_targets and destination_tid:
source = src_model.get_value(dragged_iter,0)
# Handle external Drag'n'Drop
f(source, destination_tid)
dnd_internal_target = 'gtg/task-iter-str'
__init_dnd()
tv.connect('drag_data_get', on_drag_data_get)
tv.connect('drag_data_received', on_drag_data_received)
tv.connect('drag_failed', on_drag_fail)
window.add(tv)
window.show_all()
tv.expand_all()
Gtk.main()
# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
Разница между приведенным выше фрагментом с одним в вашем вопросе
116c116
< selection.set(selection.get_target(), 0, iter_str)
---
> selection.set(dnd_internal_target, 0, iter_str)
151,152c151
< data = selection.get_data()
< if data == '':
---
> if selection.data == '':
155c154
< iters = data.split(',')
---
> iters = selection.data.split(',')
Кроме того, есть еще один пример для GTK + 3 версии Drag and Drop TreeView в другом потоке: невосприимчивость к перетаскиванию в pygobject
Ответ 2
GTG - отличное программное обеспечение! Но это слишком медленно, по крайней мере, на моем компьютере. Таким образом, я писал С++-библиотеку, которая отображает ориентированный ациклический граф с использованием Gtk:: TreeView, и я много смотрел на исходный код LibLarch.
Насколько я знаю, привязки Python и С++ для GTK имеют одинаковое ограничение, исходящее из GTK (однажды я посмотрел исходный код GTK, чтобы точно определить, почему он работает): Если вы перейдете к перетаскиванию, drop и sorting, drag-and-drop не будет работать. Я предлагаю три вещи, которые вы можете сделать по этому поводу:
-
Сделайте патч для GTK, который ограничивает dnd, когда сортировка включена, а не полностью блокирует его
-
Внедрить сортировку самостоятельно. Это легко: начните с загрузки данных в отсортированное дерево. Теперь, каждый раз, когда пользователь тащит и катит, переместите перетаскиваемую строку в новую позицию, используя вашу функцию сортировки. Но оставляйте GTK сортировкой.
-
Это можно сделать в дополнение к 2, это проблема с дизайном графического интерфейса: в GtkTreeView вы можете вставить элемент между элементами-братьями, что не имеет особого смысла в отсортированных деревьях. С точки зрения пользовательского интерфейса лучше разрешить сбросить только строки ON, а не BETWEEN их. Пример: Nautilus list-view работает следующим образом. Решение либо переопределяет обработчик TreeView drag_data_received() по умолчанию, либо лучше, чем с точки зрения ремонтопригодности: отправьте вашей модели подсказку из представления, указывающего модели, включена ли позиция падения или ПЕРЕД. Если позиция BEFORE, сделайте ваше дерево drop_possible() виртуальным переопределением return false, а затем вы не увидите древовидную структуру "вы можете отказаться отсюда" didplay ", таким образом вы получите более чистый GUI.
2 и 3 - это то, что я делаю на С++, вы можете легко сделать это на Python:)
Кроме того, примечание относительно варианта 1: GtktreeView (или был GtkTreeStore? Я забыл) просто блокирует любое падение, если сортировка включена. Если кто-то просто исправляет это (вы... или меня...) или, по крайней мере, записывает производный класс представления, у нас будет чистый графический интерфейс по умолчанию для отсортированных деревьев с поддержкой dnd.