Noob Column Sorting Confusion using GTK4, Columnview, C, looking for a working example

,

I’m a gnome newbie, working on a C application that displays mutliple columns. Created a test program to experiment, consisting of two text columns. That works.

However, failing miserably at adding the sort function for each, using the example in GTK4 api doc as the guide ( Gtk.ColumnView.get_sorter). Haven’t been able to find a good reference example in C of working code …any help?

Output from code so far.

image

Code compilation.

gcc pkg-config --cflags gtk4 column.view.test.model.c -g -o out pkg-config --libs gtk4

Code so far.

// Use for testing columnview

#include <gtk/gtk.h>

// Setup custom type - each item has two strings which will be displayed in two columns

#define TESTCOL_TYPE_ITEM (testcol_item_get_type())
G_DECLARE_FINAL_TYPE (TestColItem, testcol_item, TESTCOL, ITEM, GObject)

struct _TestColItem
{
    GObject parent_instance;
    const char *str1;
    const char *str2;
};

struct _TestColItemClass
{
    GObjectClass parent_class;
};

G_DEFINE_TYPE (TestColItem, testcol_item, G_TYPE_OBJECT)

static void testcol_item_init(TestColItem *item)
{
}

static void testcol_item_class_init(TestColItemClass *class)
{
}

// Set label for factory widget
static void setup_cb(GtkSignalListItemFactory *factory,GObject  *listitem)
{
    GtkWidget *label =gtk_label_new(NULL);
    gtk_list_item_set_child(GTK_LIST_ITEM(listitem),label);
}

// Set text from the column1 passed item for label
static void bind_col1_cb(GtkSignalListItemFactory *factory, GtkListItem *listitem)
{
    GtkWidget *label = gtk_list_item_get_child(listitem);
    TestColItem *item = gtk_list_item_get_item(GTK_LIST_ITEM(listitem));
	const char *string = item->str1;
	g_print("value is %s\n",string); // Show on std out
    gtk_label_set_text(GTK_LABEL (label), string);
}
// Set text from the column2 passed item for label
static void bind_col2_cb(GtkSignalListItemFactory *factory, GtkListItem *listitem)
{
    GtkWidget *label = gtk_list_item_get_child(listitem);
    TestColItem *item = gtk_list_item_get_item(GTK_LIST_ITEM(listitem));
	const char *string = item->str2;
	g_print("value is %s\n",string); // Show on std out
    gtk_label_set_text(GTK_LABEL (label), string);
}

// Create the custom object
static TestColItem * testcol_item_new(const char *str1, const char *str2)
{
	TestColItem  *item = g_object_new(TESTCOL_TYPE_ITEM, NULL);
	item->str1 = g_strdup(str1);
	item->str2 = g_strdup(str2);
	return item;
}	

