Dateiauswahl
Dateien öffnen und speichern und Ordner wählen mit dem Dialog der Plattform: Windows (Common Item Dialog), Linux (xdg-desktop-portal), macOS (NSOpenPanel, NSSavePanel), Android (Storage Access Framework), iOS (Dokumentauswahl) und Browser (File System Access, sonst Hochladen und Herunterladen). Gewählte Dateien sind Ströme; einen Pfad gibt es nur, wo die Plattform einen kennt. Abbrechen liefert eine leere Liste. Im Headless-Host antwortet ein Drehbuch.


KXAML
DateiauswahlSeite.kxaml
<StackPanel xmlns="https://ui.kuonen.org/kxaml"
xmlns:x="https://ui.kuonen.org/kxaml/x"
x:Class="Kuonen.UI.Galerie.Seiten.DateiauswahlVorfuehrung"
Spacing="12">
<TextBlock Text="Dialog der Plattform" FontWeight="SemiBold" />
<CheckBox x:Name="Mehrfach" Content="Mehrere Dateien" />
<StackPanel Orientation="Horizontal" Spacing="8">
<Button x:Name="Oeffnen" Content="Öffnen" Style="{x:Static Fluent.AkzentButton}" Click="Oeffnen_Click" />
<Button x:Name="Speichern" Content="Speichern" Click="Speichern_Click" />
<Button x:Name="Ordner" Content="Ordner" Click="Ordner_Click" />
</StackPanel>
<TextBlock x:Name="Ergebnis" Text="Noch nichts gewählt." TextWrapping="Wrap" />
</StackPanel>DateiauswahlSeite.kxaml.cs
using Kuonen.UI;
using Kuonen.UI.Ereignisse;
namespace Kuonen.UI.Galerie.Seiten;
public sealed partial class DateiauswahlSeite
{
protected override Func<UIElement>? KxamlFabrik => () => new DateiauswahlVorfuehrung();
}
/// <summary>KXAML-Fassung der Dateiauswahl: der Baum steht in DateiauswahlSeite.kxaml, hier nur das Verhalten.</summary>
public sealed partial class DateiauswahlVorfuehrung
{
public DateiauswahlVorfuehrung() => InitializeComponent();
private async void Oeffnen_Click(object sender, RoutedEventArgs e) =>
await DateiauswahlSeite.Zeige(Ergebnis, () => DateiauswahlSeite.OeffneAsync(Oeffnen, Mehrfach.IsChecked == true));
private async void Speichern_Click(object sender, RoutedEventArgs e) => await DateiauswahlSeite.Zeige(Ergebnis, () => DateiauswahlSeite.SpeichereAsync(Speichern));
private async void Ordner_Click(object sender, RoutedEventArgs e) => await DateiauswahlSeite.Zeige(Ergebnis, () => DateiauswahlSeite.WaehleOrdnerAsync(Ordner));
}C#
DateiauswahlSeite.cs
using System.Text;
using Kuonen.UI;
using Kuonen.UI.Controls;
using Kuonen.UI.Dienste;
using Kuonen.UI.Layout;
using Kuonen.UI.Themes;
namespace Kuonen.UI.Galerie.Seiten;
/// <summary>Galerie-Seite: Dateien öffnen und speichern, Ordner wählen über den Dialog der Plattform (Plan 6h).</summary>
public sealed partial class DateiauswahlSeite : GalerieSeite
{
/// <summary>Was „Speichern“ in die gewählte Datei schreibt.</summary>
public const string Inhalt = "Gespeichert aus der Kuonen.UI-Galerie.";
public override string Titel => "Dateiauswahl";
public override string Beschreibung =>
"Dateien öffnen und speichern und Ordner wählen mit dem Dialog der Plattform: Windows (Common Item Dialog), Linux (xdg-desktop-portal), "
+ "macOS (NSOpenPanel, NSSavePanel), Android (Storage Access Framework), iOS (Dokumentauswahl) und Browser (File System Access, sonst "
+ "Hochladen und Herunterladen). Gewählte Dateien sind Ströme; einen Pfad gibt es nur, wo die Plattform einen kennt. Abbrechen liefert "
+ "eine leere Liste. Im Headless-Host antwortet ein Drehbuch.";
public override string Stichworte => "Dateiauswahl Datei öffnen speichern Ordner Dialog IDateiauswahl Window.Dateiauswahl Dateityp OeffneAsync SpeichereAsync WaehleOrdnerAsync";
public override string Datei => nameof(DateiauswahlSeite);
private static IDateiauswahl? Dienst(Visual element) => MessageBoxSeite.FensterVon(element)?.Dateiauswahl;
/// <summary>Öffnet eine oder mehrere Dateien und beschreibt das Ergebnis.</summary>
internal static async Task<string> OeffneAsync(Visual element, bool mehrfach)
{
if (Dienst(element) is not { KannOeffnen: true } auswahl) return "Dieser Host bietet keine Dateiauswahl.";
var dateien = await auswahl.OeffneAsync(new DateiOeffnenOptionen
{
Titel = "Datei öffnen", Mehrfach = mehrfach, Dateitypen = [Dateityp.Bilder, Dateityp.Text, Dateityp.Alle],
});
if (dateien.Count == 0) return "Abgebrochen.";
var text = new StringBuilder(dateien.Count == 1 ? "Gewählt: " : $"{dateien.Count} Dateien: ");
for (int i = 0; i < dateien.Count && i < 20; i++)
{
if (i > 0) text.Append(", ");
text.Append(dateien[i].Name);
if (dateien[i].Groesse is { } groesse) text.Append(" (").Append(groesse).Append(" Bytes)");
}
return text.ToString();
}
/// <summary>Lässt eine Datei wählen und schreibt <see cref="Inhalt"/> hinein.</summary>
internal static async Task<string> SpeichereAsync(Visual element)
{
if (Dienst(element) is not { KannSpeichern: true } auswahl) return "Dieser Host bietet keine Dateiauswahl.";
var datei = await auswahl.SpeichereAsync(new DateiSpeichernOptionen { Titel = "Speichern unter", Vorschlag = "Notiz.txt", Dateitypen = [Dateityp.Text] });
if (datei is null) return "Abgebrochen.";
await using (var strom = await datei.OeffneSchreibenAsync())
{
var bytes = Encoding.UTF8.GetBytes(Inhalt);
await strom.WriteAsync(bytes);
}
return "Gespeichert: " + datei.Name;
}
/// <summary>Lässt einen Ordner wählen.</summary>
internal static async Task<string> WaehleOrdnerAsync(Visual element)
{
if (Dienst(element) is not { KannOrdnerWaehlen: true } auswahl) return "Dieser Host kann keine Ordner wählen.";
var ordner = await auswahl.WaehleOrdnerAsync(new OrdnerOptionen { Titel = "Ordner wählen" });
return ordner.Count == 0 ? "Abgebrochen." : "Ordner: " + ordner[0].Name;
}
// Eine Ausnahme der Plattform (etwa ohne Portal) erscheint als Text, statt die Anwendung anzuhalten.
internal static async Task Zeige(TextBlock ergebnis, Func<Task<string>> aktion)
{
try { ergebnis.Text = await aktion(); }
catch (Exception e) when (e is InvalidOperationException or IOException or UnauthorizedAccessException or NotSupportedException)
{
ergebnis.Text = "Nicht möglich: " + e.Message;
}
}
public override UIElement Baue()
{
var mehrfach = new CheckBox { Content = "Mehrere Dateien" };
var ergebnis = new TextBlock { Text = "Noch nichts gewählt.", TextWrapping = TextWrapping.Wrap };
var oeffnen = new Button { Content = "Öffnen", Style = Fluent.AkzentButton };
var speichern = new Button { Content = "Speichern" };
var ordner = new Button { Content = "Ordner" };
oeffnen.Click += async (_, _) => await Zeige(ergebnis, () => OeffneAsync(oeffnen, mehrfach.IsChecked == true));
speichern.Click += async (_, _) => await Zeige(ergebnis, () => SpeichereAsync(speichern));
ordner.Click += async (_, _) => await Zeige(ergebnis, () => WaehleOrdnerAsync(ordner));
var knoepfe = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 8 };
knoepfe.Children.Add(oeffnen);
knoepfe.Children.Add(speichern);
knoepfe.Children.Add(ordner);
var seite = new StackPanel { Spacing = 12 };
seite.Children.Add(new TextBlock { Text = "Dialog der Plattform", FontWeight = 600 });
seite.Children.Add(mehrfach);
seite.Children.Add(knoepfe);
seite.Children.Add(ergebnis);
return seite;
}
}