Rust:从 DrawingArea::connect_draw 中的频道接收?

Rust: Receive from channel in DrawingArea::connect_draw?

我正在尝试制作一个程序,其中 window 显示模拟的变化状态。我对如何做到这一点的想法(我对其他建议非常开放)是产生一个处理模拟逻辑的线程,并发送详细说明绘制处理程序中读取的状态的消息。我目前对此的尝试如下:

extern crate cairo;
extern crate rand;
extern crate gtk;
extern crate gdk;
use std::{thread, time};
use gtk::prelude::*;
use gtk::{Application, ApplicationWindow, DrawingArea};
use std::sync::mpsc;
fn main(){

    let (tx, rx )= mpsc::channel();

    let app = Application::builder()
        .application_id("org.example.HelloWorld")
        .build();
    app.connect_activate(|app| {
    let draw_area = DrawingArea::new();
    let _id = draw_area.connect_draw(|_unused, f| {
        let red : f64 = rx.recv().unwrap();
        f.set_source_rgb(red, 0.0, 0.0);
        f.paint().expect("Painting failed");
        Inhibit(false)
    });
    let win = ApplicationWindow::builder()
        .application(app)
        .default_width(320)
        .default_height(200)
        .title("Hello, World!")
        .build();
    win.add(&draw_area);

    win.connect_event(|w, _g|{ //Placeholder until I learn to make queue_draw fire on a timer
        w.queue_draw();
        Inhibit(false)
    });
    win.show_all();
    });
    thread::spawn(move || {
        for _i in 0..9 {
            thread::sleep(time::Duration::from_millis(1000));
            tx.send(rand::random::<f64>()).unwrap();
        }

    });
    app.run();
}

这失败了,因为 rx 活得不够长。我想我明白编译器为什么这么想,但我不清楚如何解决这个问题,或者这是否是一个可行的方法。

作为一个额外的问题,这段代码中有一个绝对的黑客攻击 win.connect_event 调用,它让我可以通过任何操作触发重绘。我希望它只是定期触发,但我还没有找到这样做的方法 - 任何帮助将不胜感激。

我最终想出了一个办法(问 )。描述我学到的东西:

  • rx 必须 moved 到使用它的闭包中,如果闭包可能比创建 rx 的原始范围长。 (这在问题的代码中发生了两次)。
  • 此代码有两个嵌套闭包,rx 在每个闭包之外创建。你可以“给” rx 给外层闭包,但它不能再给内层闭包,因为外层闭包可能会被重复调用(因为所有 rust 都知道)。如果它放弃了它唯一的副本,它可能没有一个用于第二次调用,这是不安全的。

我的解决方案是在 connect_activate 闭包之外定义 connect_draw 外壳。请注意,此解决方案仍然不完美,因为我还没有弄清楚如何像成年人一样在计时器上触发 connect_draw,但频道问题似乎已解决:

extern crate cairo;
extern crate rand;
extern crate gtk;
extern crate gdk;
extern crate glib;
use std::{thread, time};
use gtk::prelude::*;
use gtk::{Application, ApplicationWindow, DrawingArea};
use std::sync::mpsc;
use std::sync::mpsc::{Receiver, Sender};


fn main(){
    let app = Application::builder()
        .application_id("org.example.HelloWorld")
        .build();
    let (tx, rx ) : (Sender<f64>, Receiver<f64>)= mpsc::channel();
    gtk::init().expect("GTK init failed");
    let draw_area = DrawingArea::new();
    let _id = draw_area.connect_draw(move |_unused, f| {
        let red = rx.recv().unwrap();
        f.set_source_rgb(red,0.5, 0.5);
        f.paint().expect("Painting failed");
        Inhibit(false)
    });
    app.connect_activate(move |app| {
        let win = ApplicationWindow::builder()
            .application(app)
            .default_width(320)
            .default_height(200)
            .title("Hello, World!")
            .build();
        win.add(&draw_area);

        win.show_all();

        win.connect_event(|w, _g|{ //HORRIBLE HACK HELP FIX
            w.queue_draw();
            Inhibit(false)
        });
    });
    thread::spawn(move || {
        loop {
            thread::sleep(time::Duration::from_millis(100));
            tx.send(rand::random::<f64>()).unwrap();
        }
    });
    app.run();
}