簡體   English   中英

如何在GTK3中拖放和排序GtkTreeView?

[英]How to have drag-and-drop and sorted GtkTreeView in 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

我將此腳本移植到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進行排序,同時可以拖放行? 如何更改處理拖放回調以正確處理拖放?

首先,你得到的錯誤似乎與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版本的TreeView拖放示例: pygobject中無響應的拖放

GTG是一款非常棒的軟件! 但它太慢了,至少在我的電腦上。 所以我一直在編寫一個C ++庫,它使用Gtk :: TreeView顯示有向無環圖,我查看了很多LibLarch源代碼。

據我所知,GTK的Python和C ++綁定有相同的限制,來自GTK本身(我曾經看過GTK源代碼,找到它的工作原理):如果你轉向拖放和排序,拖放將無法正常工作。 我提供了三件你可以做的事情:

  1. 為GTK做一個補丁,在啟用排序時限制dnd,而不是完全阻止它

  2. 自己實施排序。 很簡單:首先將數據加載到排序的樹視圖中。 現在,每次用戶拖放時,使用排序功能將拖動的行移動到新位置。 但是讓GTK排除。

  3. 這可以在2之外完成,這是一個GUI設計問題:在GtkTreeView中,您可以在兄弟項目之間插入一個項目,這在排序樹中沒有多大意義。 在UI方面,最好只允許刪除ON行,而不是BETWEEN它們。 示例:Nautilus list-view的工作原理如下。 解決方案是覆蓋TreeView drag_data_received()默認處理程序,或者比可維護性更好:從模型中向模型發送提示,告訴模型放置位置是ON還是BEFORE。 如果位置是BEFORE,讓你的樹的drop_possible()虛擬覆蓋返回false,然后你沒有看到樹視圖的“你可以放在這里”didplay“,因此你得到一個更清晰的GUI。

2和3是我在C ++中所做的,你應該能夠在Python中輕松做到:)

另外,關於選項1的注釋:GtktreeView(或者是GtkTreeStore?我忘了)只是在啟用排序時阻止任何丟棄。 如果有人只修復了(你......或者我......),或者至少寫了一個派生的視圖類,我們將為dnd支持的排序樹提供一個默認的干凈GUI。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM