AutoCompleteBox
Ein Textfeld mit Vorschlägen: ab dem ersten Zeichen filtert es 10 000 Orte (Anfang, enthält, gleich oder ein eigener Filter nach Postleitzahl oder Ort). Pfeile wählen und der Text folgt, Eingabe übernimmt, Esc stellt das Getippte her; mit Ergänzung wird der erste Treffer inline vervollständigt.


KXAML
AutoCompleteBoxSeite.kxaml
<StackPanel xmlns="https://ui.kuonen.org/kxaml"
xmlns:x="https://ui.kuonen.org/kxaml/x"
xmlns:local="using:Kuonen.UI.Galerie.Seiten"
x:Class="Kuonen.UI.Galerie.Seiten.AutoCompleteBoxVorfuehrung"
Spacing="8">
<TextBlock Text="10 000 Orte" FontWeight="SemiBold" />
<AutoCompleteBox x:Name="Ort" Width="260" HorizontalAlignment="Left" ItemsSource="{x:Static local:AutoCompleteBoxVorfuehrung.Orte}" PlaceholderText="Ort oder Postleitzahl"
Populated="Ort_Populated" DropDownClosed="Ort_DropDownClosed" />
<TextBlock Text="Filter" FontWeight="SemiBold" />
<ComboBox x:Name="Filter" Width="260" HorizontalAlignment="Left" ItemsSource="{x:Static local:AutoCompleteBoxVorfuehrung.Filterarten}" SelectedIndex="0"
SelectionChanged="Filter_SelectionChanged" />
<CheckBox Content="Inline ergänzen" Checked="Ergaenzen_Checked" Unchecked="Ergaenzen_Unchecked" />
<TextBlock Text="Verzögerung und asynchrone Quelle" FontWeight="SemiBold" />
<!-- Verzögerung 300 ms; die asynchrone Quelle setzt das Code-Behind (sie antwortet nach 400 ms, ein neuer Text bricht die alte Anfrage ab). -->
<AutoCompleteBox x:Name="Langsam" Width="260" HorizontalAlignment="Left" MinimumPopulateDelay="0:0:0.3" PlaceholderText="Ort (langsame Quelle)" Populated="Langsam_Populated" />
<TextBlock x:Name="Meldung" Text="Bereit." />
</StackPanel>AutoCompleteBoxSeite.kxaml.cs
using Kuonen.UI;
using Kuonen.UI.Controls;
using Kuonen.UI.Ereignisse;
namespace Kuonen.UI.Galerie.Seiten;
public sealed partial class AutoCompleteBoxSeite
{
protected override Func<UIElement>? KxamlFabrik => () => new AutoCompleteBoxVorfuehrung();
}
/// <summary>KXAML-Fassung der AutoCompleteBox-Seite: der Baum steht in AutoCompleteBoxSeite.kxaml, hier die Daten und das Verhalten.</summary>
public sealed partial class AutoCompleteBoxVorfuehrung
{
// Eigene Liste: Schweizer Orte als Ausgangspunkt, aufgefüllt mit nummerierten Weilern auf 10 000 Einträge („Ort Postleitzahl“).
private static readonly string[] Namen =
[
"Bern", "Basel", "Biel", "Chur", "Genf", "Lausanne", "Lugano", "Luzern", "Neuenburg", "Olten", "St. Gallen", "Schaffhausen", "Sitten", "Solothurn",
"Thun", "Winterthur", "Zug", "Zürich", "Aarau", "Baden", "Brig", "Davos", "Frauenfeld", "Freiburg", "Glarus", "Herisau", "Interlaken", "Locarno",
"Martigny", "Montreux", "Visp", "Wil", "Yverdon", "Zermatt",
];
/// <summary>Die Einträge der Seite (im KXAML über x:Static).</summary>
public static readonly string[] Orte = BaueOrte();
public static readonly string[] Filterarten = ["Anfang", "Enthält", "Gleich", "Postleitzahl oder Ort"];
public AutoCompleteBoxVorfuehrung()
{
InitializeComponent();
Langsam.AsyncPopulator = AutoCompleteBoxSeite.LangsameQuelle;
}
private void Langsam_Populated(object? sender, EventArgs e) => Meldung.Text = AutoCompleteBoxSeite.Meldung("Geladen", Langsam);
private static string[] BaueOrte()
{
var orte = new string[10_000];
for (int i = 0; i < orte.Length; i++)
{
string name = Namen[i % Namen.Length];
int runde = i / Namen.Length;
orte[i] = runde == 0 ? $"{name} {1000 + i * 7}" : $"{name}-Weiler {runde} {1000 + (i * 7 % 8999)}";
}
return orte;
}
private void Ort_Populated(object? sender, EventArgs e) => Meldung.Text = AutoCompleteBoxSeite.Meldung("Gefiltert", Ort);
private void Ort_DropDownClosed(object? sender, EventArgs e) => Meldung.Text = AutoCompleteBoxSeite.Meldung("Geschlossen", Ort);
private void Filter_SelectionChanged(object sender, SelectionChangedEventArgs e) => AutoCompleteBoxSeite.StelleFilter(Ort, Filter.SelectedIndex);
private void Ergaenzen_Checked(object sender, RoutedEventArgs e) => Ort.IsTextCompletionEnabled = true;
private void Ergaenzen_Unchecked(object sender, RoutedEventArgs e) => Ort.IsTextCompletionEnabled = false;
}C#
AutoCompleteBoxSeite.cs
using System.Globalization;
using Kuonen.UI;
using Kuonen.UI.Controls;
using Kuonen.UI.Layout;
using Kuonen.UI.Threading;
namespace Kuonen.UI.Galerie.Seiten;
/// <summary>Galerie-Seite: <see cref="AutoCompleteBox"/>.</summary>
public sealed partial class AutoCompleteBoxSeite : GalerieSeite
{
public override string Titel => "AutoCompleteBox";
public override string Beschreibung =>
"Ein Textfeld mit Vorschlägen: ab dem ersten Zeichen filtert es 10 000 Orte (Anfang, enthält, gleich oder ein eigener Filter nach Postleitzahl oder Ort). "
+ "Pfeile wählen und der Text folgt, Eingabe übernimmt, Esc stellt das Getippte her; mit Ergänzung wird der erste Treffer inline vervollständigt.";
public override string Stichworte => "Vorschläge Autovervollständigung Suche Filter Ergänzung AutoCompleteBox FilterMode ItemFilter MinimumPrefixLength IsTextCompletionEnabled Populating";
public override string Datei => nameof(AutoCompleteBoxSeite);
/// <summary>Die Filterarten der Auswahlliste (gleiche Reihenfolge wie <see cref="AutoCompleteBoxVorfuehrung.Filterarten"/>).</summary>
internal static readonly AutoCompleteFilterMode[] Filterwerte = [AutoCompleteFilterMode.StartsWith, AutoCompleteFilterMode.Contains, AutoCompleteFilterMode.Equals, AutoCompleteFilterMode.Custom];
// Eigener Filter: der Ort oder die Postleitzahl (das letzte Wort) beginnt mit dem Getippten.
internal static bool PlzOderOrt(string? suche, object? element)
{
if (element is not string ort || string.IsNullOrEmpty(suche)) return false;
int leer = ort.LastIndexOf(' ');
return ort.StartsWith(suche, StringComparison.OrdinalIgnoreCase) || (leer > 0 && ort.AsSpan(leer + 1).StartsWith(suche, StringComparison.OrdinalIgnoreCase));
}
internal static void StelleFilter(AutoCompleteBox box, int index)
{
var art = Filterwerte[Math.Clamp(index, 0, Filterwerte.Length - 1)];
if (art == AutoCompleteFilterMode.Custom) box.ItemFilter = PlzOderOrt;
else
{
box.ItemFilter = null;
box.FilterMode = art;
}
}
internal static string Meldung(string ereignis, AutoCompleteBox box) => $"{ereignis}: „{box.Text}“, {box.Vorschlaege.Count} Vorschläge";
/// <summary>
/// Eine langsame Quelle für die Vorführung: antwortet nach 400 ms (über einen DispatcherTimer, ohne Thread) mit den Orten, die mit dem Text beginnen; ein
/// neuer Text bricht die alte Anfrage ab.
/// </summary>
internal static Task<IEnumerable<object>> LangsameQuelle(string? suche, CancellationToken abbruch)
{
var antwort = new TaskCompletionSource<IEnumerable<object>>();
if (Dispatcher.Aktuell is not { } dispatcher)
{
antwort.SetResult(Treffer(suche));
return antwort.Task;
}
var timer = new DispatcherTimer(dispatcher) { Interval = TimeSpan.FromMilliseconds(400) };
timer.Tick += (_, _) =>
{
timer.Stop();
if (abbruch.IsCancellationRequested) antwort.TrySetCanceled(abbruch);
else antwort.TrySetResult(Treffer(suche));
};
abbruch.Register(() => timer.Stop());
timer.Start();
return antwort.Task;
}
private static List<object> Treffer(string? suche)
{
var treffer = new List<object>();
foreach (var ort in AutoCompleteBoxVorfuehrung.Orte)
if (suche is not null && ort.StartsWith(suche, StringComparison.OrdinalIgnoreCase) && treffer.Count < 50) treffer.Add(ort);
return treffer;
}
public override UIElement Baue()
{
var meldung = new TextBlock { Text = "Bereit." };
var ort = new AutoCompleteBox { Width = 260, HorizontalAlignment = HorizontalAlignment.Left, ItemsSource = AutoCompleteBoxVorfuehrung.Orte, PlaceholderText = "Ort oder Postleitzahl" };
ort.Populated += (_, _) => meldung.Text = Meldung("Gefiltert", ort);
ort.DropDownClosed += (_, _) => meldung.Text = Meldung("Geschlossen", ort);
var filter = new ComboBox { Width = 260, HorizontalAlignment = HorizontalAlignment.Left, ItemsSource = AutoCompleteBoxVorfuehrung.Filterarten, SelectedIndex = 0 };
filter.SelectionChanged += (_, _) => StelleFilter(ort, filter.SelectedIndex);
var ergaenzen = new CheckBox { Content = "Inline ergänzen" };
ergaenzen.Checked += (_, _) => ort.IsTextCompletionEnabled = true;
ergaenzen.Unchecked += (_, _) => ort.IsTextCompletionEnabled = false;
// Verzögerung 300 ms und eine asynchrone Quelle (antwortet nach 400 ms; ein neuer Text bricht die alte Anfrage ab).
var langsam = new AutoCompleteBox { Width = 260, HorizontalAlignment = HorizontalAlignment.Left, MinimumPopulateDelay = TimeSpan.FromMilliseconds(300), AsyncPopulator = LangsameQuelle, PlaceholderText = "Ort (langsame Quelle)" };
langsam.Populated += (_, _) => meldung.Text = Meldung("Geladen", langsam);
return new StackPanel
{
Spacing = 8,
Children =
{
new TextBlock { Text = "10 000 Orte", FontWeight = 600 },
ort,
new TextBlock { Text = "Filter", FontWeight = 600 },
filter,
ergaenzen,
new TextBlock { Text = "Verzögerung und asynchrone Quelle", FontWeight = 600 },
langsam,
meldung,
},
};
}
}