子类化
GObject 在很大程度上依赖于继承。因此,如果我们想创建一个自定义的 GObject,通过子类化来实现是很合理的。 让我们用一个自定义按钮来替换 "Hello World!" 应用程序中的按钮,看看它是如何工作的。 首先,我们需要创建一个实现结构体来保存状态并重写虚方法。
文件名:listings/g_object_subclassing/1/custom_button/imp.rs
use gtk::glib;
use gtk::subclass::prelude::*;
// Object holding the state
#[derive(Default)]
pub struct CustomButton;
// 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
impl ObjectImpl for CustomButton {}
// Trait shared by all widgets
impl WidgetImpl for CustomButton {}
// Trait shared by all buttons
impl ButtonImpl for CustomButton {}
有关子类化的说明请参见 ObjectSubclass
.
NAME
应由 crate-name 和 object-name 组成,以避免名称冲突。这里应使用大驼峰命名法。Type
指的是之后将创建的实际 GObject。ParentType
是我们继承的 GObject。
之后,我们就可以选择重写我们祖先的虚方法。 由于我们现在只想拥有一个普通按钮,所以我们什么也不重写。 不过,我们仍然需要添加空的 impl
。 接下来,我们将描述我们自定义的 GObject 的公共接口。
文件名:listings/g_object_subclassing/1/custom_button/mod.rs
mod imp;
use glib::Object;
use gtk::glib;
glib::wrapper! {
pub struct CustomButton(ObjectSubclass<imp::CustomButton>)
@extends gtk::Button, gtk::Widget,
@implements gtk::Accessible, gtk::Actionable, gtk::Buildable, gtk::ConstraintTarget;
}
impl CustomButton {
pub fn new() -> Self {
Object::builder().build()
}
pub fn with_label(label: &str) -> Self {
Object::builder().property("label", label).build()
}
}
impl Default for CustomButton {
fn default() -> Self {
Self::new()
}
}
glib::wrapper!
实现了与 ParentType
相同的 trait。 理论上,这意味着 ParentType
也是我们唯一需要指定的。 不幸的是,还没有人找到好的方法来做到这一点。 这就是为什么到目前为止,在 Rust 中对 GObjects 进行子类化需要提及 GObject
和 GInitiallyUnowned
以外的所有祖先和接口。 对于 gtk::Button
,我们可以在 GTK4 的相应 文档页 中查找其祖先和接口。
完成这些步骤后,我们就可以用自定义按钮( CustomButton
)替换 gtk::Button
了。
文件名:listings/g_object_subclassing/1/main.rs
mod custom_button;
use custom_button::CustomButton;
use gtk::prelude::*;
use gtk::{glib, Application, ApplicationWindow};
const APP_ID: &str = "org.gtk_rs.GObjectSubclassing1";
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::with_label("Press me!");
button.set_margin_top(12);
button.set_margin_bottom(12);
button.set_margin_start(12);
button.set_margin_end(12);
// Connect to "clicked" signal of `button`
button.connect_clicked(move |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();
}
用两个结构体来描述对象是 C 语言中定义 GObject 的一种特殊方式。
imp::CustomButton
处理 GObject 的状态和重写的虚方法。CustomButton
从已实现的 trait 和添加的方法中确定要暴露的方法。
添加功能
我们可以用 CustomButton
代替 gtk::Button
。 这很酷,但在实际应用中不太有吸引力。 尽管收益为零,但毕竟提供了不少代码模板。
所以,让我们把它变得更有趣一些吧!gtk::Button
并不会保存太多状态,但我们可以让 CustomButton
保存一个数字。
文件名:listings/g_object_subclassing/2/custom_button/imp.rs
use std::cell::Cell;
use gtk::glib;
use gtk::prelude::*;
use gtk::subclass::prelude::*;
// Object holding the state
#[derive(Default)]
pub struct CustomButton {
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
impl ObjectImpl for CustomButton {
fn constructed(&self) {
self.parent_constructed();
self.obj().set_label(&self.number.get().to_string());
}
}
// Trait shared by all widgets
impl WidgetImpl for CustomButton {}
// Trait shared by all buttons
impl ButtonImpl for CustomButton {
fn clicked(&self) {
self.number.set(self.number.get() + 1);
self.obj().set_label(&self.number.get().to_string())
}
}
我们在 ObjectImpl
中重写了 constructed
,这样按钮的标签就会初始化为number
. 我们还重写了 ButtonImpl
中的 clicked
,这样每次点击都会使number
自增并更新标签。
文件名:listings/g_object_subclassing/2/main.rs
mod custom_button;
use custom_button::CustomButton;
use gtk::prelude::*;
use gtk::{glib, Application, ApplicationWindow};
const APP_ID: &str = "org.gtk_rs.GObjectSubclassing2";
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);
// Create a window
let window = ApplicationWindow::builder()
.application(app)
.title("My GTK App")
.child(&button)
.build();
// Present window
window.present();
}
在 build_ui
中,我们不再调用 connect_clicked
,仅此而已。 重新构建后,应用程序出现了标签为 "0" 的自定义按钮。 每次点击按钮,标签显示的数字都会增加 1。
那么,我们什么时候需要从 GObject 继承呢?
- 我们想使用某个控件,但要添加状态和重写虚函数。
- 我们想将 Rust 对象传递给函数,但该函数需要一个 GObject。
- 我们想向对象添加属性或信号。