Sélecteur de fichiers pour Slint
KitFilePicker · Slint Kit 0.3.0
Action Parcourir et liste des fichiers reliées à une boîte de dialogue native.

Les dialogues de fichiers et le dépôt depuis le système nécessitent l’application native ci-dessous.
Essayer le composant
Télécharger le projet complet, le décompresser et lancer cargo run --locked dans son dossier. Rust 1.92+ ; Slint Kit 0.3.0 depuis crates.io. Vous pouvez aussi copier les quatre fichiers ci-dessous.
Choisissez de vrais fichiers dans le dialogue natif puis retirez-en un de la liste. Les requêtes sont asynchrones ; annulation, plateforme non prise en charge et erreurs restent explicites.
ui/app.slint
export { KitFormValidation, HtmlInputKind, NumberInputFormat, KitInputConstraints, KitValidationResult, KitValidityState } from "@kit/kit.slint";
import {KitFilePicker,KitFileDropZone,KitThemes,FileViewEntry,FileSelectionMode,HostRequestPresentation,DropCapability} from "@kit/kit.slint";
export {FileViewEntry,FileSelectionMode,HostRequestPresentation,DropCapability}
export component App inherits Window{
title:"file-picker";preferred-width:580px;preferred-height:480px;max-width:100000px;max-height:100000px;background:KitThemes.light.canvas;
in property <[FileViewEntry]> entries;in property <HostRequestPresentation> request-state;in property <string> error;in property <string> status;
callback browse(FileSelectionMode);callback remove(int,int);
VerticalLayout{padding:24px;spacing:16px;alignment:start;
control:=KitFilePicker{label:"Selected files";description:"Names only; file content is not read.";mode:FileSelectionMode.multiple;entries:root.entries;request-state:root.request-state;error:root.error;browse-requested(mode)=>{root.browse(mode);}remove-requested(token,generation)=>{root.remove(token,generation);}}
Text{text:root.status;color:KitThemes.light.text-primary;wrap:word-wrap;}
}
}
src/main.rs
use slint::ComponentHandle;
use slint_kit::{
FileHandle, FileRequest, HostCapability, HostError, HostRequestController, SelectionOutcome,
};
use std::{cell::RefCell, rc::Rc};
mod ui {
slint::include_modules!();
}
#[derive(Clone, Copy, PartialEq)]
struct FileId(i32);
struct Entry {
id: FileId,
handle: FileHandle,
}
struct Files {
generation: i32,
next_id: i32,
entries: Vec<Entry>,
requests: HostRequestController<Vec<FileHandle>>,
}
fn publish(app: &ui::App, state: &Files) {
app.set_entries(slint::ModelRc::new(slint::VecModel::from(
state
.entries
.iter()
.map(|entry| ui::FileViewEntry {
token: entry.id.0,
generation: state.generation,
name: entry.handle.display_name.as_str().into(),
detail: "Selected resource".into(),
removable: true,
})
.collect::<Vec<_>>(),
)));
app.set_status(format!("{} selected files", state.entries.len()).into());
}
fn replace(state: &mut Files, files: Vec<FileHandle>) {
state.entries.clear();
for handle in files {
let id = FileId(state.next_id);
state.next_id = state.next_id.checked_add(1).expect("resource identity");
state.entries.push(Entry { id, handle });
}
state.generation = state.generation.checked_add(1).expect("view generation");
}
fn failed(app: &ui::App, error: HostError) {
app.set_request_state(if error == HostError::Unsupported {
ui::HostRequestPresentation::Unsupported
} else {
ui::HostRequestPresentation::Failed
});
app.set_error(format!("{error:?}").into());
}
fn main() -> Result<(), slint::PlatformError> {
let app = ui::App::new()?;
slint_kit::init_form_validation!(ui, app);
let state = Rc::new(RefCell::new(Files {
generation: 1,
next_id: 1,
entries: Vec::new(),
requests: HostRequestController::default(),
}));
publish(&app, &state.borrow());
if slint_kit::native::capabilities().files == HostCapability::Unsupported {
app.set_request_state(ui::HostRequestPresentation::Unsupported);
}
let weak = app.as_weak();
let shared = state.clone();
app.on_browse(move |mode| {
let Some(app) = weak.upgrade() else {
return;
};
let ticket = shared.borrow_mut().requests.begin();
app.set_request_state(ui::HostRequestPresentation::Pending);
app.set_error("".into());
let shared_async = shared.clone();
let weak_async = weak.clone();
if slint::spawn_local(async move {
let outcome = slint_kit::native::pick_files(FileRequest {
selection: if mode == ui::FileSelectionMode::Single {
slint_kit::FileSelectionMode::Single
} else {
slint_kit::FileSelectionMode::Multiple
},
filters: Vec::new(),
suggested_name: None,
})
.await;
let Some(app) = weak_async.upgrade() else {
return;
};
let mut state = shared_async.borrow_mut();
if !state.requests.finish(ticket, outcome) {
return;
}
match state.requests.take_result().unwrap() {
SelectionOutcome::Selected(files) => {
replace(&mut state, files);
app.set_request_state(ui::HostRequestPresentation::Idle);
publish(&app, &state);
}
SelectionOutcome::Cancelled => {
app.set_request_state(ui::HostRequestPresentation::Cancelled)
}
SelectionOutcome::Failed(error) => failed(&app, error),
}
})
.is_err()
{
shared.borrow_mut().requests.cancel();
failed(&app, HostError::Unavailable);
}
});
let weak = app.as_weak();
let shared = state.clone();
app.on_remove(move |token, generation| {
if let Some(app) = weak.upgrade() {
let mut state = shared.borrow_mut();
if state.generation != generation {
return;
}
state.requests.cancel();
state.entries.retain(|entry| entry.id != FileId(token));
state.generation = state.generation.checked_add(1).expect("view generation");
app.set_request_state(ui::HostRequestPresentation::Idle);
publish(&app, &state);
}
});
app.run()
}
Cargo.toml et build.rs
Cargo.toml
[package]
name = "slint-kit-file-picker-example"
version = "0.1.0"
edition = "2024"
rust-version = "1.92"
license = "GPL-3.0-or-later"
publish = false
[dependencies]
slint-kit = { version = "=0.3.0", features = ["native"] }
slint = { version = "=1.18.1", default-features = false, features = ["std", "compat-1-18", "backend-winit", "renderer-skia", "accessibility"] }
[build-dependencies]
slint-kit-build = "=0.3.0"
[workspace]
build.rs
fn main() {
slint_kit_build::compile("ui/app.slint").expect("compile component example");
}
Fichiers : Cargo.toml · build.rs · ui/app.slint · src/main.rs
Activez la fonctionnalité native du kit et attendez native::pick_files avec FileRequest. Publiez les noms FileHandle et conservez des identifiants stables.
L’exemple gère attente, sélection, annulation, absence de prise en charge et erreur. L’aperçu navigateur n’ouvre pas de boîte de dialogue native.
Choisir entre une option intégrée et une composition personnalisée
Options partagées
label— propriété intégrée ; exemple de composition personnalisédescription— propriété intégrée ; exemple de composition personnaliséerror— propriété intégrée ; exemple de composition personnalisé
Composition visuelle
Le fichier ci-dessous produit la capture. Pour les composants pilotés par un contrôleur, il montre l’état affiché sans fournir le branchement Rust. Créer un projet.
Afficher le fichier de la capture

