rust - 让GtkComboBox条目为Pixbuf或String

标签 rust gtk gtk3

我正在尝试实现一个下拉菜单,其中每个条目都是文本或图像。看起来,基础模型中的每一列都有一个且只有一种类型。是否仍然可以允许输入文本或图像?我的想法是使用两列,其中一列为空,然后匹配一个渲染器。我只是不知道如何。
This question's answer显示了如何在不同的上下文中将两个渲染器添加到TreeStore的一列中。当我使用ListStore时,我以为我可以用两个渲染器之一渲染空图像的方式来解决它。但是对于ComboBox,我遇到了一个问题,即未扩展的ComboBox的宽度仅取决于第一个渲染器(第二个渲染器的结果流出),并且即使对于空白内容,第一个渲染器仍会创建很多间距。
一些消息来源暗示,实现自己的Renderer是一个好主意,该Renderer动态地将Renderer用于Pixbuf或文本。我的问题是我在gtk-rs中使用Rust。虽然可以找到一些有关实现自己的渲染器的示例,但gtk-rs文档似乎没有此类文档。由于概念上的差异(由Traits等人建模的gtk继承),将示例转移到Rust并没有太多关于这种语言和gtk的经验并不是一件容易的事,我必须承认,我不知道从哪里开始。
非常感谢您提供任何帮助和/或有关前进方向的信息!

最佳答案

似乎最好的选择是实现一个自定义类型,以与自定义渲染器一起用作模型列。自定义类型包含一个可选的String和一个可选的Pixbuf,其中应将这两个参数之一设置为某个值。我没有使用枚举来确保与glib-types的兼容性(也许也可以通过这种方式,但是我不确定)。自定义渲染器包含Strings和Pixbufs的渲染器,并可以根据给定的自定义类型动态选择要使用的渲染器。
基本上,除了以自定义类型存储外,要添加的唯一功能是提供呈现单元并提供条目的首选大小的功能。在那些情况下,自定义类型的内容用于确定应从哪个给定的渲染器中复制有关渲染和尺寸请求功能的行为。
实现自定义类型和渲染器有点挑战,因为gtk-rs的文档并不是最好的文档,尤其是在使用渲染器的子类方面。我的代码基于subclassesa custom modelgtk-rs example repo的示例。由于我缺乏对Rust和gtk的经验,可能有很多零件可以解决得更优雅,更一致,但是我想分享我的解决方案,以防万一遇到类似问题的人正在寻找解决方案。有关如何进行的说明:

#[macro_use]
extern crate glib;
extern crate gdk_pixbuf;
extern crate gio;
extern crate gtk;

use gio::prelude::*;
use gtk::prelude::*;

use custom_glib_string_or_pixbuf::StringOrPixbuf;
use custom_glib_string_or_pixbuf_renderer::StringOrPixbufRenderer;
use gdk_pixbuf::Pixbuf;

// Content type
mod custom_glib_string_or_pixbuf {
    use gdk_pixbuf::Pixbuf;
    use gio::prelude::*;
    use glib::subclass;
    use glib::subclass::prelude::*;
    use glib::translate::*;

    mod internal {
        use super::*;
        use std::cell::RefCell;

        pub struct StringOrPixbuf {
            string: RefCell<Option<String>>,
            pixbuf: RefCell<Option<Pixbuf>>,
        }

        static PROPERTIES: [subclass::Property; 2] = [
            subclass::Property("string", |name| {
                glib::ParamSpec::string(name, "String", "String", None, glib::ParamFlags::READWRITE)
            }),
            subclass::Property("pixbuf", |name| {
                glib::ParamSpec::object(
                    name,
                    "Pixbuf",
                    "Pixbuf",
                    Pixbuf::static_type(),
                    glib::ParamFlags::READWRITE,
                )
            }),
        ];

        impl ObjectSubclass for StringOrPixbuf {
            const NAME: &'static str = "StringOrPixbuf";
            type ParentType = glib::Object;
            type Instance = subclass::simple::InstanceStruct<Self>;
            type Class = subclass::simple::ClassStruct<Self>;

            glib_object_subclass!();

            fn class_init(class: &mut Self::Class) {
                class.install_properties(&PROPERTIES);
            }

            fn new() -> Self {
                Self {
                    string: RefCell::new(None),
                    pixbuf: RefCell::new(None),
                }
            }
        }

        impl ObjectImpl for StringOrPixbuf {
            glib_object_impl!();

            fn set_property(&self, _obj: &glib::Object, id: usize, value: &glib::Value) {
                let prop = &PROPERTIES[id];

                match *prop {
                    subclass::Property("string", ..) => {
                        self.string.replace(value.get().unwrap());
                    }
                    subclass::Property("pixbuf", ..) => {
                        self.pixbuf.replace(value.get().unwrap());
                    }
                    _ => panic!("Tried to set unknown property of StringOrPixbuf"),
                }
            }