// Activate
static void activate (GtkApplication* app, gpointer user_data)
{
	// Create and populate custom list model
	GListStore *store = g_list_store_new(G_TYPE_OBJECT); 
	GListModel *model = G_LIST_MODEL(store);

	g_list_store_append(store,testcol_item_new("abc1","ghi1"));
	g_list_store_append(store,testcol_item_new("def2","aaa2"));
	g_list_store_append(store,testcol_item_new("aaa3","jkl3"));

	// Set selection for custom model
	GtkSingleSelection *selection = gtk_single_selection_new(G_LIST_MODEL(model));
    gtk_single_selection_set_autoselect(selection,TRUE);

	// Set Windows in child scrolled window
	GtkWidget *window = gtk_application_window_new (app);
	gtk_window_set_title (GTK_WINDOW (window), "Test");
	gtk_window_set_default_size (GTK_WINDOW (window), 200, 200);
	GtkWidget *scrolled_window = gtk_scrolled_window_new (); 
	gtk_window_set_child (GTK_WINDOW(window), scrolled_window);

	// Add column view.  Put in scrolled window.  
    GtkWidget *cv = gtk_column_view_new(GTK_SELECTION_MODEL (selection));
    gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scrolled_window),cv);
    gtk_column_view_set_show_column_separators (GTK_COLUMN_VIEW (cv),TRUE);

	// Factory setup and bind for column view
    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_col1_cb),NULL);
	GtkColumnViewColumn *column = gtk_column_view_column_new("Col1", factory);
	gtk_column_view_append_column (GTK_COLUMN_VIEW (cv), column);

	// Factory setup and bind for column view
    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_col2_cb),NULL);
	column = gtk_column_view_column_new("Col2", factory);
	gtk_column_view_append_column (GTK_COLUMN_VIEW (cv), column);

	//From: https://docs.gtk.org/gtk4/method.ColumnView.get_sorter.html
	//gtk_column_view_column_set_sorter (column, sorter);
	//gtk_column_view_append_column (view, column);
	//sorter = g_object_ref (gtk_column_view_get_sorter (view)));
	//model = gtk_sort_list_model_new (store, sorter);
	//selection = gtk_no_selection_new (model);
	//gtk_column_view_set_model (view, selection);

	// Sort attempt, fails miserably, clearly don't get it
	// First comment out append and then add
	//GtkSorter *sorter = gtk_column_view_get_sorter ((GtkColumnView *) cv); // get the sorter for col
	//gtk_column_view_column_set_sorter(column, sorter); // set the sorter on column
	//gtk_column_view_append_column (GTK_COLUMN_VIEW (cv), column); // put col into colview
	//GtkSortListModel *smodel = gtk_sort_list_model_new (model, sorter);
	//selection = gtk_single_selection_new(G_LIST_MODEL(smodel));
	//gtk_column_view_set_model ((GtkColumnView *)cv, (GtkSelectionModel *)selection);


	// Show window
	gtk_window_present (GTK_WINDOW (window));
}

// Init application
int main (int argc, char **argv)
{
	GtkApplication *app;
	int status;

	app = gtk_application_new ("org.gtk.example", G_APPLICATION_DEFAULT_FLAGS);
	g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
	status = g_application_run (G_APPLICATION (app), argc, argv);
	g_object_unref (app);

	return status;
}

That six-line example in the documentation is a little tricky. It’s showing the basic structure of how to make column sorting work, but without all of the context:

gtk_column_view_column_set_sorter (column, sorter);

This part is pretty simple. For each column, you need to create a GtkSorter that can sort it when the header is clicked. You can use a GtkStringSorter in this case, which needs a GtkExpression to get the sort string from the item. The cleanest way to do that would be using GtkPropertyExpression, but that requires turning those struct members into properties:

    GtkSorter *sorter = GTK_SORTER (gtk_string_sorter_new (gtk_property_expression_new (TESTCOL_TYPE_ITEM, NULL, "str1")));
    gtk_column_view_column_set_sorter (column, GTK_SORTER (sorter));
    g_object_unref (sorter);

If you don’t want to do that, you can use a GtkCClosureExpression with a callback function. A third option is to use a GtkCustomSorter.


gtk_column_view_append_column (view, column);

This is just adding the column. You’re already doing that.


sorter = g_object_ref (gtk_column_view_get_sorter (view)));
model = gtk_sort_list_model_new (store, sorter);
selection = gtk_no_selection_new (model);
gtk_column_view_set_model (view, selection);

This is the magic part. In order for column-based sorting to actually work, you need to wrap your list store in a GtkSortListModel using the column view’s special GtkSorter.

Because you need that sorter to make the column view’s model, you can create the column view without one to start:

    GtkWidget *cv = gtk_column_view_new (NULL);

Adapting those four lines in the example to your code would then look like this:

    GtkSorter *sorter = g_object_ref (gtk_column_view_get_sorter (GTK_COLUMN_VIEW (cv)));
    GtkSortListModel *model = gtk_sort_list_model_new (G_LIST_MODEL (store), sorter);

    GtkSingleSelection *selection = gtk_single_selection_new (G_LIST_MODEL (model));
    gtk_single_selection_set_autoselect (selection, TRUE);

    gtk_column_view_set_model (GTK_COLUMN_VIEW (cv), GTK_SELECTION_MODEL (selection));
1 Like