// Public label overrides localize this fixture; toolkit/host defaults keep their language.
// SPDX-License-Identifier: GPL-3.0-or-later
import { KitFilePicker, KitTheme, KitThemes } from "@kit/kit.slint";
export component Example inherits Window {
title: "KitFilePicker — exemple";
preferred-width: 520px;
preferred-height: 330px;
max-width: 100000px;
max-height: 100000px;
background: root.theme.canvas;
in-out property <KitTheme> theme: KitThemes.light;
in-out property <string> status;
VerticalLayout {
padding: 24px; spacing: 16px; alignment: start;
Text { text: "Sélecteur de fichiers"; color: root.theme.text-primary; font-size: 20px * root.theme.text-scale; font-weight: 600; }
KitFilePicker { label: "Pièces jointes"; theme: root.theme; entries: [{ token: 11, generation: 1, name: "project-brief.txt", detail: "Document texte · 2,4 Ko", removable: true }, { token: 23, generation: 1, name: "meeting-notes.md", detail: "Markdown · 1,2 Ko", removable: true }]; remove-requested(token, generation) => { root.status = "Retirer le fichier d’identifiant " + token; } }
if root.status != "": Text { text: root.status; color: root.theme.text-secondary; font-size: 12px * root.theme.text-scale; wrap: word-wrap; }
}
}
API publique
Hérite de Rectangle.
Propriétés
| Nom | Type | Sens |
|---|---|---|
required |
bool |
in |
required-message |
string |
in |
custom-validity |
string |
in-out |
validity |
KitValidityState |
out |
will-validate |
bool |
out |
validation-message |
string |
out |
theme |
KitTheme |
in |
control-size |
ControlSize |
in |
access |
FieldAccess |
in |
mode |
FileSelectionMode |
in |
request-state |
HostRequestPresentation |
in |
entries |
[FileViewEntry] |
in |
label |
string |
in |
description |
string |
in |
error |
string |
in |
Types et variantes
| Type | Déclaration |
|---|---|
enum ControlSize |
compact, medium, comfortable |
enum FieldAccess |
editable, read-only, disabled |
enum FileSelectionMode |
single, multiple |
struct FileViewEntry |
token: int, generation: int, name: string, detail: string, removable: bool |
enum HostRequestPresentation |
idle, pending, cancelled, failed, unsupported |
struct KitValidityState |
valid: bool, value-missing: bool, type-mismatch: bool, pattern-mismatch: bool, too-long: bool, too-short: bool, range-underflow: bool, range-overflow: bool, step-mismatch: bool, bad-input: bool, custom-error: bool, |
Signaux
invalid()browse-requested(FileSelectionMode)remove-requested(int,int)
Fonctions
check-validity() → boolreport-validity() → boolset-custom-validity(message: string)reset-validation()take-focus()