            fn get_property(&self, _obj: &glib::Object, id: usize) -> Result<glib::Value, ()> {
                let prop = &PROPERTIES[id];

                match *prop {
                    subclass::Property("string", ..) => Ok(self.string.borrow().to_value()),
                    subclass::Property("pixbuf", ..) => Ok(self.pixbuf.borrow().to_value()),
                    _ => panic!("Tried to get unknown property of StringOrPixbuf"),
                }
            }
        }
    }

    glib_wrapper! {
        pub struct StringOrPixbuf(Object<subclass::simple::InstanceStruct<internal::StringOrPixbuf>,subclass::simple::ClassStruct<internal::StringOrPixbuf>, StringOrPixbufClass>);

        match fn {
            get_type => || internal::StringOrPixbuf::get_type().to_glib(),
        }
    }

    impl StringOrPixbuf {
        pub fn new(string: Option<String>, pixbuf: Option<Pixbuf>) -> StringOrPixbuf {
            glib::Object::new(
                StringOrPixbuf::static_type(),
                &[("string", &string), ("pixbuf", &pixbuf)],
            )
            .expect("Failed to create StringOrPixbuf instance")
            .downcast()
            .unwrap()
        }

        pub fn is_string(&self) -> bool {
            let string_option = self
                .get_property("string")
                .unwrap()
                .get::<String>()
                .unwrap();
            let pixbuf_option = self
                .get_property("pixbuf")
                .unwrap()
                .get::<Pixbuf>()
                .unwrap();

            if string_option.is_some() == pixbuf_option.is_some() {
                panic!("Illegal StringOrPixbuf-state")
            } else if let Some(_) = string_option {
                true
            } else {
                false
            }
        }
    }
}

// Renderer
mod custom_glib_string_or_pixbuf_renderer {
    use custom_glib_string_or_pixbuf::StringOrPixbuf;
    use gdk_pixbuf::Pixbuf;
    use gio::prelude::*;
    use glib::subclass;
    use glib::subclass::prelude::*;
    use glib::translate::*;
    use gtk::prelude::*;

    mod internal {
        use super::*;
        use std::cell::RefCell;

        pub struct StringOrPixbufRenderer {
            text_renderer: gtk::CellRendererText,
            pixbuf_renderer: gtk::CellRendererPixbuf,
            string_or_pixbuf: RefCell<Option<StringOrPixbuf>>,
        }

        static PROPERTIES: [subclass::Property; 1] =
            [subclass::Property("string_or_pixbuf", |name| {
                glib::ParamSpec::object(
                    name,
                    "string_or_pixbuf",
                    "string_or_pixbuf",
                    StringOrPixbuf::static_type(),
                    glib::ParamFlags::READWRITE,
                )
            })];

        impl ObjectSubclass for StringOrPixbufRenderer {
            const NAME: &'static str = "StringOrPixbufRenderer";
            type ParentType = gtk::CellRenderer;
            type Instance = subclass::simple::InstanceStruct<Self>;
            type Class = subclass::simple::ClassStruct<Self>;
            glib_object_subclass!();
            fn class_init(class: &mut Self::Class) {
                class.install_properties(&PROPERTIES);
            }
            fn new() -> Self {
                Self {
                    text_renderer: gtk::CellRendererText::new(),
                    pixbuf_renderer: gtk::CellRendererPixbuf::new(),
                    string_or_pixbuf: RefCell::new(None),
                }
            }
        }

        impl ObjectImpl for StringOrPixbufRenderer {
            glib_object_impl!();

            fn set_property(&self, _obj: &glib::Object, id: usize, value: &glib::Value) {
                let prop = &PROPERTIES[id];
                match *prop {
                    subclass::Property("string_or_pixbuf", ..) => {
                        self.string_or_pixbuf.replace(value.get().unwrap());
                    }
                    _ => panic!("Tried to set unknown property of StringOrPixbufRenderer"),
                }
            }

            fn get_property(&self, _obj: &glib::Object, id: usize) -> Result<glib::Value, ()> {
                let prop = &PROPERTIES[id];
                match *prop {
                    subclass::Property("string_or_pixbuf", ..) => {
                        Ok(self.string_or_pixbuf.borrow().to_value())
                    }
                    _ => panic!("Tried to get unknown property of StringOrPixbufRenderer"),
                }
            }
        }

        impl gtk::subclass::cell_renderer::CellRendererImpl for StringOrPixbufRenderer {
            fn render<P: IsA<gtk::Widget>>(
                &self,
                _renderer: &gtk::CellRenderer,
                cr: &cairo::Context,
                widget: &P,
                background_area: &gdk::Rectangle,
                cell_area: &gdk::Rectangle,
                flags: gtk::CellRendererState,
            ) {
                self.update_renderers();
                if self.is_content_string() {
                    self.text_renderer
                        .render(cr, widget, background_area, cell_area, flags);
                } else {
                    self.pixbuf_renderer
                        .render(cr, widget, background_area, cell_area, flags);
                }
            }

