概念
Command(Selector)的用处主要是用于窗体之间的消息传递。
总体:
- 定义一个Selector 并绑定消息体
- 在Send端使用
EventCtx.submit_command()
方法发送消息。 - 接收端使用Event::Command() 来获取消息。
Druid 内部也是基于事件循环的,当程序调用 AppLauncher::launch()
方法时,程序进入事件循环。在事件循环中,窗体间的消息传递是使用Selector来进行。
查看 application.rs 源码:
代码语言:javascript复制pub fn run(self, _handler: Option<Box<dyn AppHandler>>) {
unsafe {
// 事件循环 win32
loop {
let mut msg = mem::MaybeUninit::uninit();
PeekMessageW(
msg.as_mut_ptr(),
ptr::null_mut(),
WM_TIMER,
WM_TIMER,
PM_NOREMOVE,
);
let res = GetMessageW(msg.as_mut_ptr(), ptr::null_mut(), 0, 0);
if res <= 0 {
if res == -1 {
tracing::error!(
"GetMessageW failed: {}",
Error::Hr(HRESULT_FROM_WIN32(GetLastError()))
);
}
break;
}
let mut msg: MSG = msg.assume_init();
let accels = accels::find_accels(GetAncestor(msg.hwnd, GA_ROOT));
let translated = accels.map_or(false, |it| {
TranslateAcceleratorW(msg.hwnd, it.handle(), &mut msg) != 0
});
if !translated {
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
}
}
}
1. 当主线线程进入事件循环时,其他线程向主线程发消息时, 可用使用Command。
2. 各个Widget之间的消息传递,也可以使用Command。
实践
Cargo.toml
[dependencies]
druid = { git = "https://github.com/linebender/druid.git", features=["image", "png"]}
窗体之间通信
有两个TextBox 一个Button, 当Button触发点击事件时候, 将一个TextBox中的数据发送到另外一个TextBox。
代码语言:javascript复制use druid::WidgetExt;
use druid::{
lens, text,
widget::{Button, Controller, SizedBox},
Event, Selector,
};
use druid::{
widget::{Flex, TextBox},
AppLauncher, Data, Size, Widget, WindowDesc,
};
#[derive(Data, Clone, Debug)]
pub struct DataText {
pub data: String,
pub data_rev: String,
}
const REV_CHAR: Selector<DataText> = Selector::new("com.text.rev");
struct TextBoxController;
impl<W: Widget<DataText>> Controller<DataText, W> for TextBoxController {
fn event(
&mut self,
child: &mut W,
ctx: &mut druid::EventCtx,
event: &druid::Event,
data: &mut DataText,
env: &druid::Env,
) {
if let Event::Command(e) = event {
println!("Command Event");
data.data_rev = e.get_unchecked(REV_CHAR).data.as_ref();
}
child.event(ctx, event, data, env)
}
fn lifecycle(
&mut self,
child: &mut W,
ctx: &mut druid::LifeCycleCtx,
event: &druid::LifeCycle,
data: &DataText,
env: &druid::Env,
) {
child.lifecycle(ctx, event, data, env)
}
fn update(
&mut self,
child: &mut W,
ctx: &mut druid::UpdateCtx,
old_data: &DataText,
data: &DataText,
env: &druid::Env,
) {
child.update(ctx, old_data, data, env)
}
}
fn buid_root() -> impl Widget<DataText> {
let s1 = lens!(DataText, data);
let s2 = lens!(DataText, data_rev);
let text_box_up = TextBox::multiline()
.lens(s1)
.controller(TextBoxController {});
let text_box_down = TextBox::multiline().lens(s2);
// 发送
let clear_btn = Button::new("Send").on_click(|_ctx, data: &mut DataText, _env| {
_ctx.submit_command(REV_CHAR.with(data.clone()));
data.data = "".to_string();
});
Flex::column()
.with_flex_child(text_box_up.expand(), 1.0)
.with_flex_child(text_box_down.expand(), 1.0)
.with_child(
Flex::row()
.with_flex_child(SizedBox::empty().fix_height(20.0).expand_width(), 1.0)
.with_child(clear_btn),
)
}
fn main() {
let m = DataText {
data: "Hello".to_string(),
data_rev: "".to_string(),
};
let w = WindowDesc::new(buid_root()).window_size(Size::new(400.0, 400.0));
AppLauncher::with_window(w)
.log_to_console()
.launch(m)
.unwrap();
}
跨线程通信
定义一个TextBox和一个Button, 当Button 点击时, 启动线程向界面发送信息。
代码语言:javascript复制use std::{sync::{Arc, mpsc::{self, Receiver, Sender}}, thread::{self, Thread}, time::{Duration, SystemTime}};
use druid::{Command, ExtEventSink, Target, WidgetExt, image::imageops::FilterType::Lanczos3};
use druid::{
lens, text,
widget::{Button, Controller, SizedBox},
Event, Selector,
};
use druid::{
widget::{Flex, TextBox},
AppLauncher, Data, Size, Widget, WindowDesc,
};
#[derive(Data, Clone, Debug)]
pub struct DataText {
pub data: String,
}
// 定义消息类型
const ADD_MSG : Selector<DataText> = Selector::new("com.text.add");
struct TextBoxController;
// 定义控制器, 在控制器中处理Command
impl<W: Widget<DataText>> Controller<DataText, W> for TextBoxController {
fn event(
&mut self,
child: &mut W,
ctx: &mut druid::EventCtx,
event: &druid::Event,
data: &mut DataText,
env: &druid::Env,
) {
if let Event::Command(e) = event {
println!("Command Event");
data.data = e.get_unchecked(ADD_MSG).data.as_ref();
}
child.event(ctx, event, data, env)
}
fn lifecycle(
&mut self,
child: &mut W,
ctx: &mut druid::LifeCycleCtx,
event: &druid::LifeCycle,
data: &DataText,
env: &druid::Env,
) {
child.lifecycle(ctx, event, data, env)
}
fn update(
&mut self,
child: &mut W,
ctx: &mut druid::UpdateCtx,
old_data: &DataText,
data: &DataText,
env: &druid::Env,
) {
child.update(ctx, old_data, data, env)
}
}
// 外部线程调用函数
fn event_thread(sink: ExtEventSink, rx: mpsc::Receiver<bool>){
let mut runing = false;
loop {
match rx.try_recv() {
Ok(true) | Err(mpsc::TryRecvError::Disconnected) =>{
runing = !runing;
}
_=> {}
}
thread::sleep(Duration::from_millis(500));
if runing == false {
continue;
}
let d = DataText {
data: "Hin".to_string()
};
// 向窗体线程发送消息
sink.submit_command(ADD_MSG, d, Target::Auto).expect("Sumbmit Error");
println!("Send Msg");
}
}
fn buid_root (tx:Arc<Sender<bool>>) -> impl Widget<DataText> {
let s1 = lens!(DataText, data);
let text_box = TextBox::multiline()
.lens(s1)
.controller(TextBoxController {});
// 发送消息
let clear_btn = Button::new("Send")
.on_click(move |_ctx, data: &mut DataText, _env| {
tx.send(true).expect("Send Fail!");
});
Flex::column()
.with_flex_child(text_box.expand(), 1.0)
.with_child(
Flex::row()
.with_flex_child(SizedBox::empty().fix_height(20.0).expand_width(), 1.0)
.with_child(clear_btn),
)
}
fn main() {
let m = DataText {
data: "".to_string(),
};
let (tx , rx) : (Sender<bool>, Receiver<bool>) = mpsc::channel();
let w = WindowDesc::new(buid_root(Arc::new(tx))).window_size(Size::new(400.0, 400.0));
let launcher = AppLauncher::with_window(w)
.log_to_console();
let handler = launcher.get_external_handle();
thread::spawn(move || {
event_thread(handler, rx);
});
launcher.launch(m).unwrap();
}
Ps:Druid是数据驱动的GUI框架, 所以上述的例子完全可以使用修改Data来实现。 例子的目的主要关注Selector的使用场景。