如何在GtkTreeView排序后重新启用手动拖拽重排功能?
这个问题我之前也碰到过,核心原因其实很好理解:当你点击列头排序时,GTK+会自动用GtkTreeModelSort包装你的原始数据模型(比如GtkListStore),并把TreeView的数据源切换成这个排序后的包装模型。而GtkTreeModelSort本质上只是个排序视图层,它本身不支持直接重排——所有的结构修改都必须作用在底层的原始模型上。你直接调用gtk_tree_view_set_reorderable()只会对这个包装模型生效,自然没啥用。
下面给你两种可行的解决方案,看你需求选:
方案1:排序后切换回原始模型恢复重排
如果排序后不需要保留当前的排序状态,只是想回到可拖拽重排的状态,这个方法最简单:
提前保存原始模型的引用
程序初始化时,别只靠gtk_tree_view_get_model()临时获取模型,把你的原始GtkListStore(或其他支持重排的模型)存在一个全局变量或者类实例变量里,比如叫original_model。在排序完成后切换模型
你可以连接TreeView的sort-column-changed信号,当用户完成排序后触发回调,把TreeView的模型切回原始模型:// 假设tree_view是你的TreeView控件,original_model是提前保存的原始模型 gtk_tree_view_set_model(GTK_TREE_VIEW(tree_view), GTK_TREE_MODEL(original_model)); // 确保重排功能处于开启状态 gtk_tree_view_set_reorderable(GTK_TREE_VIEW(tree_view), TRUE);这样TreeView就回到了原始的可重排状态,之前的排序状态会被清除。
方案2:保留排序状态同时允许拖拽重排
如果想让用户既能看到排序后的结果,又能拖拽重排行,就需要手动拦截拖拽信号,让重排操作直接作用在原始模型上:
手动创建并管理排序模型
别让TreeView自动创建排序模型,自己手动实例化GtkTreeModelSort来包装原始模型,再把它设置给TreeView:// original_model是你的原始可重排模型(比如GtkListStore) GtkTreeModel *sort_model = gtk_tree_model_sort_new_with_model(GTK_TREE_MODEL(original_model)); gtk_tree_view_set_model(GTK_TREE_VIEW(tree_view), sort_model); // 开启列头排序功能 gtk_tree_view_set_headers_clickable(GTK_TREE_VIEW(tree_view), TRUE); // 开启重排(后续我们会手动处理这个信号) gtk_tree_view_set_reorderable(GTK_TREE_VIEW(tree_view), TRUE);拦截拖拽信号并转换为原始模型操作
因为TreeView的拖拽信号默认是针对当前的排序模型的,我们需要连接drag-data-received信号,手动把排序模型的路径转换为原始模型的路径,再执行重排:// 绑定信号,把原始模型作为user_data传进去 g_signal_connect(tree_view, "drag-data-received", G_CALLBACK(on_drag_data_received), original_model);然后实现回调函数:
static void on_drag_data_received(GtkWidget *tree_view, GdkDragContext *context, gint x, gint y, GtkSelectionData *data, guint info, guint time, gpointer user_data) { GtkTreeModel *original_model = GTK_TREE_MODEL(user_data); GtkTreeModel *sort_model = gtk_tree_view_get_model(GTK_TREE_VIEW(tree_view)); // 获取拖拽源在排序模型中的路径 GtkTreePath *sort_source_path = gtk_tree_path_new_from_string((gchar*)gtk_selection_data_get_data(data)); // 获取目标位置在排序模型中的路径和位置(前/后) GtkTreePath *sort_dest_path; GtkTreeViewDropPosition drop_pos; gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(tree_view), x, y, &sort_dest_path, &drop_pos); // 把排序模型的路径转换为原始模型的路径 GtkTreePath *source_path = gtk_tree_model_sort_convert_path_to_child_path(GTK_TREE_MODEL_SORT(sort_model), sort_source_path); GtkTreePath *dest_path = NULL; if (sort_dest_path) { dest_path = gtk_tree_model_sort_convert_path_to_child_path(GTK_TREE_MODEL_SORT(sort_model), sort_dest_path); } // 在原始模型上执行移动操作 if (drop_pos == GTK_TREE_VIEW_DROP_BEFORE && dest_path) { gtk_list_store_move_before(GTK_LIST_STORE(original_model), NULL, dest_path); } else { gtk_list_store_move_after(GTK_LIST_STORE(original_model), NULL, dest_path); } // 释放所有路径资源 gtk_tree_path_free(sort_source_path); if (sort_dest_path) gtk_tree_path_free(sort_dest_path); gtk_tree_path_free(source_path); if (dest_path) gtk_tree_path_free(dest_path); // 通知拖拽操作完成 gtk_drag_finish(context, TRUE, FALSE, time); }这样一来,即使TreeView显示的是排序后的视图,拖拽操作也会直接修改原始模型,排序模型会自动同步更新视图,重排和排序就能同时工作了。
一些注意事项
- 确保你的原始模型是支持重排的,比如
GtkListStore,只读模型或者结构复杂的GtkTreeStore可能不适用。 - 方案2中要注意处理目标路径为空的情况(比如拖拽到列表末尾时),避免空指针错误。
内容的提问来源于stack exchange,提问作者user4918296

