欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 教育 > 幼教 > Electron 开发者的 Tauri 2.0 实战指南:窗口管理与系统集成

Electron 开发者的 Tauri 2.0 实战指南:窗口管理与系统集成

2025/1/18 18:10:19 来源:https://blog.csdn.net/ChengFengTech/article/details/145152093  浏览:    关键词:Electron 开发者的 Tauri 2.0 实战指南:窗口管理与系统集成

作为 Electron 开发者,我们已经习惯了使用 BrowserWindow 来管理窗口,以及通过各种内置模块来实现系统集成。在 Tauri 2.0 中,这些功能虽然概念类似,但实现方式有所不同。本文将帮助你快速掌握 Tauri 的窗口管理和系统集成功能。

窗口管理

基础窗口操作

Electron 方式
// main.js
const { BrowserWindow } = require('electron')// 创建窗口
const win = new BrowserWindow({width: 800,height: 600,frame: true,transparent: false,webPreferences: {nodeIntegration: true,contextIsolation: false}
})// 加载内容
win.loadFile('index.html')
// 或加载 URL
win.loadURL('https://example.com')// 窗口事件监听
win.on('closed', () => {// 窗口关闭时的处理
})
Tauri 方式
// main.rs
use tauri::{Window, WindowBuilder, WindowUrl};// 创建窗口
#[tauri::command]
async fn create_window(app_handle: tauri::AppHandle) -> Result<(), String> {WindowBuilder::new(&app_handle,"main",WindowUrl::App("index.html".into())).title("My App").inner_size(800.0, 600.0).resizable(true).decorations(true).transparent(false).build().map_err(|e| e.to_string())?;Ok(())
}// 前端调用
// App.tsx
import { WebviewWindow } from '@tauri-apps/api/window'// 创建新窗口
const createWindow = async () => {const webview = new WebviewWindow('main', {url: 'index.html',width: 800,height: 600})// 窗口事件监听webview.once('tauri://created', () => {// 窗口创建完成})webview.once('tauri://error', (e) => {// 窗口创建错误})
}

多窗口管理

Electron 方式
// main.js
const windows = new Map()function createWindow(name) {const win = new BrowserWindow({width: 800,height: 600})windows.set(name, win)win.on('closed', () => {windows.delete(name)})return win
}// 获取窗口
function getWindow(name) {return windows.get(name)
}
Tauri 方式
// main.rs
#[tauri::command]
async fn manage_windows(app_handle: tauri::AppHandle,window_label: String,action: String
) -> Result<(), String> {match action.as_str() {"create" => {WindowBuilder::new(&app_handle, window_label, WindowUrl::App("index.html".into())).build().map_err(|e| e.to_string())?;}"close" => {if let Some(window) = app_handle.get_window(&window_label) {window.close().map_err(|e| e.to_string())?;}}_ => return Err("Unknown action".into())}Ok(())
}
// windows.ts
import { WebviewWindow, getAll } from '@tauri-apps/api/window'// 创建窗口
export const createWindow = async (label: string) => {const webview = new WebviewWindow(label, {url: 'index.html'})return webview
}// 获取所有窗口
export const getAllWindows = () => {return getAll()
}// 获取特定窗口
export const getWindow = (label: string) => {return WebviewWindow.getByLabel(label)
}

窗口通信

Electron 方式
// main.js
ipcMain.on('message-to-window', (event, windowName, message) => {const targetWindow = windows.get(windowName)if (targetWindow) {targetWindow.webContents.send('message', message)}
})// renderer.js
ipcRenderer.on('message', (event, message) => {console.log('Received:', message)
})
Tauri 方式
// main.rs
#[tauri::command]
async fn send_message(window: Window,target: String,message: String
) -> Result<(), String> {if let Some(target_window) = window.app_handle().get_window(&target) {target_window.emit("message", message).map_err(|e| e.to_string())?;}Ok(())
}
// App.tsx
import { emit, listen } from '@tauri-apps/api/event'// 发送消息
const sendMessage = async (target: string, message: string) => {await emit('message-to-window', {target,message})
}// 监听消息
const unlisten = await listen('message', (event) => {console.log('Received:', event.payload)
})

