信号
GObject 信号是一个为特定事件注册回调的系统。 例如,如果我们按下一个按钮,"clicked(点击)"信号就会发出。 然后,该信号将负责执行所有已注册的回调函数。
gtk-rs
提供了注册回调的便捷方法。 在我们的 "Hello World" 示例中,我们将 "clicked" 信号连接到一个闭包,一旦它被调用,就会将按钮的标签设置为 "Hello World"。
文件名: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();
}
如果我们愿意,可以使用通用的 connect_closure
方法和 glib::closure_local!
宏来连接它。
文件名: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();
}
connect_closure
的优势在于它还能与自定义信号一起使用。
如果您需要在闭包中克隆引用计数对象,则不必将其封装在另一个
clone!
宏中。closure_local!
接受与创建强/弱引用相同的语法,并具有监视功能,一旦监视对象被删除,闭包就会自动断开连接。
向自定义 GObject 添加信号
让我们看看如何创建自己的信号。 现在,让我们可以来扩展 CustomButton
。 首先,我们覆盖 ObjectImpl
中的 signals
方法。 std::sync::OnceLock
可以确保 SIGNALS
只被初始化一次。
文件名: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);
}
}
}
signals
方法负责定义一组信号。 在本例中,我们只创建了一个名为 "max-number-reached" 的信号。 在命名信号时,我们确保使用短横线命名法(kebab-case)进行命名。 当信号发出时,它会发送一个 i32
值。
我们希望在number
达到 MAX_NUMBER
时发出信号。 在发送信号的同时,我们还将发送 number
当前的值。 之后,我们将number
重置为 0.
文件名: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);
}
}
}
如果我们现在按下按钮,其标签上的数字就会增加,直到达到 MAX_NUMBER
. 然后它会发出 "max-number-reached" 信号,我们可以很好地连接到该信号。 现在,每当我们收到 "max-number-reached" 信号时,相应的数字就会打印到标准输出中。
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();
}
现在,您已经知道如何连接各种信号以及如何创建自己的信号。 如果您想通知 GObject 的消费者发生了某一事件,自定义信号尤其有用。