How can I access the value of a GtkSpinButton?

As I’m slowly figuring out how to build an application with gtk-rs, I got stuck on the following: I implemented a custom GObject NewMeasurementWindow which contains a spin button as a child for the user to input a number standing for a temperature.

use glib::subclass::InitializingObject;
use glib::Properties;
use gtk::subclass::prelude::*;
use gtk::{glib, CompositeTemplate};
use gtk::{prelude::*, SpinButton};

// Object holding the state
#[derive(Properties, CompositeTemplate, Default)]
#[properties(wrapper_type = super::NewMeasurementWindow)]
#[template(resource = "/org/codeberg/jdw/Thermometer/NewMeasurementWindow.ui")]
pub struct NewMeasurementWindow {
    #[template_child]
    pub temperature_spinbutton: TemplateChild<SpinButton>,
}

// The central trait for subclassing a GObject
#[glib::object_subclass]
impl ObjectSubclass for NewMeasurementWindow {
    // `NAME` needs to match `class` attribute of template
    const NAME: &'static str = "ThermometerNewMeasurementWindow";
    type Type = super::NewMeasurementWindow;
    type ParentType = gtk::Window;

    fn class_init(klass: &mut Self::Class) {
        klass.bind_template();
    }

    fn instance_init(obj: &InitializingObject<Self>) {
        obj.init_template();
    }
}

// Trait shared by all GObjects
#[glib::derived_properties]
impl ObjectImpl for NewMeasurementWindow {
    fn constructed(&self) {
        println!(
            "Value of Spin Button: {}",
            self.temperature_spinbutton.value()
        );

        // Call "constructed" on parent
        self.parent_constructed();
    }
}

// Trait shared by all widgets
impl WidgetImpl for NewMeasurementWindow {}

// Trait shared by all windows
impl WindowImpl for NewMeasurementWindow {}

In the constructed method I’m accessing the value of the SpinButton. However I can’t figure out how to access the value outside the implementation module. For example the save_measurement method

mod imp;

use glib::Object;
use gtk::{gio, glib, prelude::GtkWindowExt};

use crate::main_window::MainWindow;

glib::wrapper! {
    pub struct NewMeasurementWindow(ObjectSubclass<imp::NewMeasurementWindow>)
        @extends gtk::Window, gtk::Widget,
        @implements gio::ActionGroup, gio::ActionMap, gtk::Accessible, gtk::Buildable,
                    gtk::ConstraintTarget, gtk::Native, gtk::Root, gtk::ShortcutManager;
}

impl NewMeasurementWindow {
    pub fn new(window: &MainWindow) -> Self {
        // Create new window
        Object::builder().property("transient-for", window).build()
    }

    pub fn save_measurement(&self) {
        // Right now it only prints, needs to be modified to save to database instead
        let temperature = self.temperature_spinbutton.value();
        println!("Temperature: {}", temperature);
        self.close();
    }
}

gives me the following error:

error[E0609]: no field `temperature_spinbutton` on type `&new_measurement_window::NewMeasurementWindow`
  --> src/new_measurement_window/mod.rs:23:32
   |
23 |         let temperature = self.temperature_spinbutton.value();
   |                                ^^^^^^^^^^^^^^^^^^^^^^ unknown field
   |
   = note: available fields are: `inner`, `phantom`


I would greatly appreciate any help!

Use self.imp().temperature_spinbutton.value(). temperature_spinbutton needs to be pub.

Conceptually, I’d recommend to avoid direct access to UI widgetts from the template like this, tho, and instead use data binding to bind the desired property of the UI widget to a property of the template class.

2 Likes

Dear Sebastian,

thanks a ton for your help! self.imp().temperature_spinbutton.value() does indeed work as promised. Could you maybe point me to some explanation of what exactly the imp() method is? Where and how is it defined? I didn’t find any explanation in the gtk-rs book. The hints of the language server
image
seem to suggest that it takes a reference to the NewMeasurementWindow and again returns a reference to (the same?) NewMeasurementWindow?

I only found this thread which seems helpful, but is a bit over my head currently.

These are two different types. One is the GObject wrapper struct, the other your implementation. They have (but don’t need to have) the same name in your code, but different paths: one is in the imp submodule, the other is a level above.

You use .imp() to get the latter from the former, and .obj() for the other direction.

The Subclassing chapter has some background about the use of two structs for gobjects in Rust:

Describing objects with two structs is a peculiarity coming from how GObjects are defined in C. imp::CustomButton handles the state of the GObject and the overridden virtual methods. CustomButton determines the exposed methods from the implemented traits and added methods.

For more background you’ll need to read the GObject documentation itself, starting with the Type System Concepts chapter.

Without a solid background in GObject you’ll likely have a hard time using Gtk in Rust, as the GObject type system is pervasively used by Gtk and all related libraries, and the Rust bindings cannot hide this, nor—in the absence of an object system in Rust itself—directly map GObject types to Rust types as other language bindings (e.g. GJS, Python) do.

1 Like

Thank you very much for the various pointers to the documentation, I’ll make sure to dig deeper in there! It’s just a bit hard to get started :slight_smile:

Just one more question: Above you write

Conceptually, I’d recommend to avoid direct access to UI widgetts from the template like this, tho, and instead use data binding to bind the desired property of the UI widget to a property of the template class.

Could you maybe give a short example how that would be done in the case above? I know I can add a property to my custom widget like this:

#[derive(Properties, CompositeTemplate, Default)]
#[properties(wrapper_type = super::NewMeasurementWindow)]
#[template(resource = "/org/codeberg/jdw/Thermometer/NewMeasurementWindow.ui")]
pub struct NewMeasurementWindow {
    #[template_child]
    pub temperature_spinbutton: TemplateChild<SpinButton>,
    #[property(get, set)]
    temperature: Cell<f64>,
}

See Gtk.Builder

Or take a look at simple Rust applications, eg Authenticator or Decoder on Gnome Gitlab.