系统集成

系统托盘

Electron 方式
// main.js
const { app, Tray, Menu } = require('electron')let tray = nullapp.whenReady().then(() => {tray = new Tray('icon.png')const contextMenu = Menu.buildFromTemplate([{ label: 'Show App', click: () => win.show() },{ label: 'Quit', click: () => app.quit() }])tray.setToolTip('My App')tray.setContextMenu(contextMenu)
})
Tauri 方式
// main.rs
use tauri::{CustomMenuItem, SystemTray, SystemTrayMenu, SystemTrayMenuItem, SystemTrayEvent
};fn main() {let quit = CustomMenuItem::new("quit".to_string(), "Quit");let show = CustomMenuItem::new("show".to_string(), "Show App");let tray_menu = SystemTrayMenu::new().add_item(show).add_native_item(SystemTrayMenuItem::Separator).add_item(quit);let system_tray = SystemTray::new().with_menu(tray_menu);tauri::Builder::default().system_tray(system_tray).on_system_tray_event(|app, event| match event {SystemTrayEvent::MenuItemClick { id, .. } => {match id.as_str() {"quit" => {app.exit(0);}"show" => {if let Some(window) = app.get_window("main") {window.show().unwrap();}}_ => {}}}_ => {}}).run(tauri::generate_context!()).expect("error while running tauri application");
}

全局快捷键

Electron 方式
// main.js
const { globalShortcut } = require('electron')app.whenReady().then(() => {globalShortcut.register('CommandOrControl+X', () => {console.log('Shortcut triggered')})
})app.on('will-quit', () => {globalShortcut.unregisterAll()
})
Tauri 方式
// main.rs
use tauri::GlobalShortcutManager;fn main() {tauri::Builder::default().setup(|app| {let mut shortcut = app.global_shortcut_manager();shortcut.register("CommandOrControl+X", || {println!("Shortcut triggered");}).unwrap();Ok(())}).run(tauri::generate_context!()).expect("error while running tauri application");
}

文件拖放

Electron 方式
// renderer.js
document.addEventListener('drop', (e) => {e.preventDefault()e.stopPropagation()for (const f of e.dataTransfer.files) {console.log('File path:', f.path)}
})document.addEventListener('dragover', (e) => {e.preventDefault()e.stopPropagation()
})
Tauri 方式
// main.rs
#[tauri::command]
async fn handle_drop(window: Window,paths: Vec<String>
) -> Result<(), String> {for path in paths {println!("Dropped file: {}", path);}Ok(())
}
// App.tsx
import { listen } from '@tauri-apps/api/event'// 监听文件拖放
listen('tauri://file-drop', (event: any) => {const paths = event.payload as string[]console.log('Dropped files:', paths)
})

原生菜单

