gtk2.0实现树状列表搜索,与列表视图搜索差不多,但是更加复杂:
- 在搜索时需要搜索所有子节点。
- 需要自动展开搜索到的子节点。
所以代码中就多了这两个部分。其中search_tree_recursive就是递归搜索所有节点,expand_parents是展开节点的所有父节点,实现子节点的可见。
代码主体由deepseek ai提供
#include <gtk/gtk.h>
#include <string.h>
typedef struct {
GtkTreeView *treeview;
GtkEntry *entry;
GList *matches; // 所有匹配项的路径列表
GList *current; // 当前显示的匹配项
GtkLabel *status; // 显示状态信息
} SearchData;
/* 清除之前的搜索结果 */
void clear_search_results(SearchData *data)
{
if (data->matches) {
g_list_foreach(data->matches, (GFunc)gtk_tree_path_free, NULL);
g_list_free(data->matches);
data->matches = NULL;
}
data->current = NULL;
}
/* 展开路径的所有父节点 */
void expand_parents(GtkTreeView *treeview, GtkTreePath *path)
{
GtkTreePath *parent_path = gtk_tree_path_copy(path);
/*不能直接这样用*/
/*
while (gtk_tree_path_up(parent_path)) {
gtk_tree_view_expand_row(treeview, parent_path, FALSE);
}
*/
GList *pathList = NULL;
// 构建路径列表(从最顶层到最接近的父节点)
while (gtk_tree_path_up(parent_path)) {
// 每次都需要复制新的路径对象
pathList = g_list_prepend(pathList, gtk_tree_path_copy(parent_path));
}
// 展开所有父节点(从最顶层到最接近的父节点)
GList *iter = pathList;
while (iter != NULL) {
gtk_tree_view_expand_row(treeview, (GtkTreePath *)iter->data, FALSE);
iter = iter->next;
}
// 释放资源
g_list_foreach(pathList, (GFunc)gtk_tree_path_free, NULL);
g_list_free(pathList);
gtk_tree_path_free(parent_path);
}
/* 递归搜索树的所有节点 */
void search_tree_recursive(GtkTreeModel *model, GtkTreeIter *parent,
const gchar *search_text, SearchData *data)
{
GtkTreeIter iter;
gboolean valid = gtk_tree_model_iter_children(model, &iter, parent);
while (valid) {
gchar *text;
gtk_tree_model_get(model, &iter, 0, &text, -1);
if (text && strstr(text, search_text)) {
GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
data->matches = g_list_append(data->matches, path);
}
// 递归搜索子节点
if (gtk_tree_model_iter_has_child(model, &iter)) {
search_tree_recursive(model, &iter, search_text, data);
}
g_free(text);
valid = gtk_tree_model_iter_next(model, &iter);
}
}
/* 搜索所有匹配项 */
void perform_search(SearchData *data)
{
GtkTreeModel *model = gtk_tree_view_get_model(data->treeview);
const gchar *search_text = gtk_entry_get_text(data->entry);
clear_search_results(data);
if (search_text == NULL || strlen(search_text) == 0) {
gtk_label_set_text(data->status, "Please enter search text");
return;
}
/* 从根节点开始递归搜索 */
search_tree_recursive(model, NULL, search_text, data);
/* 更新状态 */
guint count = g_list_length(data->matches);
if (count > 0) {
data->current = data->matches;
gchar *msg = g_strdup_printf("Total: %d Current: 1/%d", count, count);
gtk_label_set_text(data->status, msg);
g_free(msg);
/* 跳转到第一个匹配项 */
GtkTreePath *path = (GtkTreePath *)data->current->data;
// 展开所有父节点确保匹配项可见
expand_parents(data->treeview, path);
GtkTreeSelection *selection = gtk_tree_view_get_selection(data->treeview);
gtk_tree_selection_select_path(selection, path);
gtk_tree_view_scroll_to_cell(data->treeview, path, NULL, TRUE, 0.5, 0.0);
} else {
gtk_label_set_text(data->status, "No matches found");
}
}
/* 按钮点击事件处理函数 */
void on_search_clicked(GtkButton *button, gpointer user_data)
{
SearchData *data = (SearchData *)user_data;
perform_search(data);
}
void on_next_clicked(GtkButton *button, gpointer user_data)
{
SearchData *data = (SearchData *)user_data;
if (data->matches == NULL) {
perform_search(data);
return;
}
if (data->current && data->current->next) {
data->current = data->current->next;
GtkTreePath *path = (GtkTreePath *)data->current->data;
// 展开所有父节点确保匹配项可见
expand_parents(data->treeview, path);
GtkTreeSelection *selection = gtk_tree_view_get_selection(data->treeview);
gtk_tree_selection_select_path(selection, path);
gtk_tree_view_scroll_to_cell(data->treeview, path, NULL, TRUE, 0.5, 0.0);
/* 更新状态 */
guint pos = g_list_position(data->matches, data->current) + 1;
guint total = g_list_length(data->matches);
gchar *msg = g_strdup_printf("Total: %d Current: %d/%d", total, pos, total);
gtk_label_set_text(data->status, msg);
g_free(msg);
}
}
void on_previous_clicked(GtkButton *button, gpointer user_data)
{
SearchData *data = (SearchData *)user_data;
if (data->matches == NULL) {
perform_search(data);
return;
}
if (data->current && data->current->prev) {
data->current = data->current->prev;
GtkTreePath *path = (GtkTreePath *)data->current->data;
// 展开所有父节点确保匹配项可见
expand_parents(data->treeview, path);
GtkTreeSelection *selection = gtk_tree_view_get_selection(data->treeview);
gtk_tree_selection_select_path(selection, path);
gtk_tree_view_scroll_to_cell(data->treeview, path, NULL, TRUE, 0.5, 0.0);
/* 更新状态 */
guint pos = g_list_position(data->matches, data->current) + 1;
guint total = g_list_length(data->matches);
gchar *msg = g_strdup_printf("Total: %d Current: %d/%d", total, pos, total);
gtk_label_set_text(data->status, msg);
g_free(msg);
}
}
/* 创建示例树状数据 */
void setup_tree_data(GtkTreeStore *store)
{
GtkTreeIter iter, child_iter, grandchild_iter;
/* 添加第一级节点 - 水果 */
gtk_tree_store_append(store, &iter, NULL);
gtk_tree_store_set(store, &iter, 0, "Fruits", 1, 0, -1);
/* 添加子节点 */
gtk_tree_store_append(store, &child_iter, &iter);
gtk_tree_store_set(store, &child_iter, 0, "Apple", 1, 10, -1);
gtk_tree_store_append(store, &child_iter, &iter);
gtk_tree_store_set(store, &child_iter, 0, "Banana", 1, 20, -1);
gtk_tree_store_append(store, &child_iter, &iter);
gtk_tree_store_set(store, &child_iter, 0, "Orange", 1, 15, -1);
/* 添加第二级节点 - 蔬菜 */
gtk_tree_store_append(store, &iter, NULL);
gtk_tree_store_set(store, &iter, 0, "Vegetables", 1, 0, -1);
/* 添加子节点 */
gtk_tree_store_append(store, &child_iter, &iter);
gtk_tree_store_set(store, &child_iter, 0, "Carrot", 1, 8, -1);
gtk_tree_store_append(store, &child_iter, &iter);
gtk_tree_store_set(store, &child_iter, 0, "Potato", 1, 12, -1);
gtk_tree_store_append(store, &child_iter, &iter);
gtk_tree_store_set(store, &child_iter, 0, "Tomato", 1, 6, -1);
/* 添加第三级节点 - 饮料 */
gtk_tree_store_append(store, &iter, NULL);
gtk_tree_store_set(store, &iter, 0, "Drinks", 1, 0, -1);
/* 添加子节点 */
gtk_tree_store_append(store, &child_iter, &iter);
gtk_tree_store_set(store, &child_iter, 0, "Water", 1, 30, -1);
gtk_tree_store_append(store, &child_iter, &iter);
gtk_tree_store_set(store, &child_iter, 0, "Juice", 1, 5, -1);
gtk_tree_store_append(store, &child_iter, &iter);
gtk_tree_store_set(store, &child_iter, 0, "Milk", 1, 7, -1);
/* 添加更多层级数据 - 电子产品 */
gtk_tree_store_append(store, &iter, NULL);
gtk_tree_store_set(store, &iter, 0, "Electronics", 1, 0, -1);
/* 电脑分类 */
gtk_tree_store_append(store, &child_iter, &iter);
gtk_tree_store_set(store, &child_iter, 0, "Computers", 1, 0, -1);
gtk_tree_store_append(store, &grandchild_iter, &child_iter);
gtk_tree_store_set(store, &grandchild_iter, 0, "Laptop", 1, 5, -1);
gtk_tree_store_append(store, &grandchild_iter, &child_iter);
gtk_tree_store_set(store, &grandchild_iter, 0, "Desktop", 1, 3, -1);
/* 手机分类 */
gtk_tree_store_append(store, &child_iter, &iter);
gtk_tree_store_set(store, &child_iter, 0, "Phones", 1, 0, -1);
gtk_tree_store_append(store, &grandchild_iter, &child_iter);
gtk_tree_store_set(store, &grandchild_iter, 0, "Smartphone", 1, 8, -1);
gtk_tree_store_append(store, &grandchild_iter, &child_iter);
gtk_tree_store_set(store, &grandchild_iter, 0, "Tablet", 1, 4, -1);
}
int main(int argc, char *argv[])
{
GtkWidget *window, *scrolled_window, *treeview, *vbox, *hbox, *entry;
GtkWidget *search_btn, *prev_btn, *next_btn, *status_label;
GtkTreeStore *store;
SearchData *data = g_new0(SearchData, 1);
gtk_init(&argc, &argv);
/* 创建主窗口 */
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(window), "TreeView Search");
gtk_window_set_default_size(GTK_WINDOW(window), 500, 400);
g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
/* 创建垂直容器 */
vbox = gtk_vbox_new(FALSE, 5);
gtk_container_add(GTK_CONTAINER(window), vbox);
/* 创建搜索栏 */
hbox = gtk_hbox_new(FALSE, 5);
gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
/* 创建搜索输入框 */
entry = gtk_entry_new();
gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 0);
/* 创建搜索按钮 */
search_btn = gtk_button_new_with_label("Search");
gtk_box_pack_start(GTK_BOX(hbox), search_btn, FALSE, FALSE, 0);
/* 创建导航按钮栏 */
hbox = gtk_hbox_new(FALSE, 5);
gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
prev_btn = gtk_button_new_with_label("Previous");
gtk_box_pack_start(GTK_BOX(hbox), prev_btn, FALSE, FALSE, 0);
next_btn = gtk_button_new_with_label("Next");
gtk_box_pack_start(GTK_BOX(hbox), next_btn, FALSE, FALSE, 0);
/* 创建状态标签 */
status_label = gtk_label_new("Ready to search");
gtk_box_pack_start(GTK_BOX(vbox), status_label, FALSE, FALSE, 0);
/* 创建滚动窗口和树状视图 */
scrolled_window = gtk_scrolled_window_new(NULL, NULL);
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
gtk_box_pack_start(GTK_BOX(vbox), scrolled_window, TRUE, TRUE, 0);
treeview = gtk_tree_view_new();
gtk_container_add(GTK_CONTAINER(scrolled_window), treeview);
/* 初始化搜索数据结构 */
data->treeview = GTK_TREE_VIEW(treeview);
data->entry = GTK_ENTRY(entry);
data->status = GTK_LABEL(status_label);
data->matches = NULL;
data->current = NULL;
/* 创建TreeStore并添加数据 */
store = gtk_tree_store_new(2, G_TYPE_STRING, G_TYPE_INT);
setup_tree_data(store);
/* 将模型设置到TreeView */
gtk_tree_view_set_model(GTK_TREE_VIEW(treeview), GTK_TREE_MODEL(store));
g_object_unref(store); // 模型现在由treeview管理
/* 添加列到TreeView */
GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(treeview),
-1, "Name", renderer,
"text", 0, NULL);
renderer = gtk_cell_renderer_text_new();
gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(treeview),
-1, "Quantity", renderer,
"text", 1, NULL);
/* 连接信号 */
g_signal_connect(search_btn, "clicked", G_CALLBACK(on_search_clicked), data);
g_signal_connect(entry, "activate", G_CALLBACK(on_search_clicked), data);
g_signal_connect(next_btn, "clicked", G_CALLBACK(on_next_clicked), data);
g_signal_connect(prev_btn, "clicked", G_CALLBACK(on_previous_clicked), data);
/* 窗口关闭时清理数据 */
g_signal_connect(window, "destroy", G_CALLBACK(clear_search_results), data);
g_signal_connect(window, "destroy", G_CALLBACK(g_free), data);
gtk_widget_show_all(window);
gtk_main();
return 0;
}其中expand_parents展开所有父节点一开始deepseek提供的办法就是
GtkTreePath *parent_path = gtk_tree_path_copy(path);
while (gtk_tree_path_up(parent_path)) {
gtk_tree_view_expand_row(treeview, parent_path, FALSE);
}
gtk_tree_path_free(parent_path);测试这样在多层子节点时,并不能展开子节点的所有父节点。只能展开最顶层的父节点。
原因是gtk节点的展开顺序是从最上层到最下层子节点展开的。而deepseek提供的这个代码是直接从子节点的直接父节点一层一层向上展开的。无法展开。
所以我就改成了使用链表倒序储存节点路径,之后再遍历链表,这样就能够实现从上往下的展开了。但是我犯错,导致还是不能正常展开,因为我对于gtk的树状列表不够了解。
错误代码:
GtkTreePath *parent_path = gtk_tree_path_copy(path);
GList *pathList = NULL;
while (gtk_tree_path_up(parent_path)) {
pathList = g_list_prepend(pathList, parent_path);
}
//展开
GList *currentPos = pathList;
do{
gtk_tree_view_expand_row(treeview, (GtkTreePath *)currentPos->data, FALSE);
currentPos = currentPos->next;
}while(currentPos->next!=NULL);
g_list_free(pathList);
gtk_tree_path_free(parent_path);询问deepseek给我纠正
1.路径列表构建问题:
你在循环中使用 gtk_tree_path_up(parent_path) 修改了原始路径
但将所有修改后的同一路径指针多次添加到列表中(实际上添加的是同一个路径的不同状态)
2.展开循环问题:
循环条件 currentPos->next!=NULL 会导致漏掉最后一个节点
使用 do-while 循环不如 while 循环安全(如果 pathList 为 NULL 会崩溃)
3.内存管理问题:
你只释放了 pathList 和 parent_path,但没有释放列表中存储的路径
它给我改成了
GtkTreePath *parent_path = gtk_tree_path_copy(path);
GList *pathList = NULL;
// 构建路径列表(从最顶层到最接近的父节点)
while (gtk_tree_path_up(parent_path)) {
// 每次都需要复制新的路径对象
pathList = g_list_prepend(pathList, gtk_tree_path_copy(parent_path));
}
// 展开所有父节点(从最顶层到最接近的父节点)
GList *iter = pathList;
while (iter != NULL) {
gtk_tree_view_expand_row(treeview, (GtkTreePath *)iter->data, FALSE);
iter = iter->next;
}
// 释放资源
g_list_foreach(pathList, (GFunc)gtk_tree_path_free, NULL);
g_list_free(pathList);
gtk_tree_path_free(parent_path);
这样就成功运行了
效果
感谢deepseek!没有它我无法快速理解和创建gtk程序!gtk2.0时至今日,已经没有什么资料可以参考,只有《Foundations of GTK+ Development》,还有官方api参考文档。
但是问deepseek ai好像它什么都懂一样!
没有ai我也能作出来这些功能,但是会浪费大量时间在研究这些api用法上面。有了ai我直接作出来了。省的自己直接去写代码。