gpt4 book ai didi

callback - 如何 : Idiomatic Rust for callbacks with gtk (rust-gnome)

转载 作者:行者123 更新时间:2023-11-29 07:44:30 27 4
gpt4 key购买 nike

我目前正在学习 Rust 并希望用它来开发 GUI基于 GTK+ 的应用程序。我的问题与注册回调有关在这些回调中响应 GTK 事件/信号和变异状态。我有一个有效但不优雅的解决方案,所以我想问一下是否有是一种更简洁、更惯用的解决方案。

我已经将我的代码实现为具有方法实现的结构,其中该结构维护对 GTK 小部件的引用以及其他状态它需要。它构造一个传递给 GtkWidget::connect*为了接收事件的功能,绘制到 Canvas 等。这可能会导致借用检查器出现问题,就像我现在一样解释。我有一些工作但(恕我直言)非理想代码,我将显示。

初始的非工作解决方案:

#![cfg_attr(not(feature = "gtk_3_10"), allow(unused_variables, unused_mut))]

extern crate gtk;
extern crate cairo;

use gtk::traits::*;
use gtk::signal::Inhibit;
use cairo::{Context, RectangleInt};


struct RenderingAPITestWindow {
window: gtk::Window,
drawing_area: gtk::DrawingArea,
width: i32,
height: i32
}

impl RenderingAPITestWindow {
fn new(width: i32, height: i32) -> RenderingAPITestWindow {
let window = gtk::Window::new(gtk::WindowType::TopLevel).unwrap();
let drawing_area = gtk::DrawingArea::new().unwrap();
drawing_area.set_size_request(width, height);
window.set_title("Cairo API test");
window.add(&drawing_area);

let instance = RenderingAPITestWindow{window: window,
drawing_area: drawing_area,
width: width,
height: height,
};

instance.drawing_area.connect_draw(|widget, cairo_context| {
instance.on_draw(cairo_context);
instance.drawing_area.queue_draw();
Inhibit(true)
});

instance.drawing_area.connect_size_allocate(|widget, rect| {
instance.on_size_allocate(rect);
});

instance.window.show_all();

return instance;
}

fn exit_on_close(&self) {
self.window.connect_delete_event(|_, _| {
gtk::main_quit();
Inhibit(true)
});
}


fn on_draw(&mut self, cairo_ctx: Context) {
cairo_ctx.save();
cairo_ctx.move_to(50.0, (self.height as f64) * 0.5);
cairo_ctx.set_font_size(18.0);
cairo_ctx.show_text("The only curse they could afford to put on a tomb these days was 'Bugger Off'. --PTerry");
cairo_ctx.restore();
}

fn on_size_allocate(&mut self, rect: &RectangleInt) {
self.width = rect.width as i32;
self.height = rect.height as i32;
}
}


fn main() {
gtk::init().unwrap_or_else(|_| panic!("Failed to initialize GTK."));
println!("Major: {}, Minor: {}", gtk::get_major_version(), gtk::get_minor_version());

let window = RenderingAPITestWindow::new(800, 500);
window.exit_on_close();
gtk::main();
}

上面的代码无法编译为闭包 RenderingAPITestWindow::new创建并传递给调用 GtkWidget::connect*方法试图借用 instance .这编译器声明闭包可能比其中的函数长寿他们被宣布并且instance由外部函数拥有,因此问题。鉴于 GTK 可能会保留对这些闭包的引用在不确定的时间内,我们需要一种方法生命周期可以在运行时确定,因此我下一次尝试解决这个问题其中RenderingAPITestWindow实例被包裹在 Rc<RefCell<...>> .

包装 RenderingAPITestWindow实例编译但在运行时死亡:

#![cfg_attr(not(feature = "gtk_3_10"), allow(unused_variables, unused_mut))]

extern crate gtk;
extern crate cairo;

use std::rc::Rc;
use std::cell::RefCell;
use gtk::traits::*;
use gtk::signal::Inhibit;
use cairo::{Context, RectangleInt};


struct RenderingAPITestWindow {
window: gtk::Window,
drawing_area: gtk::DrawingArea,
width: i32,
height: i32
}

