GTKColumnView / Sorter no works

I wanted to try out a sorter in GTKColumnView. What I managed to do was to get the sorting arrows to appear in the title bar. I tried clicking all over the title bar, but the compareFunc is not called. I used g_print as a test.

What is missing in my code that this doesn’t work?


// gcc -o main main.c `pkg-config --cflags --libs gtk4`

#include <gtk/gtk.h>

static GListModel* create_model() {
    GListStore* store = g_list_store_new(G_TYPE_OBJECT);
    for (int i = 1; i < 8; i++) {
        GObject* obj = g_object_new(G_TYPE_OBJECT, NULL);
        gint * value = g_new(gint, 1); 
        *value = i;
        g_object_set_data_full(obj, "item-object", value, g_free);
        g_list_store_append(store, obj);
        g_object_unref(obj);
    }
    return G_LIST_MODEL(store);
}

static gint compareFunc(gconstpointer a, gconstpointer b, gpointer data)
{
  g_print("compare func\n");
  return 0; // From Test
}

static void setup_cb(GtkSignalListItemFactory* factory, GtkListItem* list_item, gpointer user_data) {
    GtkWidget* label = gtk_label_new(NULL);
    gtk_list_item_set_child(list_item, label);
}

static void bind_number_cb(GtkSignalListItemFactory* factory, GtkListItem* list_item, gpointer user_data) {
    GtkWidget* label = gtk_list_item_get_child(list_item);
    GObject* item = gtk_list_item_get_item(list_item);
    gint *value = g_object_get_data(item, "item-object");
    char buffer[32];
    snprintf(buffer, sizeof(buffer), "%d", *value);
    gtk_label_set_text(GTK_LABEL(label), buffer);
}

static void activate(GtkApplication* app, gpointer user_data) {
    GtkWidget* window = gtk_application_window_new(app);
    gtk_window_set_title(GTK_WINDOW(window), "Sort Test");
    gtk_window_set_default_size(GTK_WINDOW(window), 400, 300);

    GtkWidget* scrolled_window = gtk_scrolled_window_new();
    gtk_window_set_child(GTK_WINDOW(window), scrolled_window);

    GListModel* model = create_model();
    GtkSelectionModel* selection_model = GTK_SELECTION_MODEL(gtk_no_selection_new(model));

    GtkWidget* column_view = gtk_column_view_new(selection_model);
    gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scrolled_window), column_view);

    GtkListItemFactory* number_factory = gtk_signal_list_item_factory_new();
    g_signal_connect(number_factory, "setup", G_CALLBACK(setup_cb), NULL);
    g_signal_connect(number_factory, "bind", G_CALLBACK(bind_number_cb), NULL);

    GtkColumnViewColumn* column = gtk_column_view_column_new("Numbers", number_factory);
    gtk_column_view_append_column(GTK_COLUMN_VIEW(column_view), column);

    GtkSorter *sorter = GTK_SORTER(gtk_custom_sorter_new(compareFunc, NULL, NULL));
    gtk_column_view_column_set_sorter(column, sorter);
    g_object_unref(sorter); 

    gtk_window_present(GTK_WINDOW(window));
}

int main(int argc, char** argv) {
    GtkApplication* app = gtk_application_new("org.gtk.example", G_APPLICATION_DEFAULT_FLAGS);
    g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
    int status = g_application_run(G_APPLICATION(app), argc, argv);
    g_object_unref(app);
    return status;
}

See:

There’s an example showing how you need to insert the ColumnView’s special sorter into your model stack.

There is an example in Stackoverflow. Perhaps it can serve as a model.

This question has also been addressed in this forum:

My proposal:

// ...
   GtkWidget* column_view = gtk_column_view_new(NULL);
    gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scrolled_window), column_view);

    // setup sort model then overlay with columnview
    GtkSorter *sorter = g_object_ref(gtk_column_view_get_sorter(GTK_COLUMN_VIEW(column_view)));
    GtkSortListModel *model = gtk_sort_list_model_new(create_model(),sorter);
    GtkSelectionModel* selection_model = GTK_SELECTION_MODEL(gtk_no_selection_new(G_LIST_MODEL(model)));
    gtk_column_view_set_model(GTK_COLUMN_VIEW(column_view),GTK_SELECTION_MODEL(selection_model));

// ...
   sorter = GTK_SORTER(gtk_custom_sorter_new(compareFunc, NULL, NULL));
