Signals
GObject signals are a system for registering callbacks for specific events. For example, if we press on a button, the "clicked" signal will be emitted. The signal then takes care that all the registered callbacks will be executed.
gtk-rs
provides convenience methods for registering callbacks.
In our "Hello World" example we connected the "clicked" signal to a closure which sets the label of the button to "Hello World" as soon as it gets called.
Filename: listings/hello_world/3/main.rs
use gtk::prelude::*;
use gtk::{glib, Application, ApplicationWindow, Button};
const APP_ID: &str = "org.gtk_rs.HelloWorld3";
fn main() -> glib::ExitCode {
// Create a new application
let app = Application::builder().application_id(APP_ID).build();
// Connect to "activate" signal of `app`
app.connect_activate(build_ui);
// Run the application
app.run()
}
fn build_ui(app: &Application) {
// Create a button with label and margins
let button = Button::builder()
.label("Press me!")
.margin_top(12)
.margin_bottom(12)
.margin_start(12)
.margin_end(12)
.build();
// Connect to "clicked" signal of `button`
button.connect_clicked(|button| {
// Set the label to "Hello World!" after the button has been clicked on
button.set_label("Hello World!");
});
// Create a window
let window = ApplicationWindow::builder()
.application(app)
.title("My GTK App")
.child(&button)
.build();
// Present window
window.present();
}
If we wanted to, we could have connected to it with the generic connect_closure
method and the glib::closure_local!
macro.
Filename: listings/g_object_signals/1/main.rs
use glib::closure_local;
use gtk::prelude::*;
use gtk::{glib, Application, ApplicationWindow, Button};
const APP_ID: &str = "org.gtk_rs.GObjectSignals1";
fn main() -> glib::ExitCode {
// Create a new application
let app = Application::builder().application_id(APP_ID).build();
// Connect to "activate" signal of `app`
app.connect_activate(build_ui);
// Run the application
app.run()
}
fn build_ui(app: &Application) {
// Create a button
let button = Button::builder()
.label("Press me!")
.margin_top(12)
.margin_bottom(12)
.margin_start(12)
.margin_end(12)
.build();
// Connect to "clicked" signal of `button`
button.connect_closure(
"clicked",
false,
closure_local!(move |button: Button| {
// Set the label to "Hello World!" after the button has been clicked on
button.set_label("Hello World!");
}),
);
// Create a window
let window = ApplicationWindow::builder()
.application(app)
.title("My GTK App")
.child(&button)
.build();
// Present window
window.present();
}
The advantage of connect_closure
is that it also works with custom signals.
If you need to clone reference counted objects into your closure you don't have to wrap it within another
clone!
macro.closure_local!
accepts the same syntax for creating strong/weak references, plus a watch feature that automatically disconnects the closure once the watched object is dropped.
Adding Signals to Custom GObjects
Let's see how we can create our own signals.
Again we do that by extending our CustomButton
.
First we override the signals
method in ObjectImpl
.
In order to do that, we need to lazily initialize a static item SIGNALS
.
std::sync::OnceLock
ensures that SIGNALS
will only be initialized once.
Filename: listings/g_object_signals/2/custom_button/imp.rs
use std::cell::Cell;
use std::sync::OnceLock;
use glib::subclass::Signal;
use glib::Properties;
use gtk::glib;
use gtk::prelude::*;
use gtk::subclass::prelude::*;
// Object holding the state
#[derive(Properties, Default)]
#[properties(wrapper_type = super::CustomButton)]
pub struct CustomButton {
#[property(get, set)]
number: Cell<i32>,
}
// The central trait for subclassing a GObject
#[glib::object_subclass]
impl ObjectSubclass for CustomButton {
const NAME: &'static str = "MyGtkAppCustomButton";
type Type = super::CustomButton;
type ParentType = gtk::Button;
}
// Trait shared by all GObjects
#[glib::derived_properties]
impl ObjectImpl for CustomButton {
fn signals() -> &'static [Signal] {
static SIGNALS: OnceLock<Vec<Signal>> = OnceLock::new();
SIGNALS.get_or_init(|| {
vec![Signal::builder("max-number-reached")
.param_types([i32::static_type()])
.build()]
})
}
fn constructed(&self) {
self.parent_constructed();
// Bind label to number
// `SYNC_CREATE` ensures that the label will be immediately set
let obj = self.obj();
obj.bind_property("number", obj.as_ref(), "label")
.sync_create()
.build();
}
}
// Trait shared by all widgets
impl WidgetImpl for CustomButton {}
static MAX_NUMBER: i32 = 8;
// Trait shared by all buttons
impl ButtonImpl for CustomButton {
fn clicked(&self) {
let incremented_number = self.obj().number() + 1;
let obj = self.obj();
// If `number` reached `MAX_NUMBER`,
// emit "max-number-reached" signal and set `number` back to 0
if incremented_number == MAX_NUMBER {
obj.emit_by_name::<()>("max-number-reached", &[&incremented_number]);
obj.set_number(0);
} else {
obj.set_number(incremented_number);
}
}
}
The signals
method is responsible for defining a set of signals.
In our case, we only create a single signal named "max-number-reached".
When naming our signal, we make sure to do that in kebab-case.
When emitted, it sends a single i32
value.
We want the signal to be emitted, whenever number
reaches MAX_NUMBER
.
Together with the signal we send the value number
currently holds.
After we did that, we set number
back to 0.
Filename: listings/g_object_signals/2/custom_button/imp.rs
use std::cell::Cell;
use std::sync::OnceLock;
use glib::subclass::Signal;
use glib::Properties;
use gtk::glib;
use gtk::prelude::*;
use gtk::subclass::prelude::*;
// Object holding the state
#[derive(Properties, Default)]
#[properties(wrapper_type = super::CustomButton)]
pub struct CustomButton {
#[property(get, set)]
number: Cell<i32>,
}
// The central trait for subclassing a GObject
#[glib::object_subclass]
impl ObjectSubclass for CustomButton {
const NAME: &'static str = "MyGtkAppCustomButton";
type Type = super::CustomButton;
type ParentType = gtk::Button;
}
// Trait shared by all GObjects
#[glib::derived_properties]
impl ObjectImpl for CustomButton {
fn signals() -> &'static [Signal] {
static SIGNALS: OnceLock<Vec<Signal>> = OnceLock::new();
SIGNALS.get_or_init(|| {
vec![Signal::builder("max-number-reached")
.param_types([i32::static_type()])
.build()]
})
}
fn constructed(&self) {
self.parent_constructed();
// Bind label to number
// `SYNC_CREATE` ensures that the label will be immediately set
let obj = self.obj();
obj.bind_property("number", obj.as_ref(), "label")
.sync_create()
.build();
}
}
// Trait shared by all widgets
impl WidgetImpl for CustomButton {}
static MAX_NUMBER: i32 = 8;
// Trait shared by all buttons
impl ButtonImpl for CustomButton {
fn clicked(&self) {
let incremented_number = self.obj().number() + 1;
let obj = self.obj();
// If `number` reached `MAX_NUMBER`,
// emit "max-number-reached" signal and set `number` back to 0
if incremented_number == MAX_NUMBER {
obj.emit_by_name::<()>("max-number-reached", &[&incremented_number]);
obj.set_number(0);
} else {
obj.set_number(incremented_number);
}
}
}
If we now press on the button, the number of its label increases until it reaches MAX_NUMBER
.
Then it emits the "max-number-reached" signal which we can nicely connect to.
Whenever we now receive the "max-number-reached" signal, the accompanying number is printed to standard output.
Filename: listings/g_object_signals/2/main.rs
mod custom_button;
use custom_button::CustomButton;
use glib::closure_local;
use gtk::prelude::*;
use gtk::{glib, Application, ApplicationWindow};
const APP_ID: &str = "org.gtk_rs.GObjectSignals2";
fn main() -> glib::ExitCode {
// Create a new application
let app = Application::builder().application_id(APP_ID).build();
// Connect to "activate" signal of `app`
app.connect_activate(build_ui);
// Run the application
app.run()
}
fn build_ui(app: &Application) {
// Create a button
let button = CustomButton::new();
button.set_margin_top(12);
button.set_margin_bottom(12);
button.set_margin_start(12);
button.set_margin_end(12);
button.connect_closure(
"max-number-reached",
false,
closure_local!(move |_button: CustomButton, number: i32| {
println!("The maximum number {} has been reached", number);
}),
);
// Create a window
let window = ApplicationWindow::builder()
.application(app)
.title("My GTK App")
.child(&button)
.build();
// Present window
window.present();
}
You now know how to connect to every kind of signal and how to create your own. Custom signals are especially useful, if you want to notify consumers of your GObject that a certain event occurred.