Zum Inhalt
Kuonen.UI

TextBox

Ein Eingabefeld: Caret, Auswahl mit Maus und Umschalt, Wortsprünge mit Strg, Zwischenablage (Strg+C/X/V), Rückgängig und Wiederholen (Strg+Z/Y), IME-Eingabe. Mehrzeilig mit AcceptsReturn und Umbruch; dazu IsReadOnly, MaxLength und Validierung über die Bindung.

Vorführung: TextBoxVorführung: TextBox

KXAML

TextBoxSeite.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.TextBoxVorfuehrung"
            Spacing="8">
  <StackPanel.Resources>
    <!-- Das Datenmodell der Validierung; die Bindung unten hat es als Quelle. -->
    <local:TextBoxPerson x:Key="Person" />
  </StackPanel.Resources>
  <TextBlock Text="Einzeilig" FontWeight="SemiBold" />
  <!-- Einzeilig, mit Rückmeldung bei jeder Änderung (auch bei Rückgängig oder Einfügen). -->
  <TextBox x:Name="Einzeilig" Text="Hier tippen" Width="320" HorizontalAlignment="Left" TextChanged="Einzeilig_TextChanged" />
  <TextBlock x:Name="Zaehler" Text="11 Zeichen" />
  <TextBlock Text="Mehrzeilig" FontWeight="SemiBold" />
  <!-- Mehrzeilig mit Umbruch; Eingabe fügt Zeilen ein. Ein Zeilenumbruch im Attribut steht als &#10; da. -->
  <TextBox Text="Mehrzeilig mit Umbruch.&#10;Die Eingabetaste fügt eine Zeile ein, Bild auf/ab blättert."
           AcceptsReturn="True" TextWrapping="Wrap" Width="320" Height="96" HorizontalAlignment="Left" />
  <TextBlock Text="Nur lesen, höchstens 5 Zeichen, deaktiviert" FontWeight="SemiBold" />
  <TextBox Text="Nur lesen: auswählen und kopieren geht" IsReadOnly="True" Width="320" HorizontalAlignment="Left" />
  <TextBox MaxLength="5" Width="120" HorizontalAlignment="Left" />
  <TextBox Text="Deaktiviert" IsEnabled="False" Width="320" HorizontalAlignment="Left" />
  <TextBlock Text="Validierung (Name darf nicht leer sein)" FontWeight="SemiBold" />
  <!-- Validierung über die Bindung: ein leerer Name erreicht das Modell nicht, das Feld zeigt den Fehler. -->
  <!-- Die Bindung vor TextChanged: der erste Text meldet sich so nicht, bevor „Fehler“ steht. -->
  <TextBox x:Name="NameFeld" Width="320" HorizontalAlignment="Left"
           Text="{Binding Name, Mode=TwoWay, Source={StaticResource Person}, Validierung=PruefeName}" TextChanged="Name_TextChanged" />
  <TextBlock x:Name="Fehler" Text="Im Modell: Ada" />
</StackPanel>
TextBoxSeite.kxaml.cs
using Kuonen.UI;
using Kuonen.UI.Bindung;
using Kuonen.UI.Ereignisse;

namespace Kuonen.UI.Galerie.Seiten;

public sealed partial class TextBoxSeite
{
    protected override Func<UIElement>? KxamlFabrik => () => new TextBoxVorfuehrung();
}

/// <summary>Das Datenmodell der Validierung in der KXAML-Fassung.</summary>
public sealed class TextBoxPerson
{
    public string Name { get; set; } = "Ada";
}

/// <summary>KXAML-Fassung der TextBox-Seite: der Baum samt Bindung steht in TextBoxSeite.kxaml, hier nur das Verhalten.</summary>
public sealed partial class TextBoxVorfuehrung
{
    private readonly TextBoxPerson _person;

    public TextBoxVorfuehrung()
    {
        InitializeComponent();
        if (!Resources.TryGet(Schluessel.Person, out var person) || person is null)
            throw new InvalidOperationException("Die Ressource \"Person\" fehlt in TextBoxSeite.kxaml.");
        _person = person;
        // Beide Auslöser sind nötig: Der Fehler ändert sich nur beim Wechsel zwischen gültig und ungültig, das Modell
        // aber bei jedem angenommenen Text, ohne dass sich der Fehler ändert.
        NameFeld.Abonniere(Validation.FehlerProperty, ZeigeStand);
    }

    private string? PruefeName(string text) => string.IsNullOrWhiteSpace(text) ? "Bitte einen Namen eingeben." : null;

    private void ZeigeStand() => Fehler.Text = Validation.GetFehler(NameFeld) ?? "Im Modell: " + _person.Name;

    private void Einzeilig_TextChanged(object sender, RoutedEventArgs e) => Zaehler.Text = $"{Einzeilig.Text.Length} Zeichen";

    private void Name_TextChanged(object sender, RoutedEventArgs e) => ZeigeStand();
}

C#

