Rust Slint开发界面与逻辑分离开发详解
- 一、源码分享
-
- 1、工程结构
- 2、main.rs
- 3、ui_backend.rs
- 4、main.slint
- 5、models.slint
- 6、Cargo.toml
- 7、运行结果
- 二、界面与逻辑分离开发详解
-
- 1、 分离架构的核心思想
- 2、 项目结构与职责分析
-
- 2.1 、模型层 (`models.slint`): 定义契约
- 2.2 、视图层 (`main.slint`): 描述界面
- 2.3、 逻辑层 (`ui_backend.rs`): 实现业务
- 2.4 、胶水层 (`main.rs`): 装配一切
- 3、 数据流与通信机制
- 4、分离架构的优势与最佳实践
-
- 优势
- 最佳实践
- 5、总结

一、源码分享
1、工程结构

2、main.rs
slint::include_modules!();
mod ui_backend;
use ui_backend::UIBackend;
fn main() {
let main_window = MainWindow::new().unwrap();
let window_weak: slint::Weak<MainWindow> = main_window.as_weak();
let backend = UIBackend::new(window_weak);
backend.bind();
main_window.run().unwrap();
}
3、ui_backend.rs
use crate::slint_generatedMainWindow::{MainWindow, DataAdapter};
use slint::{ComponentHandle, ToSharedString, Weak};
pub struct UIBackend {
window: Weak<MainWindow>,
}
impl UIBackend {
pub fn new(window: Weak<MainWindow>) -> Self {
Self { window }
}
// 建议改成 &self,避免转移所有权
pub fn bind(&self) {
self.bind_btn_clicked();
}
fn bind_btn_clicked(&self) {
let Some(app) = self.window.clone().upgrade() else {
return;
};
let app_global = app.global::<DataAdapter>();
app_global.on_btn_1_clicked({
let weak: slint::Weak<MainWindow> = self.window.clone();
move || {
if let Some(strong) = weak.upgrade() {
let _adapter: DataAdapter<'_> = strong.global::<DataAdapter>();
println!("btn 1 clicked");
}
}
});
app_global.on_btn_2_clicked({
let weak: slint::Weak<MainWindow> = self.window.clone();
move || {
if let Some(strong) = weak.upgrade() {
let _adapter: DataAdapter<'_> = strong.global::<DataAdapter>();
println!("btn 2 clicked");
}
}
});
app_global.on_btn_3_clicked({
let weak: slint::Weak<MainWindow> = self.window.clone();
move || {
if let Some(strong) = weak.upgrade() {
let adapter: DataAdapter<'_> = strong.global::<DataAdapter>();
println!("btn 3 clicked");
adapter.set_test("100".to_shared_string());
}
}
});
}
}
4、main.slint
import { AboutSlint, VerticalBox, LineEdit, HorizontalBox, Button, GroupBox, GridBox,
ComboBox, Spinner, Slider, ListView, Palette, ProgressIndicator, CheckBox, Switch } from "std-widgets.slint";
import { DataAdapter} from "models.slint";
export { DataAdapter } from "models.slint";
export struct CardData {
color: string,
icon: string,
index: int,
}
export component MainWindow inherits Window {
width: 800px;
height: 600px;
title: "tedt";
background: gray;
VerticalLayout {
Button {
width: 150px;
height: 50px;
text: "Click me";
clicked => {
DataAdapter.btn_1_clicked();
}
}
Button {
width: 150px;
height: 50px;
text: "Click me";
clicked => {
DataAdapter.btn_2_clicked();
}
}
Button {
width: 150px;
height: 50px;
text: "Click me";
clicked => {
DataAdapter.btn_3_clicked();
}
}
Text {
width: 150px;
height: 50px;
text: DataAdapter.test;
horizontal–alignment: center;
vertical–alignment: center;
font–size: 20px;
color: white;
}
}
}
5、models.slint
export global DataAdapter {
in–out property <string> test:"0";
callback btn_1_clicked();
callback btn_2_clicked();
callback btn_3_clicked();
callback btn_4_clicked();
callback btn_5_clicked();
}
6、Cargo.toml
[package]
name = "chart"
version = "0.1.0"
edition = "2024"
[dependencies]
slint = "1.16.1"
[build–dependencies]
slint–build = "1.16.1"
7、运行结果

