在Rust中处理GTK +事件的另一种方法 [英] Alternative way to handle GTK+ events in Rust
问题描述
目前,我使用 Rc
和 RefCell
管理GTK +事件,如以下示例所示:
Currently, I manage GTK+ events with Rc
and RefCell
as shown in the following example:
extern crate gtk;
use std::cell::RefCell;
use std::rc::Rc;
use gtk::{Button, ButtonExt, ContainerExt, Inhibit, Label, WidgetExt, Window, WindowType};
use gtk::Orientation::Vertical;
struct Model {
count: i32,
}
fn main() {
gtk::init().unwrap();
let window = Window::new(WindowType::Toplevel);
let model = Rc::new(RefCell::new(Model { count: 0 }));
let vbox = gtk::Box::new(Vertical, 0);
window.add(&vbox);
let label = Label::new(Some("0"));
vbox.add(&label);
let button = Button::new_with_label("Increment");
vbox.add(&button);
window.show_all();
window.connect_delete_event(|_, _| {
gtk::main_quit();
Inhibit(false)
});
{
let model = model.clone();
button.connect_clicked(move |_| {
{
(*model.borrow_mut()).count += 1;
}
label.set_text(&format!("{}", (*model.borrow()).count));
});
}
gtk::main();
}
此代码的主要问题是由于 RefCell
s而需要的样板.
My main issue with this code is the boilerplate needed because of the RefCell
s.
我还觉得这是一种不好的做法,并且可能导致恐慌(这不是我的主要问题,所以不要建议使用 Mutex
,因为在某些示例中,这可能导致死锁).
Also I feel like it is bad practise and can lead to panics (this is not my main issue so don't propose to use a Mutex
because in some examples, that can lead to a deadlock).
所以我想我可以用类似于Elm的方式处理事件:一个函数接收可以更新模型的信号.但是,我无法在Rust中实现这一点.这是一个尝试:
So I thought I could handle events in a way similar to Elm: with one function receiving signals where the model could be updated. However, I am unable to implement this in Rust. Here is an attempt:
extern crate gtk;
use std::cell::RefCell;
use std::collections::VecDeque;
use std::rc::Rc;
use gtk::{Button, ButtonExt, ContainerExt, Inhibit, Label, WidgetExt, WindowType};
use gtk::Orientation::Vertical;
use Message::Increment;
enum Message {
Increment,
}
struct Window {
label: Label,
model: Model,
queue: Rc<RefCell<VecDeque<Message>>>,
view: gtk::Window,
}
impl Window {
fn new() -> Self {
let window = gtk::Window::new(WindowType::Toplevel);
let vbox = gtk::Box::new(Vertical, 0);
window.add(&vbox);
let label = Label::new(Some("0"));
vbox.add(&label);
let button = Button::new_with_label("Increment");
vbox.add(&button);
window.show_all();
window.connect_delete_event(|_, _| {
gtk::main_quit();
Inhibit(false)
});
let queue = Rc::new(RefCell::new(VecDeque::new()));
{
let queue = queue.clone();
button.connect_clicked(move |_| {
(*queue.borrow_mut()).push_back(Increment);
});
}
Window {
label: label,
queue: queue,
model: Model { count: 0 },
view: window,
}
}
// How to call this method when a message is received?
fn update(&mut self, message: Message) {
match message {
Increment => {
self.model.count += 1;
self.label.set_text(&format!("{}", self.model.count));
},
}
}
}
struct Model {
count: i32,
}
fn main() {
gtk::init().unwrap();
let window = Window::new();
gtk::main();
}
消息 queue
更新时,如何调用 update()
方法?
How can I call the update()
method when the message queue
is updated?
这是可行的方法吗?
如果没有,您知道有什么替代方法可以解决此问题吗?
If not, do you know any alternatives that would provide a solution to this issue?
也许可以使用基于 future
板条箱的解决方案?在这种情况下,我该如何管理两个主循环(gtk +一个和 tokio
一个).
Perhaps some solution based on the future
crate could be used?
In this case, how do I manage both main loops (the gtk+ one and the tokio
one).
还是使用渠道的解决方案?
Or a solution using channels?
推荐答案
我找到了解决此问题的方法此处.
I found a solution to this issue here.
这是我通过示例获得的成就:
Here is what I achieved with my example:
extern crate gtk;
use gtk::{Button, ButtonExt, ContainerExt, Inhibit, Label, WidgetExt, WindowType};
use gtk::Orientation::Vertical;
use Message::Increment;
macro_rules! connect {
($source:ident :: $event:ident, $target:ident :: $message:expr) => {
let target = &mut *$target as *mut _;
$source.$event(move |_| {
let target: &mut Window = unsafe { &mut *target };
target.update($message);
});
};
}
enum Message {
Increment,
}
struct Window {
label: Label,
model: Model,
view: gtk::Window,
}
impl Window {
fn new() -> Box<Self> {
let window = gtk::Window::new(WindowType::Toplevel);
let vbox = gtk::Box::new(Vertical, 0);
window.add(&vbox);
let label = Label::new(Some("0"));
vbox.add(&label);
let button = Button::new_with_label("Increment");
vbox.add(&button);
window.show_all();
window.connect_delete_event(|_, _| {
gtk::main_quit();
Inhibit(false)
});
let mut window = Box::new(Window {
label: label,
model: Model { count: 0 },
view: window,
});
connect!(button::connect_clicked, window::Increment);
window
}
fn update(&mut self, message: Message) {
match message {
Increment => {
self.model.count += 1;
self.label.set_text(&format!("{}", self.model.count));
},
}
}
}
struct Model {
count: i32,
}
fn main() {
gtk::init().unwrap();
let _window = Window::new();
gtk::main();
}
您认为这种对 unsafe
的使用会导致段错误吗?例如,如果 button
可以超过 window
,那么 window
可以在释放后使用,不是吗?有没有办法为此建立一个安全的包装器?
Do you think this use of unsafe
can cause segfault?
For instance, if the button
could outlive the window
, the window
could be used after being freed, no?
Is there a way to build a safe wrapper around this?
从美学的角度来看,有一种方法可以实现这一目标:
On an esthetical note, is there a way to achieve this:
connect!(button::clicked, window::Increment);
这篇关于在Rust中处理GTK +事件的另一种方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!