// ...

The documentation says:

“The column view supports sorting that can be customized by the user by clicking on column headers. To set this up, the GtkSorter returned by gtk_column_view_get_sorter() must be attached to a sort model for the data that the view is showing, and the columns must have sorters attached to them by calling gtk_column_view_column_set_sorter(). The initial sort order can be set with gtk_column_view_sort_by_column().”

Have fun trying.

I’ve almost got it working now. Sorting works great.
But unfortunately it still has one error.
If I remove a line with g_list_store_remove(store, 1);, object_free is called and the memory is cleaned up. This proves that g_printf. But if I quit the program by clicking on , nothing is cleaned up.
When I hadn’t built in a sorting function, it cleaned up properly when I quit.

Does anyone know how I can fix this error?


// gcc -o main main.c `pkg-config --cflags --libs gtk4`

#include <gtk/gtk.h>

void object_free(gpointer data)
{
  g_printf("free object\n");
  g_free(data);
}

static void add_item(GListStore* store ) {
  GObject* obj = g_object_new(G_TYPE_OBJECT, NULL);
  for (int j = 0; j < 3; j++) {
      gint * value = g_new(gint, 1); 
      *value = g_random_int_range(1, 100);  
      g_object_set_data_full(obj, g_strdup_printf("item-object-%d", j), value, object_free);
  }
  g_list_store_append(store, obj);
  g_object_unref(obj);
}

static gint compareFunc(gconstpointer a, gconstpointer b, gpointer user_data) {
    int column_index = GPOINTER_TO_INT(user_data);
    gint *int_a = g_object_get_data(G_OBJECT(a), g_strdup_printf("item-object-%d", column_index));
    gint *int_b = g_object_get_data(G_OBJECT(b), g_strdup_printf("item-object-%d", column_index));
    return *int_a - *int_b;
}

static void setup_cb(GtkSignalListItemFactory* factory, GtkListItem* list_item, gpointer user_data) {
    GtkWidget* label = gtk_label_new(NULL);
    gtk_list_item_set_child(list_item, label);
}

static void bind_number_cb(GtkSignalListItemFactory* factory, GtkListItem* list_item, gpointer user_data) {
    int column_index = GPOINTER_TO_INT(user_data);
    GtkWidget* label = gtk_list_item_get_child(list_item);
    GObject* item = gtk_list_item_get_item(list_item);
    gint *value = g_object_get_data(item, g_strdup_printf("item-object-%d", column_index));
    char buffer[32];
    snprintf(buffer, sizeof(buffer), "%d", *value);
    gtk_label_set_text(GTK_LABEL(label), buffer);
}

static void activate(GtkApplication* app, gpointer user_data) {
    GtkWidget* window = gtk_application_window_new(app);
    gtk_window_set_title(GTK_WINDOW(window), "Sort Example");
    gtk_window_set_default_size(GTK_WINDOW(window), 600, 300);

    GtkWidget* scrolled_window = gtk_scrolled_window_new();
    gtk_window_set_child(GTK_WINDOW(window), scrolled_window);

    GtkWidget* column_view = gtk_column_view_new(NULL);
    gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scrolled_window), column_view);

    // Erstellen des Sortierers für die ColumnView
    GtkSorter *view_sorter = gtk_column_view_get_sorter(GTK_COLUMN_VIEW(column_view));
    GListStore* store = g_list_store_new(G_TYPE_OBJECT);
    GtkSortListModel *sort_model = gtk_sort_list_model_new(G_LIST_MODEL(store), view_sorter);
    GtkSelectionModel* selection_model = GTK_SELECTION_MODEL(gtk_single_selection_new(G_LIST_MODEL(sort_model)));
    g_object_unref(view_sorter);


    gtk_column_view_set_model(GTK_COLUMN_VIEW(column_view), selection_model);

    const char* column_titles[] = {"Number 1", "Number 2", "Number 3"};
    for (int i = 0; i < 3; i++) {
        GtkListItemFactory* factory = gtk_signal_list_item_factory_new();
        g_signal_connect(factory, "setup", G_CALLBACK(setup_cb), NULL);
        g_signal_connect(factory, "bind", G_CALLBACK(bind_number_cb), GINT_TO_POINTER(i));

        GtkColumnViewColumn* column = gtk_column_view_column_new(column_titles[i], factory);
        gtk_column_view_append_column(GTK_COLUMN_VIEW(column_view), column);

        GtkSorter *column_sorter = GTK_SORTER(gtk_custom_sorter_new(compareFunc, GINT_TO_POINTER(i), NULL));
        gtk_column_view_column_set_sorter(column, column_sorter);
        g_object_unref(column_sorter);
        g_object_unref(column);
    }

    add_item(store);
    add_item(store);
    add_item(store);
    add_item(store);

    g_list_store_remove(store, 1); // free io.

    gtk_window_present(GTK_WINDOW(window));
}

