How to have drag-and-drop and sorted GtkTreeView in GTK3?

和自甴很熟 提交于 2020-01-12 06:53:27

问题


I am porting liblarch, a library for handling directed acyclic graphs, from PyGTK (GTK2) to PyGObject introspection (GTK3). I ran into the problem with GtkTreeView.

The app using liblarch needs to sort GtkTreeView by a column but in the same time, the user can drag-and-drop rows, move a row under another row. For that I had to manually process dnd_data_get() and dnd_data_receive() which is perfectly okay.

There is the minimal setup for GtkTreeView which works under PyGTK. Rows are sorted and the user can move rows around.

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

I ported this script into PyGObject (GTK3). My code:

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

I am not able to handle dnd_data_receive() correctly where either it is not evoked or no data are received. It always fails with the following callback + its parameters:

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>

My question: How to port the first script to PyGObject (GTK3) so GtkTreeView can be sorted and in the same time rows can be drag-and-dropped? How to change handling drag-and-drop callbacks to process drag-and-drop correctly?


回答1:


Firstly, the error you get seems related to version of PyGObject. I do reproduce similar error info before reinstall my laptop with latest Ubuntu 13.04 beta. But after the upgrading, the error callback changes to something like

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'

There are only two little problems:

  • the first parameter of SelectionData.set() seems only can be Gtk.gdk.Atom but not a string that specifies that as in pygtk.
  • SelectionData has no data attributes but has a get_data() method instead.

A working code snippet listed below

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

The diff between the snippet above with the one in your question is

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

Besides, there is another example for GTK+3 version Drag and Drop of TreeView in another thread: unresponsive drag and drop in pygobject




回答2:


GTG is a great piece of software! But it's far too slow, at least on my computer. So I've been writing a C++ library which displays a directed acyclic graph using a Gtk::TreeView, and I looked at the LibLarch source code a lot.

As far as I know, Python and C++ bindings of GTK share the same limitation, coming from GTK itself (I once looked at GTK source code to find exactly why it works like that): If you turn of drag-and-drop and sorting, drag-and-drop won't work. I offer three things you can do about it:

  1. Make a patch to GTK which limits dnd when sorting is enabled, instead of completely blocking it

  2. Implement sorting by yourself. It easy: start by loading your data to a sorted treeview. Now, every time the user drags and drops, move the dragged row to the new position using your sorting function. But leave GTK sorting off.

  3. This can be done in addition to 2, it's a GUI design issue: In GtkTreeView you can insert an item between to sibling items, which doesn't make much sense in sorted trees. In terms of UI it's better to allow dropping only ON rows, not BETWEEN them. Example: Nautilus list-view works like this. The solution is either override the TreeView drag_data_received() default handler, or better than that in terms of maintainability: send your model a hint from the view telling the model whether the drop position is ON or BEFORE. If the position is BEFORE, make your tree's drop_possible() virtual override return false, and then you don't see the treeview's "you can drop here" didplay", thus you get a cleaner GUI.

2 and 3 is what I do in C++, you should be able to do that in Python easily :)

Also, a note regarding option 1: GtktreeView (or was is GtkTreeStore? I forgot) simply blocks any drop if sorting is enabled. If someone just fixes that (you... or me...), or at least writes a derived view class, we'll have a default clean GUI for sorted trees with dnd support.



来源:https://stackoverflow.com/questions/13882076/how-to-have-drag-and-drop-and-sorted-gtktreeview-in-gtk3

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!