我移植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可以进行排序,并在同一时间行可以拖和下降? 如何改变处理拖动和拖放回调正确处理拖和下降?