二、界面与逻辑分离开发详解
本文将通过一个完整的示例项目,详细讲解如何在 Rust 中使用 Slint 框架实现界面(UI)与业务逻辑(Business Logic)的分离开发。这种架构模式是现代 GUI 应用开发的核心,它能显著提升代码的可维护性、可测试性和团队协作效率。
1、 分离架构的核心思想
在传统的 GUI 开发中,界面代码(如按钮点击处理、数据绑定)常常与业务逻辑(如数据计算、网络请求)混杂在一起,导致代码臃肿、难以复用和测试。
Slint 通过其声明式 UI 语言和强大的 Rust 绑定,天然支持 MVC(Model-View-Controller) 或 MVVM(Model-View-ViewModel) 模式。我们的目标是将代码清晰地划分为三个层次:
2、 项目结构与职责分析
回顾我们的示例工程结构:
src/
├── main.rs # 应用入口,负责窗口创建和前后端绑定
├── ui_backend.rs # 后端逻辑实现 (Logic Layer)
├── build.rs # Slint 编译脚本 (通常自动生成)
slint/
├── main.slint # 主窗口UI定义 (View Layer)
└── models.slint # 数据模型/适配器定义 (Model/Adapter Layer)
2.1 、模型层 (models.slint): 定义契约
export global DataAdapter {
// 定义一个可双向绑定的属性
in–out property <string> test:"0";
// 定义一系列回调函数,供UI触发
callback btn_1_clicked();
callback btn_2_clicked();
callback btn_3_clicked();
}
- 作用: 这是分离架构的核心。它不关心界面如何布局,也不关心逻辑如何实现,只声明“有什么数据”和“能发生什么交互”。
- in-out property: 声明一个可以被 UI 读取和写入,同时也能被 Rust 代码修改的属性。UI 中的 Text { text: DataAdapter.test; } 就绑定了它。
- callback: 声明一个可以由 UI 触发的事件。UI 中的 clicked => { DataAdapter.btn_1_clicked(); } 就是在调用它。
2.2 、视图层 (main.slint): 描述界面
export component MainWindow inherits Window {
// … 窗口属性
VerticalLayout {
Button {
text: "Click me";
clicked => {
// 调用模型层声明的回调,不包含具体逻辑
DataAdapter.btn_1_clicked();
}
}
Text {
// 绑定模型层声明的属性
text: DataAdapter.test;
}
}
}
- 作用: 纯描述。它通过 import 使用模型层 (DataAdapter),并将界面元素(按钮的点击事件、文本的显示内容)与模型层的回调和属性绑定。这里没有任何 Rust 代码或业务逻辑。
2.3、 逻辑层 (ui_backend.rs): 实现业务
pub struct UIBackend {
window: Weak<MainWindow>, // 持有窗口的弱引用,用于访问UI组件和模型
}
impl UIBackend {
pub fn new(window: Weak<MainWindow>) -> Self { … }
pub fn bind(&self) {
self.bind_btn_clicked();
}
fn bind_btn_clicked(&self) {
let Some(app) = self.window.upgrade() else { return };
let app_global = app.global::<DataAdapter>();
// 实现 models.slint 中定义的 btn_1_clicked 回调
app_global.on_btn_1_clicked({
let weak = self.window.clone();
move || {
if let Some(strong) = weak.upgrade() {
println!("btn 1 clicked");
// 这里可以执行复杂的业务逻辑,如网络请求、计算
}
}
});
// 实现 btn_3_clicked 回调,并修改UI属性
app_global.on_btn_3_clicked({
let weak = self.window.clone();
move || {
if let Some(strong) = weak.upgrade() {
let adapter: DataAdapter<'_> = strong.global::<DataAdapter>();
println!("btn 3 clicked");
// 业务逻辑:设置新的属性值,UI会自动更新
adapter.set_test("100".to_shared_string());
}
}
});
}
}
- 作用: 这是所有“动作”发生的地方。
- 初始化 (new): 接收窗口引用。
- 绑定 (bind): 在 on_btn_1_clicked 等方法中,为模型层声明的每个 callback 提供具体的 Rust 实现。
- 操作UI: 通过 adapter.set_test(…) 修改模型属性,Slint 运行时会自动通知所有绑定此属性的 UI 组件更新。
2.4 、胶水层 (main.rs): 装配一切
fn main() {
let main_window = MainWindow::new().unwrap();
let window_weak = main_window.as_weak();
// 1. 创建后端逻辑实例
let backend = UIBackend::new(window_weak);
// 2. 执行绑定,将UI回调与Rust实现连接起来
backend.bind();
// 3. 运行主事件循环
main_window.run().unwrap();
}
- 作用: 程序的起点,负责创建窗口、实例化后端逻辑、执行绑定,并启动事件循环。它像胶水一样把视图、模型、逻辑三层粘合在一起。
3、 数据流与通信机制
理解以下数据流是掌握分离开发的关键:
UI -> 逻辑 (用户交互):
- 用户点击按钮 -> main.slint 中的 clicked 处理器调用 DataAdapter.btn_1_clicked() -> Slint 运行时调用 ui_backend.rs 中注册的 on_btn_1_clicked 闭包 -> 执行 Rust 业务逻辑。
逻辑 -> UI (状态更新):
- Rust 逻辑中调用 adapter.set_test("new value") -> Slint 运行时更新 DataAdapter.test 属性值 -> 所有绑定 DataAdapter.test 的 UI 组件(如 Text)自动重绘,显示新值。
UI <-> 逻辑 (双向绑定):
- 对于 in-out property,UI 也可以直接修改它(例如,在 LineEdit 中输入文本),修改后的值会立即同步到 Rust 端的属性上,逻辑层可以随时读取。
4、分离架构的优势与最佳实践
优势
- 高可维护性: UI 设计师可以专注于 .slint 文件的外观和交互,Rust 开发者可以专注于 ui_backend.rs 的业务逻辑,互不干扰。
- 高可测试性: 业务逻辑 (UIBackend) 不依赖于具体的 UI 实例,可以轻松编写单元测试。你可以创建模拟的 DataAdapter 来测试回调逻辑。
- 逻辑复用: 同一套业务逻辑 (UIBackend) 可以绑定到不同的 UI 视图上(例如桌面端和移动端共用逻辑)。
- 团队协作: 前后端开发可以并行进行,只需预先约定好 models.slint 中的接口。
最佳实践
5、总结
通过 models.slint 定义接口、main.slint 描述界面、ui_backend.rs 实现逻辑、main.rs 进行装配,我们成功构建了一个清晰、松耦合的 Slint 应用程序。这种界面与逻辑分离的模式,是开发复杂、可维护 GUI 应用的基石。你可以基于此模板,将更复杂的业务逻辑(如数据列表管理、网络请求、状态机)融入 UIBackend 中,同时保持 UI 层的简洁与 declarative。

网硕互联帮助中心

评论前必须登录!
注册