You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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: &gtk::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();
}

关键说明

  1. 空间分配逻辑:在size_allocate中直接按比例分割父容器宽度,子组件高度与容器保持一致,彻底避免手动设尺寸的不稳定问题。
  2. 子组件管理:示例限制最多两个子组件,你可根据需求扩展,但核心比例逻辑不变。
  3. 布局管理器:设置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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 09:38:22