int main(int argc, char** argv) {
    GtkApplication* app = gtk_application_new("org.gtk.example", G_APPLICATION_DEFAULT_FLAGS);
    g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
    int status = g_application_run(G_APPLICATION(app), argc, argv);
    g_object_unref(app);
    return status;
}

Yeah, memory management in C can be tricky. Some of these APIs are written for C convenience where the function takes ownership of its arguments (i.e. it steals your reference). The documentation will tell you for each parameter. For example, Gtk.SortListModel.new has:

The called function takes ownership of the data, and is responsible for freeing it.

for both model and sorter. (I kind of miss the brevity of [transfer full], but whatever.)

The example in the documentation I linked to has:

sorter = g_object_ref (gtk_column_view_get_sorter (view)));

That g_object_ref() is needed because the column view retains ownership of the sorter, and gtk_sort_list_model_new() takes a ref (see above).

Your code is actually two references short on view_sorter because you also explicitly unref it.

You do need to unref selection_model since gtk_column_view_set_model() doesn’t take ownership of it. (“The data is owned by the caller of the method.”)

I haven’t thoroughly checked the rest of your code, but it seems to work correctly with those three changes.

1 Like

I copied this example. The following line is there. sorter = g_object_ref(gtk_column_view_get_sorter(GTK_COLUMN_VIEW(cv)));
But in this version it has exactly the same effect. g_list_store_remove and g_list_store_remove_all clean up properly, but nothing is cleaned up when you exit.

I also tried the following. Then it cleans up except for one entry. If there are 4 entries, it takes 3.
g_signal_connect_swapped(scrolled_window, "destroy", G_CALLBACK(g_object_unref), model);
You could live with that in a pinch, but it’s not clean.

That was only one of a few changes I suggested you make. Here’s a diff between your version and mine:

--- columnviewsort-orig.c	2025-01-30 14:03:26.307045990 -0500
+++ columnviewsort.c	2025-01-30 14:17:25.393898713 -0500
@@ -1,3 +1,4 @@
+#include <glib/gprintf.h>
 #include <gtk/gtk.h>
 
 void object_free(gpointer data)
@@ -51,14 +52,14 @@
     gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scrolled_window), column_view);
 
     // Erstellen des Sortierers für die ColumnView
-    GtkSorter *view_sorter = gtk_column_view_get_sorter(GTK_COLUMN_VIEW(column_view));
+    GtkSorter *view_sorter = g_object_ref (gtk_column_view_get_sorter(GTK_COLUMN_VIEW(column_view)));
     GListStore* store = g_list_store_new(G_TYPE_OBJECT);
     GtkSortListModel *sort_model = gtk_sort_list_model_new(G_LIST_MODEL(store), view_sorter);
     GtkSelectionModel* selection_model = GTK_SELECTION_MODEL(gtk_single_selection_new(G_LIST_MODEL(sort_model)));
-    g_object_unref(view_sorter);
 
 
     gtk_column_view_set_model(GTK_COLUMN_VIEW(column_view), selection_model);
+    g_object_unref(selection_model);
 
     const char* column_titles[] = {"Number 1", "Number 2", "Number 3"};
     for (int i = 0; i < 3; i++) {
@@ -80,7 +81,6 @@
     add_item(store);
     add_item(store);
 
-    g_list_store_remove(store, 1); // free io.
 
     gtk_window_present(GTK_WINDOW(window));
 }

With that, I get 12 "free object"s printed upon closing the window.

2 Likes

Thank you very much, now it works as intended.
I don’t understand what the view_sorter is all about. The code is quite obscure.
With column_sorter it is still understandable what is happening.
It seems that I have got a rather complex widget with GtkColumnView.

I am attaching my running code, I think others will be happy with it.


// gcc -o main main.c pkg-config --cflags --libs gtk4

#include <gtk/gtk.h>

