gtk2.0实现列表视图搜索

⌚Time: 2025-04-15 18:43:00

👨‍💻Author: Jack Ge

这是deepseek给的代码,我进行了一点修改,实现点击按钮搜索,跳转上一个/下一个。


#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 perform_search(SearchData *data)
{
    GtkTreeModel *model = gtk_tree_view_get_model(data->treeview);
    GtkTreeIter iter;
    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 input");
        return;
    }
    /* 遍历模型查找所有匹配项 */
    if (gtk_tree_model_get_iter_first(model, &iter)) {
        do {
            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);
            }
            
            g_free(text);
        } while (gtk_tree_model_iter_next(model, &iter));
    }
    
    /* 更新状态 */
    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;
        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 match 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;
        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;
        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);
    }
}

/* 主函数 */
int main(int argc, char *argv[])
{
    GtkWidget *window, *scrolled_window, *treeview, *vbox, *hbox, *entry;
    GtkWidget *search_btn, *prev_btn, *next_btn, *status_label;
    GtkListStore *store;
    GtkTreeIter iter;
    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");
    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("prev");
    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 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;
    
    /* 创建ListStore并添加一些数据 */
    store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT);
    
    // 添加示例数据
    gtk_list_store_append(store, &iter);
    gtk_list_store_set(store, &iter, 0, "Apple", 1, 10, -1);
    
    gtk_list_store_append(store, &iter);
    gtk_list_store_set(store, &iter, 0, "Banana", 1, 20, -1);
    
    gtk_list_store_append(store, &iter);
    gtk_list_store_set(store, &iter, 0, "Orange", 1, 15, -1);
    
    gtk_list_store_append(store, &iter);
    gtk_list_store_set(store, &iter, 0, "Grape", 1, 8, -1);
    
    gtk_list_store_append(store, &iter);
    gtk_list_store_set(store, &iter, 0, "Watermelon", 1, 3, -1);
    
    // 添加更多数据以便测试
    for (int i = 0; i < 50; i++) {
        gchar *name = g_strdup_printf("Item %d", i);
        gtk_list_store_append(store, &iter);
        gtk_list_store_set(store, &iter, 0, name, 1, i, -1);
        g_free(name);
    }
    
    /* 将模型设置到TreeView */
    gtk_tree_view_set_model(GTK_TREE_VIEW(treeview), GTK_TREE_MODEL(store));
    
    /* 添加列到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, "number", 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;
}

原理就是遍历列表数据,找到后就将所在路径储存在链表里,之后上一个,下一个就从链表里取得位置,并且跳转选中列表项。

其中清除搜索结果的函数释放了两处

//释放列表中每个元素指向的 GtkTreePath 对象
g_list_foreach(data->matches, (GFunc)gtk_tree_path_free, NULL);
//释放的是 GList 链表结构本身
g_list_free(data->matches);

内存结构示意图

data->matches (GList*) 
   |
   v
[list node #1] -> [list node #2] -> ... -> NULL
   |                |
   v                v
GtkTreePath*     GtkTreePath*
   |                |
   v                v
实际路径数据     实际路径数据

正确的释放顺序就是:

  1. 先用 g_list_foreach 释放所有路径对象

  2. 再用 g_list_free 释放链表结构

效果