如何在gtk-rs 0.15.0(GTK-3)中编写自定义Container?求示例代码
如何用gtk-rs 0.15.0(GTK3)编写自定义Container
核心逻辑
GTK3自定义Container的核心是实现Container trait的关键虚函数,重点处理尺寸请求与空间分配:
get_preferred_width/get_preferred_height:告知GTK容器自身所需的最小、自然尺寸size_allocate:实际为子组件分配空间,这是实现2/3-1/3固定比例的核心环节
示例代码(固定比例水平容器)
以下是适配你需求的自定义Container实现:左侧子组件占父容器宽度2/3,右侧占1/3,高度取两个子组件的最大值,彻底避免手动设尺寸导致的抖动问题。
use gtk::prelude::*; use gtk::{Container, ContainerExt, Widget, WidgetExt, ContainerClass, WidgetClass}; use glib::subclass::prelude::*; use std::cell::RefCell; // 自定义容器结构体,存储两个子组件(适配2/3-1/3的布局需求) #[derive(Default)] struct TwoThirdsContainer { children: RefCell<Option<(Widget, Widget)>>, } // 实现ObjectSubclass trait #[glib::object_subclass] impl ObjectSubclass for TwoThirdsContainer { const NAME: &'static str = "TwoThirdsContainer"; type Type = super::TwoThirdsContainer; type ParentType = Container; fn class_init(klass: &mut Self::Class) { // 设置固定布局管理器,告诉GTK我们将完全手动控制子组件尺寸分配 klass.set_layout_manager_type::<gtk::FixedLayout>(); } } // 实现Object trait impl ObjectImpl for TwoThirdsContainer {} // 实现Widget trait,处理尺寸计算与空间分配 impl WidgetImpl for TwoThirdsContainer { // 计算容器所需的最小、自然宽度 fn get_preferred_width(&self, widget: &Self::Type) -> (i32, i32) { let (child1, child2) = match *self.children.borrow() { Some((ref c1, ref c2)) => (c1, c2), None => return (0, 0), }; let (min_w1, nat_w1) = child1.get_preferred_width(); let (min_w2, nat_w2) = child2.get_preferred_width(); (min_w1 + min_w2, nat_w1 + nat_w2) } // 按给定宽度计算容器所需的最小、自然高度 fn get_preferred_height(&self, widget: &Self::Type, for_width: i32) -> (i32, i32) { let (child1, child2) = match *self.children.borrow() { Some((ref c1, ref c2)) => (c1, c2), None => return (0, 0), }; let left_width = for_width * 2 / 3; let right_width = for_width - left_width; let (min_h1, nat_h1) = child1.get_preferred_height_for_width(left_width); let (min_h2, nat_h2) = child2.get_preferred_height_for_width(right_width); (std::cmp::max(min_h1, min_h2), std::cmp::max(nat_h1, nat_h2)) } // 实际为子组件分配空间 fn size_allocate(&self, widget: &Self::Type, allocation: >k::Allocation) { let (child1, child2) = match *self.children.borrow() { Some((ref c1, ref c2)) => (c1, c2), None => return, }; let total_width = allocation.width(); let left_width = total_width * 2 / 3; let right_width = total_width - left_width; // 左侧组件空间分配 let mut left_alloc = *allocation; left_alloc.set_width(left_width); child1.size_allocate(&left_alloc); // 右侧组件空间分配 let mut right_alloc = *allocation; right_alloc.set_x(allocation.x() + left_width); right_alloc.set_width(right_width); child2.size_allocate(&right_alloc); } } // 实现Container trait,处理子组件的添加与移除 impl ContainerImpl for TwoThirdsContainer { fn add(&self, container: &Self::Type, widget: &Widget) { let mut children = self.children.borrow_mut(); match *children { None => *children = Some((widget.clone(), Widget::new())), Some((ref mut c1, ref mut c2)) => { if c1.is_none() { *c1 = widget.clone(); } else if c2.is_none() { *c2 = widget.clone(); } else { eprintln!("TwoThirdsContainer仅支持添加2个子组件"); return; } } } widget.set_parent(Some(container)); container.queue_resize(); } fn remove(&self, container: &Self::Type, widget: &Widget) { let mut children = self.children.borrow_mut(); match *children { Some((ref mut c1, ref mut c2)) => { if *c1 == *widget { *c1 = Widget::new(); } else if *c2 == *widget { *c2 = Widget::new(); } } None => return, } widget.unparent(); container.queue_resize(); } } // 定义公开的容器类型 glib::wrapper! { pub struct TwoThirdsContainer(ObjectSubclass<TwoThirdsContainer>) @extends Container, Widget; } impl TwoThirdsContainer { pub fn new() -> Self { glib::Object::new(&[]).expect("创建TwoThirdsContainer失败") } } // 使用示例 fn main() { gtk::init().expect("GTK初始化失败"); let window = gtk::Window::new(gtk::WindowType::Toplevel); window.set_default_size(600, 400); window.connect_destroy(|_| gtk::main_quit()); let container = TwoThirdsContainer::new(); // 左侧组件示例:可滚动文本框 let left = gtk::ScrolledWindow::new(None, None); let text_view = gtk::TextView::new(); text_view.set_wrap_mode(gtk::WrapMode::Word); left.add(&text_view); // 右侧组件示例:居中显示的标签 let right = gtk::Label::new(Some("右侧区域")); right.set_valign(gtk::Align::Center); right.set_halign(gtk::Align::Center); container.add(&left); container.add(&right); window.add(&container); window.show_all(); gtk::main(); }
关键说明
- 空间分配逻辑:在
size_allocate中直接按比例分割父容器宽度,子组件高度与容器保持一致,彻底避免手动设尺寸的不稳定问题。 - 子组件管理:示例限制最多两个子组件,你可根据需求扩展,但核心比例逻辑不变。
- 布局管理器:设置
FixedLayout是为了禁用GTK默认布局逻辑,完全由我们控制子组件的位置与尺寸。
替代方案(无需自定义Container)
如果不想编写自定义容器,也可以用GtkGrid实现同样的固定比例效果,代码更简洁:
let grid = gtk::Grid::new(); grid.set_column_homogeneous(false); // 设置列权重:左侧权重2,右侧权重1,空间将按2:1分配 grid.set_column_weight(0, 2.0); grid.set_column_weight(1, 1.0); grid.attach(&left_component, 0, 0, 1, 1); grid.attach(&right_component, 1, 0, 1, 1);
内容的提问来源于stack exchange,提问作者Jan Diederich
相关产品推荐
相关产品推荐