impl RenderingAPITestWindow {
fn new(width: i32, height: i32) -> Rc<RefCell<RenderingAPITestWindow>> {
let window = gtk::Window::new(gtk::WindowType::TopLevel).unwrap();
let drawing_area = gtk::DrawingArea::new().unwrap();
drawing_area.set_size_request(width, height);
window.set_title("Cairo API test");
window.add(&drawing_area);

let instance = RenderingAPITestWindow{window: window,
drawing_area: drawing_area,
width: width,
height: height,
};
let wrapped_instance = Rc::new(RefCell::new(instance));

let wrapped_instance_for_draw = wrapped_instance.clone();
wrapped_instance.borrow().drawing_area.connect_draw(move |widget, cairo_context| {
wrapped_instance_for_draw.borrow_mut().on_draw(cairo_context);

wrapped_instance_for_draw.borrow().drawing_area.queue_draw();
Inhibit(true)
});

let wrapped_instance_for_sizealloc = wrapped_instance.clone();
wrapped_instance.borrow().drawing_area.connect_size_allocate(move |widget, rect| {
wrapped_instance_for_sizealloc.borrow_mut().on_size_allocate(rect);
});

wrapped_instance.borrow().window.show_all();

return wrapped_instance;
}

fn exit_on_close(&self) {
self.window.connect_delete_event(|_, _| {
gtk::main_quit();
Inhibit(true)
});
}


fn on_draw(&mut self, cairo_ctx: Context) {
cairo_ctx.save();
cairo_ctx.move_to(50.0, (self.height as f64) * 0.5);
cairo_ctx.set_font_size(18.0);
cairo_ctx.show_text("The only curse they could afford to put on a tomb these days was 'Bugger Off'. --PTerry");
cairo_ctx.restore();
}

fn on_size_allocate(&mut self, rect: &RectangleInt) {
self.width = rect.width as i32;
self.height = rect.height as i32;
}
}


fn main() {
gtk::init().unwrap_or_else(|_| panic!("Failed to initialize GTK."));
println!("Major: {}, Minor: {}", gtk::get_major_version(), gtk::get_minor_version());

let wrapped_window = RenderingAPITestWindow::new(800, 500);
wrapped_window.borrow().exit_on_close();
gtk::main();
}

上面的解决方案编译但不是特别漂亮:

  • RenderingAPITestWindow::new返回一个 Rc<RefCell<RenderingAPITestWindow>>而不是一个 RenderingAPITestWindow
  • 访问 RenderingAPITestWindow 的字段和方法很复杂事实上 Rc<RefCell<...>>必须打开;现在需要 wrapped_instance.borrow().some_method(...)而不仅仅是 instance.some_method(...)
  • 每个闭包都需要它自己的 wrapped_instance 克隆;尝试使用 wrapped_instance会尝试借用一个对象——包装器而不是 RenderingAPITestWindow这次——也就是属于RenderingAPITestWindow::new和以前一样

虽然上面的编译,它在运行时死了:

thread '<main>' panicked at 'RefCell<T> already borrowed', ../src/libcore/cell.rs:442
An unknown error occurred

这是因为对 window.show_all() 的调用导致 GTK 到初始化小部件层次结构,生成绘图区域小部件收到size-allocate事件。访问窗口调用 show_all()要求 Rc<RefCell<...>>被打开(因此 wrapped_instance.borrow().window.show_all(); ) 和实例借来的。 show_all() 借用结束前返回时,GTK 调用绘图区的size-allocate事件处理程序,导致关闭连接到它(上面的 4 行)以被调用。闭包试图借用对 RenderingAPITestWindow 的可变引用实例( wrapped_instance_for_sizealloc.borrow_mut().on_size_allocate(rect); )为了调用 on_size_allocate方法。这试图借用一个可变引用,而第一个不可变引用仍在范围内。第二次借用会导致运行时 panic 。

工作 但是 - 恕我直言 - 我设法得到的不优雅的解决方案到目前为止的工作是拆分RenderingAPITestWindow分为两个结构,由回调修改的可变状态移入单独的结构。

拆分 RenderingAPITestWindow 的可行但不优雅的解决方案结构:

#![cfg_attr(not(feature = "gtk_3_10"), allow(unused_variables, unused_mut))]

extern crate gtk;
extern crate cairo;

use std::rc::Rc;
use std::cell::RefCell;
use gtk::traits::*;
use gtk::signal::Inhibit;
use cairo::{Context, RectangleInt};


struct RenderingAPITestWindowState {
width: i32,
height: i32
}

impl RenderingAPITestWindowState {
fn new(width: i32, height: i32) -> RenderingAPITestWindowState {
return RenderingAPITestWindowState{width: width, height: height};
}

fn on_draw(&mut self, cairo_ctx: Context) {
cairo_ctx.save();
cairo_ctx.move_to(50.0, (self.height as f64) * 0.5);
cairo_ctx.set_font_size(18.0);
cairo_ctx.show_text("The only curse they could afford to put on a tomb these days was 'Bugger Off'. --PTerry");
cairo_ctx.restore();
}

fn on_size_allocate(&mut self, rect: &RectangleInt) {
self.width = rect.width as i32;
self.height = rect.height as i32;
}
}


struct RenderingAPITestWindow {
window: gtk::Window,
drawing_area: gtk::DrawingArea,
state: Rc<RefCell<RenderingAPITestWindowState>>
}