Thank you! Now out of the weeds, your help is much appreciated. On to the next confusion!

I should have said almost out of the weeds. Now banging my head on a property error.

The problem - with the modified code a) I get a gtk type error to std out from the expression evaluation, b) the columns show the up/down sort direction icon but do not sort.

Can’t spot an error in the setup of the custom object type, which is working well enough for the factory to extract the values for the columnview list. Or the very helpful suggestions re sort additions, reviewing each api call and the parameters provided sure looks like it should work.

The modified code output to stdout:

(out:15451): Gtk-CRITICAL **: 13:50:48.267: Type TestColItem does not have a property named str1

(out:15451): Gtk-CRITICAL **: 13:50:48.267: Type TestColItem does not have a property named str2

value is abc1
value is ghi1
value is def2
value is aaa2
value is aaa3
value is jkl3

The modified code, using the suggestions from above.

// Use for testing columnview

#include <gtk/gtk.h>

// Setup custom type - each item has two strings which will be displayed in two columns

#define TESTCOL_TYPE_ITEM (testcol_item_get_type())
G_DECLARE_FINAL_TYPE (TestColItem, testcol_item, TESTCOL, ITEM, GObject)

struct _TestColItem
{
	GObject parent_instance;
	const char *str1;
	const char *str2;
};

struct _TestColItemClass
{
	GObjectClass parent_class;
};

G_DEFINE_TYPE (TestColItem, testcol_item, G_TYPE_OBJECT)

static void testcol_item_init(TestColItem *item)
{
}

static void testcol_item_class_init(TestColItemClass *class)
{
}

// Set label for factory widget
static void setup_cb(GtkSignalListItemFactory *factory,GObject  *listitem)
{
	GtkWidget *label =gtk_label_new(NULL);
	gtk_list_item_set_child(GTK_LIST_ITEM(listitem),label);
}

// Set text from the column1 passed item for label
static void bind_col1_cb(GtkSignalListItemFactory *factory, GtkListItem *listitem)
{
	GtkWidget *label = gtk_list_item_get_child(listitem);
	TestColItem *item = gtk_list_item_get_item(GTK_LIST_ITEM(listitem));
	const char *string = item->str1;
	g_print("value is %s\n",string); // Show on std out
	gtk_label_set_text(GTK_LABEL (label), string);
}
// Set text from the column2 passed item for label
static void bind_col2_cb(GtkSignalListItemFactory *factory, GtkListItem *listitem)
{
	GtkWidget *label = gtk_list_item_get_child(listitem);
	TestColItem *item = gtk_list_item_get_item(GTK_LIST_ITEM(listitem));
	const char *string = item->str2;
	g_print("value is %s\n",string); // Show on std out
	gtk_label_set_text(GTK_LABEL (label), string);
}

// Create the custom object
static TestColItem * testcol_item_new(const char *str1, const char *str2)
{
	TestColItem  *item = g_object_new(TESTCOL_TYPE_ITEM, NULL);
	item->str1 = g_strdup(str1);
	item->str2 = g_strdup(str2);
	return item;
}	

