Connect signals of custom widgets in the UI file in Python+GTK4

I am building a GTK4 application in Python. My code base consists of custom widgets with .py and .ui files of the same name.

I have built a simple custom widget derived from Gtk.Button, let’s call it MyButton. It serves as a template for multiple buttons in my applications. Its users will listen to its clicked signal to execute some logic. I would like to connect this clicked signal to an action in the corresponding .ui file.

However, I noticed that when doing so for custom widgets, the signal will not get caught, while connecting it in the .py file would work.

Consider the following example code for my button:

<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <requires lib="gtk" version="4.0"/>
  
  <template class="MyButton" parent="GtkButton">
    <property name="width-request">32</property>
    <property name="height-request">32</property>
    <property name="halign">center</property>
    <property name="valign">center</property>
  </template>
</interface>

and

import os
import gi

gi.require_version('Gtk', '4.0')

from gi.repository import Gtk


@Gtk.Template(filename=os.path.join(os.path.dirname(__file__), 'my_button.ui'))
class MyButton(Gtk.Button):
    
    __gtype_name__ = 'MyButton'
    
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.init_template()

Now, I want to use this button in my main application:

<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <requires lib="gtk" version="4.0"/>
  
  <template class="MainWindow" parent="GtkApplicationWindow">
    <property name="title">Custom Widget Test</property>
    <property name="default-width">400</property>
    <property name="default-height">300</property>
    
    <child>
      <object class="GtkBox" id="main_box">
        <property name="orientation">vertical</property>
        <property name="spacing">20</property>
        <property name="halign">center</property>
        <property name="valign">center</property>
        
        <child>
          <object class="MyButton" id="my_button">
            <property name="tooltip-text">Add new item</property>
            <property name="icon-name">list-add-symbolic</property>
            <signal name="clicked" handler="on_my_button_clicked"/>
          </object>
        </child>
      </object>
    </child>
  </template>
</interface>

and

import sys
import os
import gi

gi.require_version('Gtk', '4.0')

from gi.repository import Gtk, Gio
from .my_button import MyButton

MyButton.__gtype__  # Force registration of the custom widget type


@Gtk.Template(filename=os.path.join(os.path.dirname(__file__), 'main.ui'))
class MainWindow(Gtk.ApplicationWindow):
    
    __gtype_name__ = 'MainWindow'
    
    MY_button = Gtk.Template.Child()
    
    def __init__(self, app):
        super().__init__(application=app)
        self.init_template()
        
    def on_my_button_clicked(self, button):
        print(f"My button was clicked!")


class TestApp(Gtk.Application):
    
    def __init__(self):
        super().__init__(
            application_id="com.example.MyButtonTest",
            flags=Gio.ApplicationFlags.DEFAULT_FLAGS
        )
        
        self.window = None
    
    def do_activate(self):
        if not self.window:
            self.window = MainWindow(self)
        
        self.window.present()
    
    def do_startup(self):
        Gtk.Application.do_startup(self)
        

def main():
    app = TestApp()
    return app.run(sys.argv)


if __name__ == "__main__":
    exit_code = main()
    sys.exit(exit_code)

When you run this, you would notice that nothing happens when clicking the button. However, when changing MainWindow.__init__ to

    def __init__(self, app):
        super().__init__(application=app)
        self.init_template()
        self.my_button.connect('clicked', self.on_my_button_clicked)

it will work. What can I do to make it work in the .ui file, too?

You’re missing the Gtk.Template.Callback decorator on your callback.

You can find the documentation for Gtk.Template on the PyGObject website.

Thank you very much, this did solve my problem!

    @Gtk.Template.Callback()
    def on_my_button_clicked(self, button):
        print(f"My button was clicked!")