本文介绍了如何在GTK3中进行拖放和排序GtkTreeView?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧! 问题描述 29岁程序员,3月因学历无情被辞! 我从PyGTK移植 liblarch ,一个用于处理有向无环图的库。 (GTK2)转换为PyGObject内省(GTK3)。我遇到了GtkTreeView的问题。 使用liblarch的应用程序需要按列排序GtkTreeView,但同时用户可以拖放行,在另一行下移动一行。为此,我必须手动处理 dnd_data_get()和 dnd_data_receive(),这是完全可以的。 b $ b 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()) #定义Liblarch树 store = gtk.TreeStore(str,str) store.insert(None,-1,[A,Task A]) store.insert(None,-1,[B,Task B]) store.insert(无,-1,[C,任务C]) d_parent = store.insert(无,-1,[D,任务D]) store.insert(d_parent,-1,[E,Task E]) #以类似GTG / Liblarch_gtk tv = gtk的方式定义TreeView。 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):通过获取节点对象的函数对两个迭代器排序。 这是一个简单的包装器,用于准备节点对象,然后调用比较函数。在其他情况下返回默认值-1 node_a = model.get_value(iter1,0) node_b = model.get_value(iter2,0)如果node_a和node_b: sort = cmp(node_a,node_b) else: sort = -1 返回排序 treemodel.set_sort_func(1,_sort_func) tv.set_model(treemodel) def on_child_toggled(treemodel2,path,iter,param = None):展开行如果不是tv.row_expanded(路径): 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) ### #拖放的东西 dnd_internal_target ='' dnd_external_targets = {} def on_drag_fail(widget,dc,result): printFailed拖动,widget,dc,结果 def __init_dnd():初始化Drag'n'Drop sup港口 首先建立DND目标列表: *名称 *范围 - 只是相同的窗口小部件/相同的应用程序 * id 通过调用enable_model_drag_dest(), enable_model-drag_source() $ b $来启用DND它没有使用gtk.Widget(drag_source_set(), drag_dest_set())的支持。要知道差异,请查看PyGTK FAQ: http://faq.pygtk.org/index.py?file=faq13.033.htp&req=show # defer_select = False $ b $如果dnd_internal_target =='': error ='无法初始化DND,但不包含有效名称\' error + ='使用set_dnd_name()first' 提升异常(错误) dnd_targets = [(dnd_internal_target,gtk.TARGET_SAME_WIDGET,0)] 用于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):从源代码中提取数据的DnD操作。 以格式 ,< iter>,...,< iter>格式序列化所选任务的迭代器。并将其设置为DND的参数 printon_drag_data_get(,treeview,context,selection,info,timestamp treeselection = treeview.get_selection() model, paths = treeselection.get_selected_rows() iters = [模型.get_iter(路径)用于路径] iter_str =','。join([iter] for iter in iters]) selection.set(dnd_internal_target,0,iter_str) printSending,iter_str def on_drag_data_received(treeview,context,x,y,selection,info,\ timestamp):处理放置情况 首先,我们需要获得节点的id,它应该接受所有被拖动的节点作为它们的新节点如果没有节点,则放到根节点 反序列化拖拽节点的迭代器(请参阅self.on_drag_data_get()) Info参数确定使用哪个目标: * info == 0 =>内部的DND这个TreeView * info> 0 =>外部免打扰 如果是内部免打扰,我们只需使用Tree.move_node()。 在外部DND的情况下,我们调用与由self.set_dnd_external()设置的DND 相关联的函数。 printon_drag_data_received,treeview,context,x,y ,选择,信息,时间戳 模型= treeview.get_model() destination_iter =无 destination_tid =无 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) #将它拖动为TaskTreeModel iter #如果没有选定的任务(空selection.data),#显式跳过处理它(设置为空列表)如果selection.data =='': iters = [] else: iters = selection.data.split(',') dragged_iters = [] iter iters: printIn fo,info if info == 0: try: dragged_iters.append(model.get_iter_from_string(iter))除了ValueError:#我讨厌默默地失败,但我们别无选择。 #这意味着iter不好。 #谢谢shitty gtk API不允许我们测试字符串 printShitty iter,iter dragged_iter = None elif信息在dnd_external_targets和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)) 为dragged_iters中的dragged_iter: if info == 0:如果dragged_iter和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) printmove_after(%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'拖动时出现问题:%s'%e elnd信息在dnd_external_targets和destination_tid中: source = src_model.get_value(dragged_iter,0)#处理外部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 shiftwidt h = 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)窗口。 connect('delete_event',lambda w,e:Gtk.main_quit()) #定义Liblarch树 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]) #定义TreeView的方式与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_co lumn(col) tv.set_property(expander-column,col) treemodel = store $ b $ def _sort_func(model,iter1,iter2):按获取节点对象的函数对两个迭代器排序。 这是一个简单的包装器,用于准备节点对象,然后调用比较函数。在其他情况下返回默认值-1 node_a = model.get_value(iter1,0) node_b = model.get_value(iter2,0)如果node_a和node_b: sort = cmp(node_a,node_b) else: sort = -1 返回排序 treemodel.set_sort_func(1,_sort_func) tv.set_model(treemodel) def on_child_toggled(treemodel2,path,iter,param = None):展开行如果不是tv.row_expanded(路径): 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) ### #拖放的东西 dnd_internal_target ='' dnd_external_targets = {} def on_drag_fail(widget,dc,result): printFailed拖动,widget,dc,结果 def __init_dnd():初始化Drag'n'Drop sup港口 首先建立DND目标列表: *名称 *范围 - 只是相同的窗口小部件/相同的应用程序 * id 通过调用enable_model_drag_dest(), enable_model-drag_source() $ b $来启用DND它没有使用Gtk.Widget(drag_source_set(), drag_dest_set())的支持。要知道差异,请查看PyGTK FAQ: http://faq.pygtk.org/index.py?file=faq13.033.htp&req=show # defer_select = False $ b $如果dnd_internal_target =='': error ='无法初始化DND,但不包含有效名称\' error + ='使用set_dnd_name()first' 举例异常(错误) dnd_targets = [(dnd_internal_target,Gtk.Target.SAME_WIDGET,0)] 用于dnd_external_targets中的目标: name = dnd_external_targets [target ] [b] 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):提取数据f从DnD操作的来源开始。 以格式 ,< iter>,...,< iter>格式序列化所选任务的迭代器。并将其设置为DND的参数 printon_drag_data_get(,treeview,context,selection,info,timestamp treeselection = treeview.get_selection() model, paths = treeselection.get_selected_rows() iters = [模型.get_iter(路径)用于路径] iter_str =','。join([iter] for iter in iters]) selection.set(dnd_internal_target,0,iter_str) printSending,iter_str def on_drag_data_received(treeview,context,x,y,selection,info,\ timestamp):处理放置情况 首先,我们需要获得节点的id,它应该接受所有被拖动的节点作为它们的新节点如果没有节点,则放到根节点 反序列化拖拽节点的迭代器(请参阅self.on_drag_data_get()) Info参数确定使用哪个目标: * info == 0 =>内部的DND这个TreeView * info> 0 =>外部免打扰 如果是内部免打扰,我们只需使用Tree.move_node()。 在外部DND的情况下,我们调用与由self.set_dnd_external()设置的DND 相关联的函数。 printon_drag_data_received,treeview,context,x,y ,选择,信息,时间戳 模型= treeview.get_model() destination_iter =无 destination_tid =无 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) #将它拖动为TaskTreeModel iter #如果没有选定的任务(空selection.data),#显式跳过处理它(设置为空列表)如果selection.data =='': iters = [] else: iters = selection.data.split(',') dragged_iters = [] iter iters: printIn fo,info if info == 0: try: dragged_iters.append(model.get_iter_from_string(iter))除了ValueError:#我讨厌默默地失败,但我们别无选择。 #这意味着iter不好。 #由于不允许我们测试字符串 printShitty iter,iter dragged_iter = None elif信息在dnd_external_targets和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)) 为dragged_iters中的dragged_iter: if info == 0:如果dragged_iter和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) printmove_after(%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'拖动时出现问题:%s'%e elnd信息在dnd_external_targets和destination_tid中: source = src_model.get_value(dragged_iter,0)#处理外部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 shiftwidt h = 4 softtabstop = 4 我无法处理 dnd_data_receive() 正确的地方不是它被引起或没有收到数据。它总是以下面的回调+它的参数失败: 拖动失败< TreeView对象在0xeb4370(GtkTreeView at 0xf742a0)> < gtk.gdk.X11DragContext object at 0xf351e0(GdkX11DragContext at 0xf96ca0)> <类型GtkDragResult的枚举GTK_DRAG_RESULT_NO_TARGET> 我的问题:如何将第一个脚本移植到PyGObject(GTK3)中,使GtkTreeView可以排序并在同一时间行可以拖放?如何更改处理拖放回调来正确处理拖放?解决方案首先,你得到的错误似乎与PyGObject的版本有关。在重新安装我的笔记本电脑之前,我使用最新的Ubuntu 13.04测试版重新制作了类似的错误信息。但是在升级之后,错误回调会变成像 on_drag_data_get(< TreeView对象在0x1765870(GtkTreeView在0x19120a0)> ;< gtk.gdk.X11DragContext object at 0x1765aa0(GdkX11DragContext at 0x1988820)>< GtkSelectionData at 0x7fffb106b760> 0 21962912 Traceback(最近一次调用最后一次):文件dnd_gtk3_org.py,line 116,在on_drag_data_get selection.set(dnd_internal_target,0,iter_str)文件/usr/lib/python2.7/dist-packages/gi/types.py,行113,在函数返回info.invoke(* args,** kwargs) TypeError:参数类型:期望的Gdk.Atom,但得到str on_drag_data_received< TreeView对象在0x1765870(GtkTreeView at 0x19120a0)> < gtk.gdk.X11DragContext object at 0x1765be0(GdkX11DragContext at 0x1988940)> 45 77< GtkSelectionData at 0x7fffb106b6e0> 0 21962912 Traceback(最近一次调用最后一次):文件dnd_gtk3_org.py,第151行,在on_drag_data_rece如果selection.data =='',则为: AttributeError:'SelectionData'对象没有属性'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) #定义Liblarch Tree store = store.insert(None,-1,[A,Task A]) store.insert(None,-1,[B ,任务B]) store.insert(None,-1,[C,Task C]) d_parent = store.insert(None,-1, [D,任务D]) store.insert(d_parent,-1,[E,任务E]) #以类似的方式定义TreeView它发生在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 $ b $ def _sort_func(model,iter1,iter2):按函数对两个迭代器排序,得到节点对象。 这是一个简单的包装器,用于准备节点对象,然后调用比较函数。在其他情况下返回默认值-1 node_a = model.get_value(iter1,0) node_b = model.get_value(iter2,0)如果node_a和node_b: sort = cmp(node_a,node_b) else: sort = -1 返回排序 treemodel.set_sort_func(1,_sort_func) tv.set_model(treemodel) def on_child_toggled(treemodel2,path,iter,param = None):展开行如果不是tv.row_expanded(路径): 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) ### #拖放的东西 dnd_internal_target ='' dnd_external_targets = {} def on_drag_fail(widget,dc,result): printFailed拖动,widget,dc,结果 def __init_dnd():初始化Drag'n'Drop sup港口 首先建立DND目标列表: *名称 *范围 - 只是相同的窗口小部件/相同的应用程序 * id 通过调用enable_model_drag_dest(), enable_model-drag_source() $ b $来启用DND它没有使用Gtk.Widget(drag_source_set(), drag_dest_set())的支持。要知道差异,请查看PyGTK FAQ: http://faq.pygtk.org/index.py?file=faq13.033.htp&req=show # defer_select = False $ b $如果dnd_internal_target =='': error ='无法初始化DND,但不包含有效名称\' error + ='使用set_dnd_name()first' 举例异常(错误) dnd_targets = [(dnd_internal_target,Gtk.Target.SAME_WIDGET,0)] 用于dnd_external_targets中的目标: name = dnd_external_targets [target ] [b] 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):提取数据f从DnD操作的来源开始。 以格式 ,< iter>,...,< iter>格式序列化所选任务的迭代器。并将其设置为DND的参数 printon_drag_data_get(,treeview,context,selection,info,timestamp treeselection = treeview.get_selection() model, paths = treeselection.get_selected_rows() iters = [模型.get_iter(路径)用于路径] iter_str =','。join([iter] for iter in iters]) selection.set(selection.get_target(),0,iter_str) printSending,iter_str def on_drag_data_received(treeview,context,x,y,selection,info) ,\ timestamp):处理放置情况 首先,我们需要获得节点的id,它应该接受全部拖动节点作为它们的新子节点,如果没有节点,将放到根节点 反序列化拖拽节点的迭代器(参见self.on_drag_data_get()) Info参数确定哪个节点目标被使用: * info == 0 =>内部免打扰在这个TreeView * info> 0 =>外部免打扰 如果是内部免打扰,我们只需使用Tree.move_node()。 在外部DND的情况下,我们调用与由self.set_dnd_external()设置的DND 相关联的函数。 printon_drag_data_received,treeview,context,x,y ,选择,信息,时间戳 模型= treeview.get_model() destination_iter =无 destination_tid =无 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) #将它拖动为TaskTreeModel iter #如果没有选定的任务(空selection.data),#显式跳过处理它(设置为空列表) data = selection.get_data() if data =='': iters = [] else: iters = data.split(',') dragged_iters = [] 用于iter:打印Info,info if info == 0: try: dragged_iters.append(model.get_iter_from_string(iter))除ValueError: #I讨厌默默地失败,但我们别无选择。 #这意味着iter不好。 #由于不允许我们测试字符串 printShitty iter,iter dragged_iter = None elif信息在dnd_external_targets和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)) 为dragged_iters中的dragged_iter: if info == 0:如果dragged_iter和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) printmove_after(%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'拖动时出现问题:%s'%e elnd信息在dnd_external_targets和destination_tid中: source = src_model.get_value(dragged_iter,0)#处理外部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 shiftwidt h = 4 softtabstop = 4 上面的代码片段与问题中的代码段之间的差异是 116c116 --- > selection.set(dnd_internal_target,0,iter_str) 151,152c151 < data = selection.get_data() --- >如果selection.data =='': 155c154 < iters = data.split(',') --- > iters = selection.data.split(',') 另外,GTK + 3版本在另一个线程中拖放TreeView: pygobject中无响应的拖放 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 gtkwindow = gtk.Window()window.set_size_request(300, 200)window.connect('delete_event', lambda w,e: gtk.main_quit())# Define Liblarch Treestore = 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_gtktv = 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 = storedef _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 sorttreemodel.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 stuffdnd_internal_target = ''dnd_external_targets = {}def on_drag_fail(widget, dc, result): print "Failed dragging", widget, dc, resultdef __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_strdef 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=4I ported this script into PyGObject (GTK3). My code:#!/usr/bin/python# -*- coding: utf-8 -*-from gi.repository import Gtk, Gdkwindow = Gtk.Window()window.set_size_request(300, 200)window.connect('delete_event', lambda w,e: Gtk.main_quit())# Define Liblarch Treestore = 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_gtktv = 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 = storedef _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 sorttreemodel.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 stuffdnd_internal_target = ''dnd_external_targets = {}def on_drag_fail(widget, dc, result): print "Failed dragging", widget, dc, resultdef __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_strdef 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=4I 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? 解决方案 Firstly, the error you get seems related to version of PyGObject. I do reproduce similiar error info before reinstall my laptop with latest Ubuntu 13.04 beta. But after the upgrading, the error callback changes to something likeon_drag_data_get( <TreeView object at 0x1765870 (GtkTreeView at 0x19120a0)> <gtk.gdk.X11DragContext object at 0x1765aa0 (GdkX11DragContext at 0x1988820)> <GtkSelectionData at 0x7fffb106b760> 0 21962912Traceback (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 stron_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 21962912Traceback (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, Gdkwindow = Gtk.Window()window.set_size_request(300, 200)window.connect('delete_event', Gtk.main_quit)# Define Liblarch Treestore = 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_gtktv = 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 = storedef _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 sorttreemodel.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 stuffdnd_internal_target = ''dnd_external_targets = {}def on_drag_fail(widget, dc, result): print "Failed dragging", widget, dc, resultdef __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_strdef 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=4The 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 这篇关于如何在GTK3中进行拖放和排序GtkTreeView?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持! 上岸,阿里云!