TextBoxSeite.cs
using Kuonen.UI;
using Kuonen.UI.Bindung;
using Kuonen.UI.Controls;
using Kuonen.UI.Layout;

namespace Kuonen.UI.Galerie.Seiten;

/// <summary>Galerie-Seite: <see cref="TextBox"/>.</summary>
public sealed partial class TextBoxSeite : GalerieSeite
{
    public override string Titel => "TextBox";

    public override string Beschreibung =>
        "Ein Eingabefeld: Caret, Auswahl mit Maus und Umschalt, Wortsprünge mit Strg, Zwischenablage (Strg+C/X/V), "
        + "Rückgängig und Wiederholen (Strg+Z/Y), IME-Eingabe. Mehrzeilig mit AcceptsReturn und Umbruch; dazu "
        + "IsReadOnly, MaxLength und Validierung über die Bindung.";

    public override string Stichworte => "Eingabefeld Textfeld Eingabe tippen mehrzeilig AcceptsReturn MaxLength IsReadOnly Validierung Validation Zwischenablage Rückgängig IME";

    public override string Datei => nameof(TextBoxSeite);

    /// <summary>Das Datenmodell des Beispiels für die Validierung.</summary>
    private sealed class Person
    {
        public string Name { get; set; } = "Ada";
    }

    private static readonly IBindingPath<Person, string> NamePfad = BindingPath.Stufe<Person, string>("Name", p => p.Name, (p, w) => p.Name = w);

    public override UIElement Baue()
    {
        TextBlock Beschriftung(string text) => new() { Text = text, FontWeight = 600 };

        // Einzeilig, mit Rückmeldung bei jeder Änderung.
        var einzeilig = new TextBox { Text = "Hier tippen", Width = 320, HorizontalAlignment = HorizontalAlignment.Left };
        var zaehler = new TextBlock { Text = "11 Zeichen" };
        // Der Zähler stimmt nach jeder Änderung des Textes, auch bei Rückgängig oder Einfügen.
        einzeilig.TextChanged += (_, _) => zaehler.Text = $"{einzeilig.Text.Length} Zeichen";

        // Mehrzeilig mit Umbruch; Eingabe fügt Zeilen ein.
        var mehrzeilig = new TextBox
        {
            Text = "Mehrzeilig mit Umbruch.\nDie Eingabetaste fügt eine Zeile ein, Bild auf/ab blättert.",
            AcceptsReturn = true, TextWrapping = TextWrapping.Wrap, Width = 320, Height = 96, HorizontalAlignment = HorizontalAlignment.Left,
        };

        // Nur lesen, höchstens 5 Zeichen, deaktiviert.
        var nurLesen = new TextBox { Text = "Nur lesen: auswählen und kopieren geht", IsReadOnly = true, Width = 320, HorizontalAlignment = HorizontalAlignment.Left };
        var kurz = new TextBox { MaxLength = 5, Width = 120, HorizontalAlignment = HorizontalAlignment.Left };
        var aus = new TextBox { Text = "Deaktiviert", IsEnabled = false, Width = 320, HorizontalAlignment = HorizontalAlignment.Left };

        // Validierung über die Bindung: ein leerer Name erreicht das Modell nicht, das Feld zeigt den Fehler.
        var person = new Person();
        var name = new TextBox { Width = 320, HorizontalAlignment = HorizontalAlignment.Left };
        name.SetBinding(TextBox.TextProperty, new Binding<Person, string>(NamePfad)
        {
            Source = person, Mode = BindingMode.TwoWay,
            Validierung = text => string.IsNullOrWhiteSpace(text) ? "Bitte einen Namen eingeben." : null,
        });
        var fehler = new TextBlock { Text = "Im Modell: Ada" };
        // Beide Auslöser sind nötig: Der Fehler ändert sich nur beim Wechsel zwischen gültig und ungültig, das Modell
        // aber bei jedem angenommenen Text, ohne dass sich der Fehler ändert.
        void ZeigeStand() => fehler.Text = Validation.GetFehler(name) ?? "Im Modell: " + person.Name;
        name.Abonniere(Validation.FehlerProperty, ZeigeStand);
        name.TextChanged += (_, _) => ZeigeStand();

        var seite = new StackPanel { Spacing = 8 };
        seite.Children.Add(Beschriftung("Einzeilig"));
        seite.Children.Add(einzeilig);
        seite.Children.Add(zaehler);
        seite.Children.Add(Beschriftung("Mehrzeilig"));
        seite.Children.Add(mehrzeilig);
        seite.Children.Add(Beschriftung("Nur lesen, höchstens 5 Zeichen, deaktiviert"));
        seite.Children.Add(nurLesen);
        seite.Children.Add(kurz);
        seite.Children.Add(aus);
        seite.Children.Add(Beschriftung("Validierung (Name darf nicht leer sein)"));
        seite.Children.Add(name);
        seite.Children.Add(fehler);
        return seite;
    }
}