// Activate
static void activate (GtkApplication* app, gpointer user_data)
{
	// Create and populate custom list model
	GListStore *store = g_list_store_new(G_TYPE_OBJECT); 
	g_list_store_append(store,testcol_item_new("abc1","ghi1"));
	g_list_store_append(store,testcol_item_new("def2","aaa2"));
	g_list_store_append(store,testcol_item_new("aaa3","jkl3"));

	// Set Windows in child scrolled window
	GtkWidget *window = gtk_application_window_new (app);
	gtk_window_set_title (GTK_WINDOW (window), "Test");
	gtk_window_set_default_size (GTK_WINDOW (window), 200, 200);
	GtkWidget *scrolled_window = gtk_scrolled_window_new (); 
	gtk_window_set_child (GTK_WINDOW(window), scrolled_window);

	// Add column view.  Put in scrolled window.  
	GtkWidget *cv = gtk_column_view_new(NULL); // Null for now want to add sorter
	gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scrolled_window),cv);
	gtk_column_view_set_show_column_separators (GTK_COLUMN_VIEW (cv),TRUE);

	// Factory setup and bind for column 1 
	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_col1_cb),NULL);
	GtkColumnViewColumn *column = gtk_column_view_column_new("Col1", factory);
	GtkSorter *sorter = GTK_SORTER (gtk_string_sorter_new (gtk_property_expression_new (TESTCOL_TYPE_ITEM, NULL, "str1")));
	gtk_column_view_column_set_sorter (column, GTK_SORTER (sorter));
	gtk_column_view_append_column (GTK_COLUMN_VIEW (cv), column);

	// Factory setup and bind for column 2
	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_col2_cb),NULL);
	column = gtk_column_view_column_new("Col2", factory);
	sorter = GTK_SORTER (gtk_string_sorter_new (gtk_property_expression_new (TESTCOL_TYPE_ITEM, NULL, "str2")));
	gtk_column_view_column_set_sorter (column, GTK_SORTER (sorter));
	gtk_column_view_append_column (GTK_COLUMN_VIEW (cv), column);

	// Setup sort model then overlay with with columnview
	sorter = g_object_ref (gtk_column_view_get_sorter (GTK_COLUMN_VIEW (cv)));
    GtkSortListModel *model = gtk_sort_list_model_new (G_LIST_MODEL (store), sorter);
    GtkSingleSelection *selection = gtk_single_selection_new (G_LIST_MODEL (model));
    gtk_single_selection_set_autoselect (selection, TRUE);
    gtk_column_view_set_model (GTK_COLUMN_VIEW (cv), GTK_SELECTION_MODEL (selection));

	// Show window
	gtk_window_present (GTK_WINDOW (window));
}

// Init application
int main (int argc, char **argv)
{
	GtkApplication *app;
	int status;

	app = gtk_application_new ("org.gtk.example", G_APPLICATION_DEFAULT_FLAGS);
	g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
	status = g_application_run (G_APPLICATION (app), argc, argv);
	g_object_unref (app);

	return status;
}

That’s what I meant by “that requires turning those struct members into properties”.

In C, this requires some boilerplate code, but properties are a crucial part of effective GObject usage:

1 Like

I read up on properties and added the boilerplate code from the examples in the doc you referenced. That entire page is very interesting - also starting to wonder if I should be going the builder/ui route. In any case still getting errors and not sorting.

The errors:

`(out:60776): GLib-GObject-CRITICAL **: 12:56:56.846: g_object_class_install_properties: assertion ‘pspecs[0] == NULL’ failed

(out:60776): Gtk-CRITICAL **: 12:56:56.847: Type TestColItem does not have a property named str1

(out:60776): Gtk-CRITICAL **: 12:56:56.847: Type TestColItem does not have a property named str2
``
Now tripping up on the g_object_class_install_properties call. Using GDB ran through the code. The call appears to fail on line 866 in the snip below,

From file /usr/src/debug/glib2-2.78.3-1.fc39.x86_64/gobject/gobject.c

 857 g_object_class_install_properties (GObjectClass  *oclass,
 858                                    guint          n_pspecs,
 859                                    GParamSpec   **pspecs)
 860 {
 861   GType oclass_type, parent_type;
 862   guint i;
 863 
 864   g_return_if_fail (G_IS_OBJECT_CLASS (oclass));
 865   g_return_if_fail (n_pspecs > 1);
 866   g_return_if_fail (pspecs[0] == NULL);

After entering the install properties function these are the various parameter values reported by GDB.

14		g_object_class_install_properties (object_class,

(gdb) step
0x00007ffff7efec05 in g_object_class_install_properties (oclass=0x4ddf90, 
    n_pspecs=2, pspecs=0x405200 <obj_properties>) at ../gobject/gobject.c:860
860	{

(gdb) p *oclass
$1 = {g_type_class = {g_type = 0x562290 [TestColItem]}, construct_properties = 0x0, 
  constructor = 0x7ffff7f05f10 <g_object_constructor>, 
  set_property = 0x40264b <testcol_set_property>, 
  get_property = 0x40275e <testcol_get_property>, 
  dispose = 0x7ffff7f00710 <g_object_real_dispose>, 
  finalize = 0x7ffff7eff9e0 <g_object_finalize>, 
  dispatch_properties_changed = 0x7ffff7f006b0 <g_object_dispatch_properties_changed>, notify = 0x0, constructed = 0x7ffff7efee60 <g_object_constructed>, flags = 0, 
  n_construct_properties = 0, pspecs = 0x0, n_pspecs = 0, pdummy = {0x0, 0x0, 0x0}}

(gdb) p *pspecs
$2 = 0x4ddd70 [GParamString]

(gdb) p *pspecs[0]
$3 = {g_type_instance = {g_class = 0x4204d0 [g_type: GParamString]}, name = 0x42ccc3 "str1", flags = 11, value_type = 0x40 [gchararray], 
  owner_type = 0x0, _nick = 0x4e0ac0 "Str1", _blurb = 0x516870 "First column string", qdata = 0x2, ref_count = 1, param_id = 0}

(gdb) n
865	  g_return_if_fail (n_pspecs > 1);

(gdb) n
866	  g_return_if_fail (pspecs[0] == NULL);


(gdb) n
[Thread 0x7fffe4c006c0 (LWP 60124) exited]
0x00007ffff7eefb00 in g_return_if_fail_warning@plt () from /lib64/libgobject-2.0.so.0

So … pspecs[0] is NOT null, and it fails. Why SHOULD it be null though … aren’t we trying to define a property???

Code in play


#include <gtk/gtk.h>

// Setup custom type - each item has two strings which will be displayed in two columns

#define TESTCOL_TYPE_ITEM (testcol_item_get_type())
G_DECLARE_FINAL_TYPE (TestColItem, testcol_item, TESTCOL, ITEM, GObject)

struct _TestColItem
{
	GObject parent_instance;
	const char *str1;
	const char *str2;
};

struct _TestColItemClass
{
	GObjectClass parent_class;
};

G_DEFINE_TYPE (TestColItem, testcol_item, G_TYPE_OBJECT)

static void testcol_item_init(TestColItem *item)
{
}


// Add code to install property - see https://docs.gtk.org/gobject/concepts.html#object-properties
typedef enum
{
PROP_STR1,
PROP_STR2,
N_PROPERTIES
} TestColProperty;

static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, }; // Use in class init


// Function will be referenced in meta data for class
static void
testcol_set_property (GObject      *object,
			  guint         property_id,
			  const GValue *value,
			  GParamSpec   *pspec)
{
	TestColItem *self = (TestColItem *) object;

	switch ((TestColProperty) property_id)
	{
		case PROP_STR1:
			self->str1 = g_value_dup_string (value);
			g_print ("self str1: %s\n", self->str1);
			break;

		case PROP_STR2:
			self->str2 = g_value_dup_string (value);
			g_print ("self str2: %s\n", self->str2);
			break;

		default:
			/* We don't have any other property... */
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
			break;
}
}

// Function will be referenced in meta data for class
testcol_get_property (GObject    *object,
			  guint       property_id,
			  GValue     *value,
			  GParamSpec *pspec)
{
	TestColItem *self = (TestColItem *) object;

	switch ((TestColProperty) property_id)
	{
		case PROP_STR1:
			g_value_set_string (value, self->str1);
			break;

		case PROP_STR2:
			g_value_set_string (value, self->str2);
			break;

		default:
			/* We don't have any other property... */
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
			break;
	}
}

// Add code to install property for sort function reference
static void testcol_item_class_init(TestColItemClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);

	object_class->set_property = testcol_set_property;
	object_class->get_property = testcol_get_property;

	obj_properties[PROP_STR1] =  g_param_spec_string ("str1",
				 				"Str1",
				 				"First column string",
				 				NULL,
				 				G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);

	obj_properties[PROP_STR2] = g_param_spec_string ("str2",
				 				"Str2",
				 				"Second column string",
				 				NULL,
				 				G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);


	g_object_class_install_properties (object_class,
								N_PROPERTIES,
								obj_properties);
}

// Set label for factory widget
static void setup_cb(GtkSignalListItemFactory *factory,GObject  *listitem)
{
	GtkWidget *label =gtk_label_new(NULL);
	gtk_list_item_set_child(GTK_LIST_ITEM(listitem),label);
}

// Set text from the column1 passed item for label
static void bind_col1_cb(GtkSignalListItemFactory *factory, GtkListItem *listitem)
{
	GtkWidget *label = gtk_list_item_get_child(listitem);
	TestColItem *item = gtk_list_item_get_item(GTK_LIST_ITEM(listitem));
	const char *string = item->str1;
	g_print("value is %s\n",string); // Show on std out
	gtk_label_set_text(GTK_LABEL (label), string);
}
// Set text from the column2 passed item for label
static void bind_col2_cb(GtkSignalListItemFactory *factory, GtkListItem *listitem)
{
	GtkWidget *label = gtk_list_item_get_child(listitem);
	TestColItem *item = gtk_list_item_get_item(GTK_LIST_ITEM(listitem));
	const char *string = item->str2;
	g_print("value is %s\n",string); // Show on std out
	gtk_label_set_text(GTK_LABEL (label), string);
}

// Create the custom object
static TestColItem * testcol_item_new(const char *str1, const char *str2)
{
	TestColItem  *item = g_object_new(TESTCOL_TYPE_ITEM, NULL);
	item->str1 = g_strdup(str1);
	item->str2 = g_strdup(str2);
	return item;
}	

// Activate
static void activate (GtkApplication* app, gpointer user_data)
{
	// Create and populate custom list model
	GListStore *store = g_list_store_new(G_TYPE_OBJECT); 
	g_list_store_append(store,testcol_item_new("abc1","ghi1"));
	g_list_store_append(store,testcol_item_new("def2","aaa2"));
	g_list_store_append(store,testcol_item_new("aaa3","jkl3"));

	// Set Windows in child scrolled window
	GtkWidget *window = gtk_application_window_new (app);
	gtk_window_set_title (GTK_WINDOW (window), "Test");
	gtk_window_set_default_size (GTK_WINDOW (window), 200, 200);
	GtkWidget *scrolled_window = gtk_scrolled_window_new (); 
	gtk_window_set_child (GTK_WINDOW(window), scrolled_window);

	// Add column view.  Put in scrolled window.  
	GtkWidget *cv = gtk_column_view_new(NULL); // Null for now want to add sorter
	gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scrolled_window),cv);
	gtk_column_view_set_show_column_separators (GTK_COLUMN_VIEW (cv),TRUE);

	// Factory setup and bind for column 1 
	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_col1_cb),NULL);
	GtkColumnViewColumn *column = gtk_column_view_column_new("Col1", factory);
	GtkSorter *sorter = GTK_SORTER (gtk_string_sorter_new (gtk_property_expression_new (TESTCOL_TYPE_ITEM, NULL, "str1")));
	gtk_column_view_column_set_sorter (column, GTK_SORTER (sorter));
	gtk_column_view_append_column (GTK_COLUMN_VIEW (cv), column);

	// Factory setup and bind for column 2
	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_col2_cb),NULL);
	column = gtk_column_view_column_new("Col2", factory);
	sorter = GTK_SORTER (gtk_string_sorter_new (gtk_property_expression_new (TESTCOL_TYPE_ITEM, NULL, "str2")));
	gtk_column_view_column_set_sorter (column, GTK_SORTER (sorter));
	gtk_column_view_append_column (GTK_COLUMN_VIEW (cv), column);

	// Setup sort model then overlay with with columnview
	sorter = g_object_ref (gtk_column_view_get_sorter (GTK_COLUMN_VIEW (cv)));
    GtkSortListModel *model = gtk_sort_list_model_new (G_LIST_MODEL (store), sorter);
    GtkSingleSelection *selection = gtk_single_selection_new (G_LIST_MODEL (model));
    gtk_single_selection_set_autoselect (selection, TRUE);
    gtk_column_view_set_model (GTK_COLUMN_VIEW (cv), GTK_SELECTION_MODEL (selection));

	// Show window
	gtk_window_present (GTK_WINDOW (window));
}

// Init application
int main (int argc, char **argv)
{
	GtkApplication *app;
	int status;

	app = gtk_application_new ("org.gtk.example", G_APPLICATION_DEFAULT_FLAGS);
	g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
	status = g_application_run (G_APPLICATION (app), argc, argv);
	g_object_unref (app);

	return status;
}

or, add PROP_0 as the first element of the enum.

The first property id must be non-zero, which means the first element in the GParamSpec array must be NULL. It’s an implementation detail of how GObject properties work.

1 Like

Added PROP_STR = 1 in enum to leave the first entry NULL.

Now working!!! Columns sort without any Critical errors to std out. Yahoo!!!
Many thanks Chris and Emmanuele, I learned a lot from your guidance.

Just to close off, the working code:

// Use for testing columnview

#include <gtk/gtk.h>

// Setup custom type - each item has two strings which will be displayed in two columns

#define TESTCOL_TYPE_ITEM (testcol_item_get_type())
G_DECLARE_FINAL_TYPE (TestColItem, testcol_item, TESTCOL, ITEM, GObject)

struct _TestColItem
{
	GObject parent_instance;
	const char *str1;
	const char *str2;
};

struct _TestColItemClass
{
	GObjectClass parent_class;
};

G_DEFINE_TYPE (TestColItem, testcol_item, G_TYPE_OBJECT)

static void testcol_item_init(TestColItem *item)
{
}


// Add code to install property - see https://docs.gtk.org/gobject/concepts.html#object-properties
typedef enum
{
	PROP_STR1 = 1,
	PROP_STR2,
	N_PROPERTIES
} TestColProperty;

static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, }; // Use in class init


// Function will be referenced in meta data for class
static void
testcol_set_property (GObject      	*object,
			 		guint         	property_id,
			  		const GValue 	*value,
			  		GParamSpec   	*pspec)
{
	TestColItem *self = (TestColItem *) object;

	switch ((TestColProperty) property_id)
	{
		case PROP_STR1:
			self->str1 = g_value_dup_string (value);
			g_print ("self str1: %s\n", self->str1);
			break;

		case PROP_STR2:
			self->str2 = g_value_dup_string (value);
			g_print ("self str2: %s\n", self->str2);
			break;

		default:
			/* We don't have any other property... */
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
			break;
	}
}

// Function will be referenced in meta data for class
testcol_get_property (GObject    *object,
			  guint       property_id,
			  GValue     *value,
			  GParamSpec *pspec)
{
	TestColItem *self = (TestColItem *) object;

	switch ((TestColProperty) property_id)
	{
		case PROP_STR1:
			g_value_set_string (value, self->str1);
			break;

		case PROP_STR2:
			g_value_set_string (value, self->str2);
			break;

		default:
			/* We don't have any other property... */
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
			break;
	}
}

// Add code to install property for sort function reference
static void testcol_item_class_init(TestColItemClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);

	object_class->set_property = testcol_set_property;
	object_class->get_property = testcol_get_property;

	obj_properties[PROP_STR1] =  g_param_spec_string ("str1",
				 				"Str1",
				 				"First column string",
				 				NULL,
				 			        G_PARAM_READWRITE);

	obj_properties[PROP_STR2] = g_param_spec_string ("str2",
				 				"Str2",
				 				"Second column string",
				 				NULL,
				 				G_PARAM_READWRITE);


	g_object_class_install_properties (object_class,
								N_PROPERTIES,
								obj_properties);
}

// Set label for factory widget
static void setup_cb(GtkSignalListItemFactory *factory,GObject  *listitem)
{
	GtkWidget *label =gtk_label_new(NULL);
	gtk_list_item_set_child(GTK_LIST_ITEM(listitem),label);
}

// Set text from the column1 passed item for label
static void bind_col1_cb(GtkSignalListItemFactory *factory, GtkListItem *listitem)
{
	GtkWidget *label = gtk_list_item_get_child(listitem);
	TestColItem *item = gtk_list_item_get_item(GTK_LIST_ITEM(listitem));
	const char *string = item->str1;
	g_print("value is %s\n",string); // Show on std out
	gtk_label_set_text(GTK_LABEL (label), string);
}
// Set text from the column2 passed item for label
static void bind_col2_cb(GtkSignalListItemFactory *factory, GtkListItem *listitem)
{
	GtkWidget *label = gtk_list_item_get_child(listitem);
	TestColItem *item = gtk_list_item_get_item(GTK_LIST_ITEM(listitem));
	const char *string = item->str2;
	g_print("value is %s\n",string); // Show on std out
	gtk_label_set_text(GTK_LABEL (label), string);
}

// Create the custom object
static TestColItem * testcol_item_new(const char *str1, const char *str2)
{
	TestColItem  *item = g_object_new(TESTCOL_TYPE_ITEM, NULL);
	item->str1 = g_strdup(str1);
	item->str2 = g_strdup(str2);
	return item;
}	

// Activate
static void activate (GtkApplication* app, gpointer user_data)
{
	// Create and populate custom list model
	GListStore *store = g_list_store_new(G_TYPE_OBJECT); 
	g_list_store_append(store,testcol_item_new("abc1","ghi1"));
	g_list_store_append(store,testcol_item_new("def2","aaa2"));
	g_list_store_append(store,testcol_item_new("aaa3","jkl3"));

	// Set Windows in child scrolled window
	GtkWidget *window = gtk_application_window_new (app);
	gtk_window_set_title (GTK_WINDOW (window), "Test");
	gtk_window_set_default_size (GTK_WINDOW (window), 200, 200);
	GtkWidget *scrolled_window = gtk_scrolled_window_new (); 
	gtk_window_set_child (GTK_WINDOW(window), scrolled_window);

	// Add column view.  Put in scrolled window.  
	GtkWidget *cv = gtk_column_view_new(NULL); // Null for now want to add sorter
	gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scrolled_window),cv);
	gtk_column_view_set_show_column_separators (GTK_COLUMN_VIEW (cv),TRUE);

	// Factory setup and bind for column 1 
	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_col1_cb),NULL);
	GtkColumnViewColumn *column = gtk_column_view_column_new("Col1", factory);
	GtkSorter *sorter = GTK_SORTER (gtk_string_sorter_new (gtk_property_expression_new (TESTCOL_TYPE_ITEM, NULL, "str1")));
	gtk_column_view_column_set_sorter (column, GTK_SORTER (sorter));
	gtk_column_view_append_column (GTK_COLUMN_VIEW (cv), column);

	// Factory setup and bind for column 2
	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_col2_cb),NULL);
	column = gtk_column_view_column_new("Col2", factory);
	sorter = GTK_SORTER (gtk_string_sorter_new (gtk_property_expression_new (TESTCOL_TYPE_ITEM, NULL, "str2")));
	gtk_column_view_column_set_sorter (column, GTK_SORTER (sorter));
	gtk_column_view_append_column (GTK_COLUMN_VIEW (cv), column);

	// Setup sort model then overlay with with columnview
	sorter = g_object_ref (gtk_column_view_get_sorter (GTK_COLUMN_VIEW (cv)));
    GtkSortListModel *model = gtk_sort_list_model_new (G_LIST_MODEL (store), sorter);
    GtkSingleSelection *selection = gtk_single_selection_new (G_LIST_MODEL (model));
    gtk_single_selection_set_autoselect (selection, TRUE);
    gtk_column_view_set_model (GTK_COLUMN_VIEW (cv), GTK_SELECTION_MODEL (selection));

	// Show window
	gtk_window_present (GTK_WINDOW (window));
}

// Init application
int main (int argc, char **argv)
{
	GtkApplication *app;
	int status;

	app = gtk_application_new ("org.gtk.example", G_APPLICATION_DEFAULT_FLAGS);
	g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
	status = g_application_run (G_APPLICATION (app), argc, argv);
	g_object_unref (app);

	return status;
}

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.