Nativer Ordner-Dialog unter Dioxus 0.7 Desktop #2

Closed
opened 2026-08-27 12:32:18 +00:00 by RDietermann · 2 comments
Owner

Part of #1

Question

Mit welcher Bibliothek öffnet eine Dioxus-0.7-Desktop-App den nativen Ordner-Auswahldialog des Betriebssystems, und wie sieht der Aufruf konkret aus?

Konkret zu klären:

  • Welche Crate ist der Standardweg (rfd? etwas aus dem Dioxus-SDK? etwas Webview-eigenes?) und ist sie mit Dioxus 0.7 / dessen Webview-Version kompatibel?
  • Blockierend oder async? Ein blockierender Dialog im Render-Pfad einer Dioxus-Komponente wäre ein Problem — welche Variante passt zu onclick?
  • Was kommt zurück, wenn der Nutzer abbricht?
  • Funktioniert derselbe Aufruf unter Windows, macOS und Linux, oder braucht es Feature-Flags / Extra-Abhängigkeiten pro Plattform?

Zielgröße: eine benennbare Crate mit Version, ein lauffähiges Beispiel-Snippet für „Ordner wählen", und die Aussage, ob das unter Windows out of the box läuft.

Part of #1 ## Question Mit welcher Bibliothek öffnet eine Dioxus-0.7-Desktop-App den nativen Ordner-Auswahldialog des Betriebssystems, und wie sieht der Aufruf konkret aus? Konkret zu klären: - Welche Crate ist der Standardweg (`rfd`? etwas aus dem Dioxus-SDK? etwas Webview-eigenes?) und ist sie mit Dioxus 0.7 / dessen Webview-Version kompatibel? - Blockierend oder async? Ein blockierender Dialog im Render-Pfad einer Dioxus-Komponente wäre ein Problem — welche Variante passt zu `onclick`? - Was kommt zurück, wenn der Nutzer abbricht? - Funktioniert derselbe Aufruf unter Windows, macOS und Linux, oder braucht es Feature-Flags / Extra-Abhängigkeiten pro Plattform? Zielgröße: eine benennbare Crate mit Version, ein lauffähiges Beispiel-Snippet für „Ordner wählen", und die Aussage, ob das unter Windows out of the box läuft.
RDietermann added the wayfinder:research label 2026-08-27 12:32:18 +00:00
RDietermann added a new dependency 2026-08-27 12:36:05 +00:00
RDietermann added a new dependency 2026-08-27 12:36:06 +00:00
RDietermann self-assigned this 2026-08-27 12:39:00 +00:00
Author
Owner

Antwort

