15

我正在将用于处理有向无环图的库liblarch从 PyGTK (GTK2) 移植到 PyGObject 内省 (GTK3)。我遇到了 GtkTreeView 的问题。

使用 liblarch 的应用程序需要按列对 GtkTreeView 进行排序,但同时,用户可以拖放行,将一行移动到另一行下。为此,我必须手动处理dnd_data_get()dnd_data_receive()这完全可以。

在 PyGTK 下工作的 GtkTreeView 有最小的设置。行已排序,用户可以移动行。

#!/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

我将此脚本移植到 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>

我的问题:如何将第一个脚本移植到 PyGObject (GTK3) 以便可以对 GtkTreeView 进行排序并同时拖放行?如何更改处理拖放回调以正确处理拖放?

4

2 回答 2

5

首先,您得到的错误似乎与 PyGObject 的版本有关。在使用最新的 Ubuntu 13.04 beta 重新安装我的笔记本电脑之前,我确实重现了类似的错误信息。但升级后,错误回调变为类似

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(',')

TreeView另外,另一个线程中的GTK+3版本的拖放还有一个例子: unresponsive drag and drop in pygobject

于 2013-04-16T08:01:57.347 回答
1

GTG 是一款很棒的软件!但它太慢了,至少在我的电脑上。所以我一直在编写一个 C++ 库,它使用 Gtk::TreeView 显示有向无环图,并且我看了很多 LibLarch 源代码。

据我所知,GTK 的 Python 和 C++ 绑定具有相同的限制,来自 GTK 本身(我曾经查看 GTK 源代码以找到它的确切原因):如果您转向拖放和排序, 拖放将不起作用。我提供了三件你可以做的事情:

  1. 为 GTK 打一个补丁,在启用排序时限制 dnd,而不是完全阻止它

  2. 自己实现排序。很简单:首先将数据加载到排序的树视图中。现在,每次用户拖放时,使用排序功能将拖动的行移动到新位置。但是让 GTK 整理一下。

  3. 这可以在 2 之外完成,这是一个 GUI 设计问题:在 GtkTreeView 中,您可以在兄弟项目之间插入一个项目,这在排序树中没有多大意义。在 UI 方面,最好只允许删除 ON 行,而不是 BETWEEN。示例:Nautilus 列表视图的工作方式如下。解决方案是覆盖 TreeView drag_data_received() 默认处理程序,或者在可维护性方面更好:从视图中向模型发送提示,告诉模型放置位置是 ON 还是 BEFORE。如果位置是之前,让你的树的 drop_possible() 虚拟覆盖返回 false,然后你看不到树视图的“你可以放在这里”didplay”,因此你得到一个更干净的 GUI。

2 和 3 是我在 C++ 中所做的,你应该能够轻松地在 Python 中做到这一点:)

此外,关于选项 1 的注释:GtktreeView(或者是 GtkTreeStore?我忘了)如果启用排序,则只会阻止任何丢弃。如果有人只是修复了这个问题(你......或我......),或者至少编写了一个派生视图类,我们将拥有一个默认的干净 GUI,用于支持 dnd 的排序树。

于 2013-02-24T09:20:31.937 回答