impl RenderingAPITestWindow {
fn new(width: i32, height: i32) -> Rc<RefCell<RenderingAPITestWindow>> {
let window = gtk::Window::new(gtk::WindowType::TopLevel).unwrap();
let drawing_area = gtk::DrawingArea::new().unwrap();
drawing_area.set_size_request(width, height);
window.set_title("Cairo API test");
window.add(&drawing_area);

let wrapped_state = Rc::new(RefCell::new(RenderingAPITestWindowState::new(width, height)))
;

let instance = RenderingAPITestWindow{window: window,
drawing_area: drawing_area,
state: wrapped_state.clone()
};
let wrapped_instance = Rc::new(RefCell::new(instance));

let wrapped_state_for_draw = wrapped_state.clone();
let wrapped_instance_for_draw = wrapped_instance.clone();
wrapped_instance.borrow().drawing_area.connect_draw(move |widget, cairo_context| {
wrapped_state_for_draw.borrow_mut().on_draw(cairo_context);

wrapped_instance_for_draw.borrow().drawing_area.queue_draw();
Inhibit(true)
});

let wrapped_state_for_sizealloc = wrapped_state.clone();
wrapped_instance.borrow().drawing_area.connect_size_allocate(move |widget, rect| {
wrapped_state_for_sizealloc.borrow_mut().on_size_allocate(rect);
});

wrapped_instance.borrow().window.show_all();

return wrapped_instance;
}

fn exit_on_close(&self) {
self.window.connect_delete_event(|_, _| {
gtk::main_quit();
Inhibit(true)
});
}
}


fn main() {
gtk::init().unwrap_or_else(|_| panic!("Failed to initialize GTK."));
println!("Major: {}, Minor: {}", gtk::get_major_version(), gtk::get_minor_version());

let wrapped_window = RenderingAPITestWindow::new(800, 500);
wrapped_window.borrow().exit_on_close();
gtk::main();
}

虽然上面的代码可以按要求工作,但我想找到一个更好的方法为了前进;我想问问是否有人知道更好的方法以上使编程过程变得相当复杂,需要使用 Rc<RefCell<...>>并拆分结构以满足 Rust 的借用规则。

最佳答案

这是我想出的一个工作版本:

#![cfg_attr(not(feature = "gtk_3_10"), allow(unused_variables, unused_mut))]

extern crate gtk;
extern crate cairo;

use std::rc::Rc;
use std::cell::RefCell;
use gtk::traits::*;
use gtk::signal::Inhibit;
use cairo::{Context, RectangleInt};


struct RenderingAPITestWindow {
window: gtk::Window,
drawing_area: gtk::DrawingArea,
state: RefCell<RenderingState>,
}

struct RenderingState {
width: i32,
height: i32,
}

impl RenderingAPITestWindow {
fn new(width: i32, height: i32) -> Rc<RenderingAPITestWindow> {
let window = gtk::Window::new(gtk::WindowType::TopLevel).unwrap();
let drawing_area = gtk::DrawingArea::new().unwrap();
drawing_area.set_size_request(width, height);
window.set_title("Cairo API test");
window.add(&drawing_area);

let instance = Rc::new(RenderingAPITestWindow {
window: window,
drawing_area: drawing_area,
state: RefCell::new(RenderingState {
width: width,
height: height,
}),
});

{
let instance2 = instance.clone();
instance.drawing_area.connect_draw(move |widget, cairo_context| {
instance2.state.borrow().on_draw(cairo_context);
instance2.drawing_area.queue_draw();
Inhibit(true)
});
}
{
let instance2 = instance.clone();
instance.drawing_area.connect_size_allocate(move |widget, rect| {
instance2.state.borrow_mut().on_size_allocate(rect);
});
}
instance.window.show_all();
instance
}

fn exit_on_close(&self) {
self.window.connect_delete_event(|_, _| {
gtk::main_quit();
Inhibit(true)
});
}
}

impl RenderingState {
fn on_draw(&self, cairo_ctx: Context) {
cairo_ctx.save();
cairo_ctx.move_to(50.0, (self.height as f64) * 0.5);
cairo_ctx.set_font_size(18.0);
cairo_ctx.show_text("The only curse they could afford to put on a tomb these days was 'Bugger Off'. --PTerry");
cairo_ctx.restore();
}

fn on_size_allocate(&mut self, rect: &RectangleInt) {
self.width = rect.width as i32;
self.height = rect.height as i32;
}
}

fn main() {
gtk::init().unwrap_or_else(|_| panic!("Failed to initialize GTK."));
println!("Major: {}, Minor: {}", gtk::get_major_version(), gtk::get_minor_version());

let window = RenderingAPITestWindow::new(800, 500);
window.exit_on_close();
gtk::main();
}

我通过一些观察得出了这个结论:

  • 实例正在多个闭包中共享,持续时间不确定。 Rc 是该场景的正确答案,因为它提供共享所有权。 Rc 使用起来非常符合人体工学;它像任何其他指针类型一样工作。
  • instance 中唯一实际发生变化的部分是您的状态。由于您的实例正在共享,因此无法使用标准 &mut 指针可变地借用它。因此,您必须使用内部可变性。这就是 RefCell 提供的。但请注意,您只需要在要改变的状态上使用 RefCell。所以这仍然将状态分离到一个单独的结构中,但它在 IMO 中运行良好。
  • 对此代码的可能修改是将 #[derive(Clone, Copy)] 添加到 RenderingState 结构的定义中。因为它可以是Copy(因为它所有的组件类型都是Copy),所以你可以使用Cell而不是 RefCell

关于callback - 如何 : Idiomatic Rust for callbacks with gtk (rust-gnome),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31966497/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com