[gtk-rs]Can't get root or parent of a custom Widget using Composite Template

Hello, I have an issue and I am looking for help with it please.

I am using gtk-rs and the Composite Template feature.

I create 2 custom widgets:

  • MainWindow subclass of adw::ApplicationWindow
  • State subclass of gtk::Widget: This widget has only data fields and has no graphic representation. Hence the choice of subclassing only gtk::Widget because it is the smallest subset I need (or so I think).

Problem: The State widget’s instance has no root or parent.

State Widget code:

#[derive(Debug, Default, CompositeTemplate)]
#[template(resource = "/ui/state.ui")]
pub struct State {
        // Some data fields
}

#[glib::object_subclass]
   impl ObjectSubclass for State {
   const NAME: &'static str = "State";
   type Type = super::State;
   type ParentType = gtk::Widget;

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

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

impl ObjectImpl for State {
    fn constructed(&self) {
        self.parent_constructed();
    }
}

impl WidgetImpl for State {}
}

glib::wrapper! {
    pub struct State(ObjectSubclass<imp::State>)
        @extends gtk::Widget,
        @implements gtk::Accessible;
}

impl Default for State {
    fn default() -> Self {
        Self::new()
    }
}
impl State {
   pub fn foo(&self) {
	// This line panic on a None value.
	let root = self.root().unwrap();
	let mainwindow = root.downcast::<MainWindow>().unwrap();
	// Do something...
   }
}

XML definition of State (Complete there is nothing else)

<?xml version='1.0' encoding='UTF-8'?>
<interface>
  <template class="State" parent="GtkWidget">
  </template>
</interface>

A call to State::foo() function returns a panic:

thread ‘main’ panicked at ‘called Option::unwrap() on a None value’.

To correct this, I set State’s parent myself in his parent construction function. MainWindow is the parent and root Widget.
Here is MainWindow widget subclass of adw::ApplicationWindow.

MainWindow widget code:

#[derive(Debug, Default, CompositeTemplate)]
#[template(resource = "/ui/window.ui")]
pub struct MainWindow {
    #[template_child]
    pub state: TemplateChild<State>,
    // Others fields with template widgets
}
    
#[glib::object_subclass]
impl ObjectSubclass for MainWindow {
    const NAME: &'static str = "MainAppWindow";
    type Type = super::MainWindow;
    type ParentType = adw::ApplicationWindow;

    fn class_init(klass: &mut Self::Class) {
        State::ensure_type();

        klass.bind_template();
        klass.bind_template_instance_callbacks();
    }

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

impl ObjectImpl for MainWindow {
    fn constructed(&self) {
        self.parent_constructed();
        // For unknown reasons, at creation time, the State Widget has no root or parent widget.
        // We set MainWindow as the parent for state Widget
        let obj = self.obj();
        self.obj().imp().state.set_parent(&*obj);
    }
        
    // Other implementations
 }

 #[gtk::template_callbacks]
 impl MainWindow {
    
    #[template_callback]
    fn file_open_response(&self, res: i32) {
	// Some code...
        let state = &self.imp().state;    
        state.foo();
    }
 }

XML definition of MainWindow:

<?xml version='1.0' encoding='UTF-8'?>
<interface>
  <template class="MainAppWindow" parent="AdwApplicationWindow">
    <object class="State" id="state"/>
    <child>
       <object class="AdwToastOverlay" id="something">
       ...
  </template>
</interface>

Having set the root of State myself, the application doesn’t panic anymore but I get another gtk warning:

Trying to snapshot State 0x55b3795d4050 without a current allocation.

I suspect this has something to do with size allocation for my custom State widget but I am not sure and I don’t know how I can get rid of it.

My questions are:

  • Why MainApplication widget isn’t automatically set as the State’s parent or root as in the XML file? What did I miss?
  • What is the meaning of the warning: Trying to snapshot State 0x55b3795d4050 without a current allocation. How do I create a custom gtk::Widget without getting this warning?

Thank you for spending time on my problem :wink:

Widgets are only for things that have graphic representation. If this state is for the whole window then you can just put the data fields in the MainWindow class itself. Or there are a few other things you could try:

  • If the state is global to the app, put the data fields in your adw::Application subclass.
  • Have State as a plain Rust object (not a widget subclass) and pass around Rc<State> to where you need it.
  • Have a State singleton that has MainWindow as a field and sends updates to the window.
  • Make State only a subclass of glib::Object. This way you can control the GUI with signals and properties.

It’s really up to how you want to structure the data flow for your app.

1 Like

Thanks a lot.

Make State only a subclass of glib::Object. This way you can control the GUI with signals and properties.

This is what I needed. I’ll use properties and signals between this State GObject and various Widget to display information to the user.

Do I understand well Gtk/Gtk-rs here?

Subclassing a GObject and not gtk::Widget:

  1. Doesn’t require an allocation of size because it is not a “graphic” object in the UI like a gtk::Widget. Doesn’t require a ManagerLayout either for the same reason.

  2. Doesn’t implement gtk::Widget traits so:

    1. Can’t be a Composite Template Child
    2. Can’t be part of a Widget hierarchy.
    3. Has no gtk::WidgetExt::root() method to get the root Window reference.
  3. Communicate with Widgets through signals and properties only.

  4. Is it still possible to include the subclassed GObject (like State) in the XML definition of a subclassed Widget and define signal, handler and properties? All without using the Composite Template feature?

    <object class="State">
         <property name="my_property">true</property>
    </object>
        ...
    

Thank you.

Yes, almost all that is correct, except objects can be composite template children if the widget knows how to handle it. To do this you would implement gtk::Buildable on the widget and check the type of the child, see the custom_buildable example for how to do this. You can also implement Buildable on the object itself to customize its XML tags. This is how you can do things like put EventControllers as widget children in the builder XML even though an event controller is not a widget.

Note that it also is fine for a plain object to hold references to widgets for various reasons, if you want the object to control a widget somehow. So for example for an object that controls a window, instead of calling root you would just hold a reference (possibly a weak reference) to the window inside the object and have it do things to the window in response to signals, etc. If just binding some properties and signals to the window then this would happen automatically so it’s only necessary if doing something more complicated like if you want to call rust/C methods.

Thank you.

Your answer gave me a picture of gtk-rs mechanisms like no other documentation has to this point. It is really, really nice.

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