Electron 方式
// main.js
const { Menu } = require('electron')const template = [{label: 'File',submenu: [{ label: 'New', click: () => { /* ... */ } },{ type: 'separator' },{ role: 'quit' }]}
]const menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)
Tauri 方式
// main.rs
use tauri::{Menu, MenuItem, Submenu};fn main() {let file_menu = Submenu::new("File",Menu::new().add_item(CustomMenuItem::new("new", "New")).add_native_item(MenuItem::Separator).add_item(CustomMenuItem::new("quit", "Quit")));let menu = Menu::new().add_submenu(file_menu);tauri::Builder::default().menu(menu).on_menu_event(|event| {match event.menu_item_id() {"new" => {// 处理新建操作}"quit" => {event.window().app_handle().exit(0);}_ => {}}}).run(tauri::generate_context!()).expect("error while running tauri application");
}

实战案例:多窗口文件管理器

让我们通过一个实际的案例来综合运用这些功能:

// main.rs
use std::fs;
use tauri::{Window, WindowBuilder, WindowUrl};#[derive(serde::Serialize)]
struct FileItem {name: String,path: String,is_dir: bool,
}#[tauri::command]
async fn list_files(path: String) -> Result<Vec<FileItem>, String> {let entries = fs::read_dir(path).map_err(|e| e.to_string())?;let mut files = Vec::new();for entry in entries {let entry = entry.map_err(|e| e.to_string())?;let metadata = entry.metadata().map_err(|e| e.to_string())?;files.push(FileItem {name: entry.file_name().to_string_lossy().into_owned(),path: entry.path().to_string_lossy().into_owned(),is_dir: metadata.is_dir(),});}Ok(files)
}#[tauri::command]
async fn open_folder(app_handle: tauri::AppHandle,path: String
) -> Result<(), String> {WindowBuilder::new(&app_handle,path.clone(),WindowUrl::App("index.html".into())).title(format!("Folder: {}", path)).inner_size(800.0, 600.0).build().map_err(|e| e.to_string())?;Ok(())
}fn main() {tauri::Builder::default().invoke_handler(tauri::generate_handler![list_files,open_folder]).run(tauri::generate_context!()).expect("error while running tauri application");
}
// App.tsx
import { useState, useEffect } from 'react'
import { invoke } from '@tauri-apps/api/tauri'
import { WebviewWindow } from '@tauri-apps/api/window'interface FileItem {name: stringpath: stringis_dir: boolean
}function App() {const [files, setFiles] = useState<FileItem[]>([])const [currentPath, setCurrentPath] = useState('/')useEffect(() => {loadFiles(currentPath)}, [currentPath])const loadFiles = async (path: string) => {try {const items = await invoke<FileItem[]>('list_files', { path })setFiles(items)} catch (error) {console.error('Failed to load files:', error)}}const handleFileClick = async (file: FileItem) => {if (file.is_dir) {try {await invoke('open_folder', { path: file.path })} catch (error) {console.error('Failed to open folder:', error)}}}return (<div className="container"><h2>Current Path: {currentPath}</h2><div className="file-list">{files.map((file) => (<divkey={file.path}className={`file-item ${file.is_dir ? 'directory' : 'file'}`}onClick={() => handleFileClick(file)}><span>{file.name}</span></div>))}</div></div>)
}export default App
/* styles.css */
.container {padding: 20px;
}.file-list {display: grid;grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));gap: 10px;margin-top: 20px;
}.file-item {padding: 10px;border: 1px solid #ddd;border-radius: 4px;cursor: pointer;transition: background-color 0.2s;
}.file-item:hover {background-color: #f5f5f5;
}.directory {background-color: #e3f2fd;
}.file {background-color: #fff;
}

性能优化建议

  1. 窗口创建优化

    • 延迟加载非必要窗口
    • 使用窗口预加载
    • 合理设置窗口属性
  2. 系统资源管理

    • 及时释放不需要的窗口
    • 使用事件解绑
    • 避免内存泄漏
  3. 通信优化

    • 批量处理消息
    • 使用防抖和节流
    • 避免频繁的跨进程通信

安全注意事项

  1. 窗口安全

    • 限制窗口创建数量
    • 验证加载的 URL
    • 控制窗口权限
  2. 系统集成安全

    • 限制文件系统访问
    • 验证拖放文件
    • 控制系统 API 访问
  3. 通信安全

    • 验证消息来源
    • 过滤敏感信息
    • 使用安全的通信方式

小结

  1. Tauri 2.0 的窗口管理特点:

    • 更轻量的实现
    • 更安全的权限控制
    • 更灵活的定制能力
  2. 系统集成优势:

    • 原生性能
    • 更小的内存占用
    • 更好的系统集成
  3. 开发建议:

    • 合理使用窗口
    • 注意性能优化
    • 关注安全问题

下一篇文章,我们将深入探讨 Tauri 2.0 的 IPC 通信重构,帮助你更好地理解和使用这个核心功能。

如果觉得这篇文章对你有帮助,别忘了点个赞 👍

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com