            fn get_preferred_width<P: IsA<gtk::Widget>>(
                &self,
                _renderer: &gtk::CellRenderer,
                widget: &P,
            ) -> (i32, i32) {
                self.update_renderers();
                if self.is_content_string() {
                    self.text_renderer.get_preferred_width(widget)
                } else {
                    self.pixbuf_renderer.get_preferred_width(widget)
                }
            }

            fn get_preferred_height<P: IsA<gtk::Widget>>(
                &self,
                _renderer: &gtk::CellRenderer,
                widget: &P,
            ) -> (i32, i32) {
                self.update_renderers();
                if self.is_content_string() {
                    self.text_renderer.get_preferred_height(widget)
                } else {
                    self.pixbuf_renderer.get_preferred_height(widget)
                }
            }
        }

        impl StringOrPixbufRenderer {
            fn is_content_string(&self) -> bool {
                self.string_or_pixbuf
                    .borrow()
                    .as_ref()
                    .expect("No StringOrPixbuf known to StringOrPixbufRenderer")
                    .is_string()
            }

            fn update_renderers(&self) {
                if self.is_content_string() {
                    self.text_renderer.set_property_text(Some(
                        &self
                            .string_or_pixbuf
                            .borrow()
                            .as_ref()
                            .unwrap()
                            .get_property("string")
                            .unwrap()
                            .get::<String>()
                            .unwrap()
                            .unwrap()[..],
                    ));
                } else {
                    self.pixbuf_renderer.set_property_pixbuf(Some(
                        &self
                            .string_or_pixbuf
                            .borrow()
                            .as_ref()
                            .unwrap()
                            .get_property("pixbuf")
                            .unwrap()
                            .get::<Pixbuf>()
                            .unwrap()
                            .unwrap(),
                    ));
                }
            }
        }
    }

    glib_wrapper! {
        pub struct StringOrPixbufRenderer(
            Object<subclass::simple::InstanceStruct<internal::StringOrPixbufRenderer>,
            subclass::simple::ClassStruct<internal::StringOrPixbufRenderer>,
            SimpleAppWindowClass>)
            @extends gtk::CellRenderer;
        match fn {
            get_type => || internal::StringOrPixbufRenderer::get_type().to_glib(),
        }
    }

    impl StringOrPixbufRenderer {
        pub fn new() -> StringOrPixbufRenderer {
            glib::Object::new(StringOrPixbufRenderer::static_type(), &[])
                .expect("Failed to create StringOrPixbufRenderer instance")
                .downcast::<StringOrPixbufRenderer>()
                .unwrap()
        }
    }
}

fn main() {
    let application =
        gtk::Application::new(None, Default::default())
            .expect("failed to initialize GTK application");

    application.connect_activate(|app| {
        let window = gtk::ApplicationWindow::new(app);
        window.set_title("Mixed Pixbuf and String ComboBox Demo");
        window.set_default_size(350, 70);

        let model = gtk::ListStore::new(&[StringOrPixbuf::static_type()]);
        let combo = gtk::ComboBox::with_model(&model);

        let row = model.append();
        let image = Pixbuf::from_file("image.png")
            .unwrap()
            .scale_simple(100, 70, gdk_pixbuf::InterpType::Bilinear)
            .unwrap();
        model.set(&row, &[0], &[&StringOrPixbuf::new(None, Some(image))]);
        combo.set_active(Some(0));

        let row = model.append();
        model.set(
            &row,
            &[0],
            &[&StringOrPixbuf::new(
                Some(String::from("Hello World")),
                None,
            )],
        );

        let row = model.append();
        let image = Pixbuf::from_file("another_image.png")
            .unwrap()
            .scale_simple(100, 70, gdk_pixbuf::InterpType::Bilinear)
            .unwrap();
        model.set(&row, &[0], &[&StringOrPixbuf::new(None, Some(image))]);

        let renderer = StringOrPixbufRenderer::new();
        combo.pack_start(&renderer, true);
        combo.add_attribute(&renderer, "string_or_pixbuf", 0);

        window.add(&combo);

        window.show_all();
    });

    application.run(&[]);
}

关于rust - 让GtkComboBox条目为Pixbuf或String,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65031222/

相关文章:

c++ - gtkmm 和 MSAA 可访问性

在 gtk 中创建圆形图像

python - 从小部件到小部件的自定义信号

c - 用于 GTK 开发的 VS Code C/C++ 配置

rust - 将字符串与 Hangman 项目中的字符进行比较时出现编译错误

rust - 用于多个错误处理的 impl trait

rust - 我可以在循环中重置对本地的借用吗?

rust - 如何使用 `BorrowMut` 中包含的 `RefCell` ?

python - Gtk3按钮如何设置字体大小

python - 如何从不同线程的事件更新 Gtk.TextView?