Crate: rfd („Rusty File Dialogs"), aktuell 0.17.2 (veröffentlicht 2026-01-12). Das ist der Standardweg — und der einzige belastbare.

  • Nicht aus dem Dioxus-SDK: dioxus-sdk 0.7.0 hat die Features geolocation, notification, storage, sync, time, util, window — kein Dialog-Modul. (crates.io)
  • Nicht Webview-eigen: die Dioxus-0.7-Doku verweist für alles jenseits des Fensters explizit auf Wry/Tao direkt und bietet selbst keine Dialog-API. (dioxuslabs.com/learn/0.7)

Kompatibilität: ja, konfliktfrei

dioxus-desktop 0.7.1 zieht tao ^0.34.0 und wry ^0.53.5. rfd 0.17.2 hängt an keinem davon — die einzige Berührungsfläche ist raw-window-handle ^0.6, das tao 0.34 per Default-Feature rwh_06 ebenfalls bedient. Kein Versionskonflikt.

Blockierend oder async: zwingend async

Es gibt beide Varianten:

  • rfd::FileDialog::pick_folder(self) -> Option<PathBuf> — synchron
  • rfd::AsyncFileDialog::pick_folder(self) -> impl Future<Output = Option<FileHandle>> — async

Die synchrone fährt die modale COM-Schleife auf dem aufrufenden Thread (dialog.show() in init_com). Dioxus-Event-Handler und spawn-Tasks laufen auf dem Main-Thread — pub fn spawn(fut: impl Future<Output = ()> + 'static) -> Task hat kein Send-Bound, kann den Thread also gar nicht wechseln. Ein FileDialog::pick_folder() im onclick friert die tao-Event-Loop ein.

Die async-Variante lagert unter Windows den Dialog auf einen eigenen OS-Thread aus und liefert per Channel zurück (std::thread::spawn(move || { tx.send(f()).ok(); }) in pick_folder_async) — die Event-Loop läuft weiter. Kein Tokio-/async-std-Feature nötig, rfd hat keine Runtime-Dependency.

Abbruch

None. Kein Result, keine Fehlerunterscheidung: ein technisch fehlgeschlagener Dialog ist von einem abgebrochenen nicht unterscheidbar (die Windows-Impl endet auf run(self).ok()).

Snippet (Dioxus 0.7 API)

# Cargo.toml — noch NICHT eingetragen, gehört ins Implementierungs-Ticket
rfd = "0.17"
use dioxus::prelude::*;
use std::path::PathBuf;

#[component]
fn WorkspaceOrdnerWahl() -> Element {
    let mut ordner = use_signal(|| None::<PathBuf>);

    let label = ordner()
        .map(|p| p.display().to_string())
        .unwrap_or_else(|| "Noch kein Ordner gewählt".to_string());

    rsx! {
        button {
            // Async-Closures in Event-Handlern werden von Dioxus automatisch gespawnt
            onclick: move |_| async move {
                match rfd::AsyncFileDialog::new()
                    .set_title("Workspace-Ordner wählen")
                    .pick_folder()
                    .await
                {
                    Some(handle) => ordner.set(Some(handle.path().to_path_buf())),
                    None => {} // Nutzer hat abgebrochen
                }
            },
            "Ordner wählen…"
        }
        p { "{label}" }
    }
}

Windows: ja, out of the box

Einzige Windows-Dependency ist windows-sys ^0.61 (Features Win32_Foundation, Win32_System_Com, Win32_UI_Shell*, Win32_UI_WindowsAndMessaging) — reines Rust-Binding, keine C-Header, kein vcpkg, kein Feature-Flag. rfd = "0.17" eintragen, fertig. Das Feature common-controls-v6 betrifft nur Message-Dialoge, nicht Ordner-Dialoge.

macOS / Linux

  • macOS: nur objc2-Crates, keine Flags. Einschränkung „async braucht eine NSApplication-Instanz" ist in einer tao-App erfüllt.
  • Linux: hier liegt der einzige echte Stolperstein. Default-Backend xdg-portal braucht zur Laufzeit libdbus (sonst Fallback auf zenity) und ein installiertes Portal-Backend (GTK/GNOME/KDE; wlroots reicht nicht). Alternative default-features = false, features = ["gtk3"] braucht GTK3-Dev-Header zur Build-Zeit.

Der Rust-Code ist auf allen drei Plattformen identisch — das Packaging unter Linux nicht.

Offen / nicht belegt

  • set_parent(&*use_window()) für einen besitzer-modalen Dialog: typtheoretisch plausibel (DesktopService: Deref<Target = tao::Window>, tao::Window: HasWindowHandle via rwh_06), aber nicht kompiliert. Empfehlung: erst ohne set_parent.
  • Es wurde nichts gebaut — keine Dependency angefasst, alles aus Signaturen, Quellcode und Manifesten abgeleitet.

Langfassung mit Quellenangabe je Aussage: docs/research/nativer-ordner-dialog.md auf Branch research/ordner-dialog (lokal, nicht gepusht).

## Antwort **Crate: [`rfd`](https://crates.io/crates/rfd) („Rusty File Dialogs"), aktuell `0.17.2`** (veröffentlicht 2026-01-12). Das ist der Standardweg — und der einzige belastbare. - **Nicht** aus dem Dioxus-SDK: `dioxus-sdk 0.7.0` hat die Features `geolocation, notification, storage, sync, time, util, window` — kein Dialog-Modul. ([crates.io](https://crates.io/api/v1/crates/dioxus-sdk)) - **Nicht** Webview-eigen: die Dioxus-0.7-Doku verweist für alles jenseits des Fensters explizit auf Wry/Tao direkt und bietet selbst keine Dialog-API. ([dioxuslabs.com/learn/0.7](https://dioxuslabs.com/learn/0.7)) ### Kompatibilität: ja, konfliktfrei `dioxus-desktop 0.7.1` zieht `tao ^0.34.0` und `wry ^0.53.5`. `rfd 0.17.2` hängt an **keinem** davon — die einzige Berührungsfläche ist `raw-window-handle ^0.6`, das `tao 0.34` per Default-Feature `rwh_06` ebenfalls bedient. Kein Versionskonflikt. ### Blockierend oder async: **zwingend async** Es gibt beide Varianten: - `rfd::FileDialog::pick_folder(self) -> Option<PathBuf>` — synchron - `rfd::AsyncFileDialog::pick_folder(self) -> impl Future<Output = Option<FileHandle>>` — async Die synchrone fährt die modale COM-Schleife **auf dem aufrufenden Thread** (`dialog.show()` in `init_com`). Dioxus-Event-Handler und `spawn`-Tasks laufen auf dem Main-Thread — `pub fn spawn(fut: impl Future<Output = ()> + 'static) -> Task` hat **kein `Send`-Bound**, kann den Thread also gar nicht wechseln. Ein `FileDialog::pick_folder()` im `onclick` friert die tao-Event-Loop ein. Die **async**-Variante lagert unter Windows den Dialog auf einen eigenen OS-Thread aus und liefert per Channel zurück (`std::thread::spawn(move || { tx.send(f()).ok(); })` in `pick_folder_async`) — die Event-Loop läuft weiter. Kein Tokio-/async-std-Feature nötig, `rfd` hat keine Runtime-Dependency. ### Abbruch **`None`.** Kein `Result`, keine Fehlerunterscheidung: ein technisch fehlgeschlagener Dialog ist von einem abgebrochenen nicht unterscheidbar (die Windows-Impl endet auf `run(self).ok()`). ### Snippet (Dioxus 0.7 API) ```toml # Cargo.toml — noch NICHT eingetragen, gehört ins Implementierungs-Ticket rfd = "0.17" ``` ```rust use dioxus::prelude::*; use std::path::PathBuf; #[component] fn WorkspaceOrdnerWahl() -> Element { let mut ordner = use_signal(|| None::<PathBuf>); let label = ordner() .map(|p| p.display().to_string()) .unwrap_or_else(|| "Noch kein Ordner gewählt".to_string()); rsx! { button { // Async-Closures in Event-Handlern werden von Dioxus automatisch gespawnt onclick: move |_| async move { match rfd::AsyncFileDialog::new() .set_title("Workspace-Ordner wählen") .pick_folder() .await { Some(handle) => ordner.set(Some(handle.path().to_path_buf())), None => {} // Nutzer hat abgebrochen } }, "Ordner wählen…" } p { "{label}" } } } ``` ### Windows: ja, out of the box Einzige Windows-Dependency ist `windows-sys ^0.61` (Features `Win32_Foundation`, `Win32_System_Com`, `Win32_UI_Shell*`, `Win32_UI_WindowsAndMessaging`) — reines Rust-Binding, **keine C-Header, kein vcpkg, kein Feature-Flag**. `rfd = "0.17"` eintragen, fertig. Das Feature `common-controls-v6` betrifft nur Message-Dialoge, nicht Ordner-Dialoge. ### macOS / Linux - **macOS**: nur objc2-Crates, keine Flags. Einschränkung „async braucht eine `NSApplication`-Instanz" ist in einer tao-App erfüllt. - **Linux**: hier liegt der einzige echte Stolperstein. Default-Backend `xdg-portal` braucht zur Laufzeit `libdbus` (sonst Fallback auf `zenity`) **und** ein installiertes Portal-Backend (GTK/GNOME/KDE; wlroots reicht nicht). Alternative `default-features = false, features = ["gtk3"]` braucht GTK3-Dev-Header zur Build-Zeit. Der **Rust-Code** ist auf allen drei Plattformen identisch — das **Packaging** unter Linux nicht. ### Offen / nicht belegt - `set_parent(&*use_window())` für einen besitzer-modalen Dialog: typtheoretisch plausibel (`DesktopService: Deref<Target = tao::Window>`, `tao::Window: HasWindowHandle` via `rwh_06`), **aber nicht kompiliert**. Empfehlung: erst ohne `set_parent`. - Es wurde **nichts gebaut** — keine Dependency angefasst, alles aus Signaturen, Quellcode und Manifesten abgeleitet. --- **Langfassung mit Quellenangabe je Aussage:** `docs/research/nativer-ordner-dialog.md` auf Branch **`research/ordner-dialog`** (lokal, nicht gepusht).
Author
Owner

Nachtrag / Korrektur zum Kommentar oben

Eine Gegenprüfung hat zwei Dinge zutage gefördert, die den Kommentar oben an einer Stelle falsch machen und an einer zweiten deutlich entschärfen.

1. Korrektur: Dioxus hat sehr wohl einen eigenen Ordner-Dialog-Weg

Oben stand „Dioxus selbst bringt keine Datei-/Ordner-Dialog-API mit". Das ist falsch. Die offizielle 0.7-Doku beschreibt Ordner-Auswahl über ein directory-Attribut am File-Input:

„Dioxus will map this attribute to browser specific attributes, because there is no standardized way to allow a directory to be selected."

(dioxuslabs.com/learn/0.7/essentials/basics/event_handlers)

Auf dem Desktop ist das kein Browser-Dialog — dioxus-desktop feuert dahinter exakt den nativen rfd-Ordner-Dialog:

// packages/desktop/src/file_upload.rs @ v0.7.10
if self.directory {
    if self.multiple { dialog.pick_folders().await } else { dialog.pick_folder().await }
}
pub struct DesktopFileData(pub(crate) PathBuf);
fn path(&self) -> PathBuf { self.0.clone() }

Und der absolute Pfad ist öffentlich abgreifbar: pub fn path(&self) -> PathBuf auf dioxus_html::FileData (docs.rs).

Weg A — null neue Dependencies:

input {
    r#type: "file",
    directory: true,
    onchange: move |evt| {
        if let Some(file) = evt.files().first() {
            ordner.set(Some(file.path()));   // PathBuf
        }
    },
}

Grenze: braucht ein DOM-Element und einen echten Klick darauf. Ein programmatisch geöffneter Dialog (Menü, Shortcut, Wizard-Folgeschritt) geht damit nicht — dafür bleibt Weg B (rfd direkt) wie oben beschrieben.

2. Entschärfung: rfd ist längst im Dependency-Graph

Cargo.toml sagt dioxus = "0.7.1", aufgelöst wird real aber 0.7.10 (Cargo.lock Zeile 2337/2493). Und dioxus-desktop 0.7.10 zieht rfd ^0.17.2 nicht-optional herein (default-features = false, features = ["xdg-portal"], Target-cfg für Windows/macOS/BSDs) — crates.io. Bestätigt durch Cargo.lock: rfd 0.17.2, tao 0.34.8, wry 0.53.5.

Heißt: rfd = "0.17" einzutragen fügt dem Graph keinen neuen Crate hinzu und kostet keinen zusätzlichen Compile-Unit. Die Features vereinigt Cargo additiv, unsere Defaults sind ohnehin xdg-portal. Das „ist es die Dependency wert?"-Argument gegen Weg B fällt damit weitgehend weg.

(Randnotiz: dioxus-desktop 0.7.1 hatte noch rfd ^0.15.3 — der Sprung auf 0.17 kam zwischen 0.7.1 und 0.7.10.)

3. Bestätigt geblieben

  • dioxus-sdk 0.7.0 hat kein Dialog-Modul (Features: geolocation, notification, storage, sync, time, util, window).
  • dioxus_desktop::rfd gibt es nicht — mod file_upload; ist privat, die Re-Export-Liste in lib.rs führt nur tao, wry, muda, trayicon, launch, AssetRequest, Config, DesktopContext/DesktopService, WryEventHandler, hooks::*, Shortcut-Typen und wry::RequestAsyncResponder.
  • Alles zu async-vs-sync, Abbruch → None und „Windows out of the box" im Kommentar oben gilt unverändert.

Empfehlung

Reine UX-Frage: Dialog aus einem sichtbaren Button/Input im Workspace-Formular → Weg A. Dialog programmatisch → Weg B. Beide landen unter Windows im selben nativen IFileOpenDialog.


Langfassung aktualisiert: docs/research/nativer-ordner-dialog.md auf Branch research/ordner-dialog (Commit 68c2ae7, lokal, nicht gepusht).

## Nachtrag / Korrektur zum Kommentar oben Eine Gegenprüfung hat zwei Dinge zutage gefördert, die den Kommentar oben an einer Stelle **falsch** machen und an einer zweiten deutlich entschärfen. ### 1. Korrektur: Dioxus hat sehr wohl einen eigenen Ordner-Dialog-Weg Oben stand „Dioxus selbst bringt keine Datei-/Ordner-Dialog-API mit". **Das ist falsch.** Die offizielle 0.7-Doku beschreibt Ordner-Auswahl über ein `directory`-Attribut am File-Input: > „Dioxus will map this attribute to browser specific attributes, because there is no standardized way to allow a directory to be selected." ([dioxuslabs.com/learn/0.7/essentials/basics/event_handlers](https://dioxuslabs.com/learn/0.7/essentials/basics/event_handlers/)) Auf dem Desktop ist das **kein** Browser-Dialog — `dioxus-desktop` feuert dahinter exakt den nativen rfd-Ordner-Dialog: ```rust // packages/desktop/src/file_upload.rs @ v0.7.10 if self.directory { if self.multiple { dialog.pick_folders().await } else { dialog.pick_folder().await } } pub struct DesktopFileData(pub(crate) PathBuf); fn path(&self) -> PathBuf { self.0.clone() } ``` Und der absolute Pfad ist öffentlich abgreifbar: `pub fn path(&self) -> PathBuf` auf `dioxus_html::FileData` ([docs.rs](https://docs.rs/dioxus-html/0.7.10/dioxus_html/struct.FileData.html)). **Weg A — null neue Dependencies:** ```rust input { r#type: "file", directory: true, onchange: move |evt| { if let Some(file) = evt.files().first() { ordner.set(Some(file.path())); // PathBuf } }, } ``` Grenze: braucht ein DOM-Element und einen echten Klick darauf. Ein **programmatisch** geöffneter Dialog (Menü, Shortcut, Wizard-Folgeschritt) geht damit nicht — dafür bleibt Weg B (`rfd` direkt) wie oben beschrieben. ### 2. Entschärfung: `rfd` ist längst im Dependency-Graph `Cargo.toml` sagt `dioxus = "0.7.1"`, aufgelöst wird real aber **0.7.10** (`Cargo.lock` Zeile 2337/2493). Und `dioxus-desktop 0.7.10` zieht **`rfd ^0.17.2` nicht-optional** herein (`default-features = false, features = ["xdg-portal"]`, Target-cfg für Windows/macOS/BSDs) — [crates.io](https://crates.io/api/v1/crates/dioxus-desktop/0.7.10/dependencies). Bestätigt durch `Cargo.lock`: `rfd 0.17.2`, `tao 0.34.8`, `wry 0.53.5`. Heißt: **`rfd = "0.17"` einzutragen fügt dem Graph keinen neuen Crate hinzu und kostet keinen zusätzlichen Compile-Unit.** Die Features vereinigt Cargo additiv, unsere Defaults sind ohnehin `xdg-portal`. Das „ist es die Dependency wert?"-Argument gegen Weg B fällt damit weitgehend weg. *(Randnotiz: `dioxus-desktop 0.7.1` hatte noch `rfd ^0.15.3` — der Sprung auf 0.17 kam zwischen 0.7.1 und 0.7.10.)* ### 3. Bestätigt geblieben - `dioxus-sdk 0.7.0` hat kein Dialog-Modul (Features: `geolocation, notification, storage, sync, time, util, window`). - `dioxus_desktop::rfd` gibt es **nicht** — `mod file_upload;` ist privat, die Re-Export-Liste in `lib.rs` führt nur `tao`, `wry`, `muda`, `trayicon`, `launch`, `AssetRequest`, `Config`, `DesktopContext`/`DesktopService`, `WryEventHandler`, `hooks::*`, Shortcut-Typen und `wry::RequestAsyncResponder`. - Alles zu async-vs-sync, Abbruch → `None` und „Windows out of the box" im Kommentar oben gilt unverändert. ### Empfehlung Reine UX-Frage: Dialog aus einem sichtbaren Button/Input im Workspace-Formular → **Weg A**. Dialog programmatisch → **Weg B**. Beide landen unter Windows im selben nativen `IFileOpenDialog`. --- Langfassung aktualisiert: `docs/research/nativer-ordner-dialog.md` auf Branch **`research/ordner-dialog`** (Commit `68c2ae7`, lokal, nicht gepusht).
Sign in to join this conversation.