void object_free(gpointer data)
{
  g_print("free object\n");
  g_free(data);
}

static void add_item(GListStore* store ) {
  GObject* obj = g_object_new(G_TYPE_OBJECT, NULL);
  for (int j = 0; j < 3; j++) {
      gint * value = g_new(gint, 1); 
      *value = g_random_int_range(1, 100);  
      g_object_set_data_full(obj, g_strdup_printf("item-object-%d", j), value, object_free);
  }
  g_list_store_append(store, obj);
  g_object_unref(obj);
}

static gint compareFunc(gconstpointer a, gconstpointer b, gpointer user_data) {
    int column_index = GPOINTER_TO_INT(user_data);
    gint *int_a = g_object_get_data(G_OBJECT(a), g_strdup_printf("item-object-%d", column_index));
    gint *int_b = g_object_get_data(G_OBJECT(b), g_strdup_printf("item-object-%d", column_index));
    return *int_a - *int_b;
}

static void setup_cb(GtkSignalListItemFactory* factory, GtkListItem* list_item, gpointer user_data) {
    GtkWidget* label = gtk_label_new(NULL);
    gtk_list_item_set_child(list_item, label);
}

static void bind_number_cb(GtkSignalListItemFactory* factory, GtkListItem* list_item, gpointer user_data) {
    int column_index = GPOINTER_TO_INT(user_data);
    GtkWidget* label = gtk_list_item_get_child(list_item);
    GObject* item = gtk_list_item_get_item(list_item);
    gint *value = g_object_get_data(item, g_strdup_printf("item-object-%d", column_index));
    char buffer[32];
    snprintf(buffer, sizeof(buffer), "%d", *value);
    gtk_label_set_text(GTK_LABEL(label), buffer);
}

static void activate(GtkApplication* app, gpointer user_data) {
    GtkWidget* window = gtk_application_window_new(app);
    gtk_window_set_title(GTK_WINDOW(window), "Sort Example");
    gtk_window_set_default_size(GTK_WINDOW(window), 600, 300);

    GtkWidget* scrolled_window = gtk_scrolled_window_new();
    gtk_window_set_child(GTK_WINDOW(window), scrolled_window);

    GtkWidget* column_view = gtk_column_view_new(NULL);
    gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scrolled_window), column_view);

    GListStore* store = g_list_store_new(G_TYPE_OBJECT);

    GtkSorter *view_sorter = g_object_ref (gtk_column_view_get_sorter(GTK_COLUMN_VIEW(column_view)));
    GtkSortListModel *sort_model = gtk_sort_list_model_new(G_LIST_MODEL(store), view_sorter);
    GtkSelectionModel* selection_model = GTK_SELECTION_MODEL(gtk_single_selection_new(G_LIST_MODEL(sort_model)));
    gtk_column_view_set_model(GTK_COLUMN_VIEW(column_view), selection_model);
    g_object_unref(selection_model);

    const char* column_titles[] = {"Number 1", "Number 2", "Number 3"};
    for (int i = 0; i < 3; i++) {
        GtkListItemFactory* factory = gtk_signal_list_item_factory_new();
        g_signal_connect(factory, "setup", G_CALLBACK(setup_cb), NULL);
        g_signal_connect(factory, "bind", G_CALLBACK(bind_number_cb), GINT_TO_POINTER(i));

        GtkColumnViewColumn* column = gtk_column_view_column_new(column_titles[i], factory);
        gtk_column_view_append_column(GTK_COLUMN_VIEW(column_view), column);

        GtkSorter *column_sorter = GTK_SORTER(gtk_custom_sorter_new(compareFunc, GINT_TO_POINTER(i), NULL));
        gtk_column_view_column_set_sorter(column, column_sorter);
        g_object_unref(column_sorter);
        g_object_unref(column);
    }

    add_item(store);
    add_item(store);
    add_item(store);

    gtk_window_present(GTK_WINDOW(window));
}

int main(int argc, char** argv) {
    GtkApplication* app = gtk_application_new("org.gtk.example", G_APPLICATION_DEFAULT_FLAGS);
    g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
    int status = g_application_run(G_APPLICATION(app), argc, argv);
    g_object_unref(app);
    return status;
}

Now I have another problem.
g_list_store_remove(store, selected_position);
Doesn’t work anymore because the visual table no longer matches store after sorting.
How do I get to the real row in store if I have determined the selector position using gtk_bitset_get_nth(selected, 0);?