How to create radio buttons and bind them to an action

I think that GTK documentation is not clear about the question in the title, and it has reasons since there is no only one way to do it, this post will try to solve that by showing an example with code.

Imagine you want to change the UI depending on the value of a group of radio buttons, in this case, I will show the property action approach to solve this, you will have a template like this:

using Gtk 4.0;

template $MyBin: Bin {
  Box {
    Label {
      label: bind template.message;
    }

    CheckButton radio_1 {
      group: radio_2;
      action-name: "my-bin.message";
      action-target: "Showing message 1";
    }

    CheckButton radio_2 {
      action-name: "my-bin.message";
      action-target: "Showing message 2";
    }
  }
}

This template will show the messages “Showing message 1” or “Showing message 2”, depending on which radio button is selected.

Following with the code, we have to install the property action my-bin.message, I’m gonna show the Rust code to handle this, it should be pretty similar in other language bindings:


/*
You have to write the public struct wrapper, it will depend on the
language binding you are using, in Rust you use the glib::wrapper! macro
*/
glib::wrapper! {
   // TODO
}

mod imp {
    #[derive(Default, gtk::CompositeTemplate, glib::Properties)]
    #[template(resource = "/com/example/my_bin.ui")]
    #[properties(wrapper_type = super::MyBin)]
    pub struct MyBin {
        #[property(get, set)]
        message: RefCell<String>, // This is the property referenced in the template "bind template.message"
    }

    #[glib::object_subclass]
    impl ObjectSubclass for MyBin {
        const NAME: &'static str = "MyBin";

        type Type = super::MyBin;

        type ParentType = gtk::Bin;

        // VERY important method, in other OO language bindings you should override this method with the equivalent code
        fn class_init(klass: &mut Self::Class) {
            let action_name = "my-bin.message";
            let property_name = "message";

            // We install the property action
            klass.install_property_action(action_name, property_name);

            klass.bind_template();
        }

        fn instance_init(obj: &glib::subclass::types::InitializingObject) {
            obj.init_template();
        }
    }

    /* ... Here goes the rest of classes implementations for MyBin struct ... */
}

Troubleshooting:

It behaves really odd when you put the action-target in the template, if it happens to be a number, it will convert it integer type, even though the installed action type is string, you will get the following error:

Gtk-WARNING **: 12:40:43.895: actionhelper: action my-bin.message can't be activated due to parameter type mismatch (parameter type s, target type i)

You solve this by setting the action target in the constructed() method, something like this:

fn constructed(&self) {
    // In other languages may be other ways to obtain template child objects
    self.radio_1.set_action_target(Some("Showing message 1"));
    self.radio_2.set_action_target(Some("Showing message 2"));
}

References:

A very important point is that the binded action must have an state (stateless actions won’t work and will turn your radio button into a check button), that’s why you can use any GAction implementation that has the state enabled, not just the method WidgetClass.install_property_action.

As an example you could use Widget.insert_action_group() method for binding a GActionGroup, in case you want to reuse actions across your application.