Zum Inhalt
Kuonen.UI

Calendar

Ein Monatskalender mit Jahr- und Jahrzehntansicht (Klick auf den Titel). Gewählt wird ein Tag, ein Bereich oder mehrere; gesperrte Tage (Bereiche, Vergangenheit, Wochenenden über einen Prüfer) sind per Tastatur erreichbar, aber nicht wählbar. Das Format (Monate, Wochentage, erster Tag, Wochenzählung) gilt je Kalender; die Designsprache bestimmt Zellen, Titel und Band.

Vorführung: CalendarVorführung: Calendar

KXAML

CalendarSeite.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.CalendarVorfuehrung"
            Spacing="12">
  <StackPanel Orientation="Horizontal" Spacing="24">
    <!-- Ab 2026, die Weihnachtstage gesperrt (Sammlung als Kindelemente); das Format setzt das Code-Behind. -->
    <Calendar x:Name="Kalender" DisplayDateStart="2026-01-01" SelectedDatesChanged="Kalender_SelectedDatesChanged">
      <Calendar.BlackoutDates>
        <CalendarDateRange Start="2026-12-24" End="2026-12-26" />
      </Calendar.BlackoutDates>
    </Calendar>
    <StackPanel Spacing="8">
      <TextBlock Text="Auswahlart" FontWeight="SemiBold" />
      <!-- Erst die Einträge, dann die Wahl, dann die Handler: der Startzustand meldet nichts. -->
      <ComboBox x:Name="Art" Width="180" ItemsSource="{x:Static local:CalendarSeiteHilfe.Auswahlarten}" SelectedIndex="0"
                SelectionChanged="Art_SelectionChanged" />
      <CheckBox x:Name="Wochennummern" Content="Wochennummern zeigen" Click="Wochennummern_Click" />
      <CheckBox x:Name="Vergangenheit" Content="Vergangenheit sperren" Click="Vergangenheit_Click" />
      <CheckBox x:Name="Wochenenden" Content="Wochenenden sperren" Click="Wochenenden_Click" />
      <CheckBox x:Name="Anzeigeende" Content="Anzeige bis Ende 2026 begrenzen" Click="Anzeigeende_Click" />
      <TextBlock Text="Erster Wochentag" FontWeight="SemiBold" />
      <ComboBox x:Name="Erster" Width="180" ItemsSource="{x:Static local:CalendarSeiteHilfe.ErsteTage}" SelectedIndex="0"
                SelectionChanged="Erster_SelectionChanged" />
      <TextBlock Text="Format" FontWeight="SemiBold" />
      <ComboBox x:Name="Format" Width="180" ItemsSource="{x:Static local:CalendarSeiteHilfe.Formate}" SelectedIndex="0"
                SelectionChanged="Format_SelectionChanged" />
    </StackPanel>
  </StackPanel>
  <TextBlock x:Name="Meldung" Text="Gewählt: nichts." />
</StackPanel>
CalendarSeite.kxaml.cs
using Kuonen.UI;
using Kuonen.UI.Controls;
using Kuonen.UI.Ereignisse;

namespace Kuonen.UI.Galerie.Seiten;

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

/// <summary>KXAML-Fassung der Calendar-Seite: der Baum steht in CalendarSeite.kxaml, hier das Format und das Verhalten.</summary>
public sealed partial class CalendarVorfuehrung
{
    public CalendarVorfuehrung()
    {
        InitializeComponent();
        // Das Format gilt nur für diesen Kalender (nicht für die ganze Anwendung).
        Kalender.Kalenderformat = Kalenderformat.Fuer("de-CH");
    }

    private void Kalender_SelectedDatesChanged(object sender, SelectionChangedEventArgs e) => Meldung.Text = CalendarSeiteHilfe.Meldung(Kalender);

    private void Art_SelectionChanged(object sender, SelectionChangedEventArgs e) => Kalender.SelectionMode = CalendarSeiteHilfe.Auswahlart(Art.SelectedIndex);

    private void Wochennummern_Click(object sender, RoutedEventArgs e) => Kalender.ShowWeekNumbers = Wochennummern.IsChecked == true;

    private void Vergangenheit_Click(object sender, RoutedEventArgs e) => CalendarSeiteHilfe.SperreVergangenheit(Kalender, Vergangenheit.IsChecked == true);

    private void Wochenenden_Click(object sender, RoutedEventArgs e) => CalendarSeiteHilfe.SperreWochenenden(Kalender, Wochenenden.IsChecked == true);

    private void Anzeigeende_Click(object sender, RoutedEventArgs e) => Kalender.DisplayDateEnd = Anzeigeende.IsChecked == true ? new DateTime(2026, 12, 31) : null;

    private void Erster_SelectionChanged(object sender, SelectionChangedEventArgs e) => CalendarSeiteHilfe.WaehleErstenTag(Kalender, Erster.SelectedIndex);

    private void Format_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        Kalender.Kalenderformat = Kalenderformat.Fuer(CalendarSeiteHilfe.Formate[Format.SelectedIndex]);
        Meldung.Text = CalendarSeiteHilfe.Meldung(Kalender);
    }
}

C#

CalendarSeite.cs
using Kuonen.UI;
using Kuonen.UI.Controls;
using Kuonen.UI.Layout;

namespace Kuonen.UI.Galerie.Seiten;

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

    public override string Beschreibung =>
        "Ein Monatskalender mit Jahr- und Jahrzehntansicht (Klick auf den Titel). Gewählt wird ein Tag, ein Bereich oder mehrere; "
        + "gesperrte Tage (Bereiche, Vergangenheit, Wochenenden über einen Prüfer) sind per Tastatur erreichbar, aber nicht wählbar. "
        + "Das Format (Monate, Wochentage, erster Tag, Wochenzählung) gilt je Kalender; die Designsprache bestimmt Zellen, Titel und Band.";

    public override string Stichworte => "Kalender Datum Monat Jahr Auswahl BlackoutDates Wochennummern DisplayDate SelectedDates FirstDayOfWeek";

    public override string Datei => nameof(CalendarSeite);

    public override UIElement Baue()
    {
        // Ab 2026, die Weihnachtstage gesperrt; das Format gilt nur für diesen Kalender (nicht für die ganze Anwendung).
        var kalender = new Calendar { DisplayDateStart = new DateTime(2026, 1, 1), Kalenderformat = Kalenderformat.Fuer("de-CH") };
        kalender.BlackoutDates.Add(new CalendarDateRange(new DateTime(2026, 12, 24), new DateTime(2026, 12, 26)));
        var meldung = new TextBlock { Text = "Gewählt: nichts." };
        kalender.SelectedDatesChanged += (_, _) => meldung.Text = CalendarSeiteHilfe.Meldung(kalender);

        var art = new ComboBox { Width = 180, ItemsSource = CalendarSeiteHilfe.Auswahlarten, SelectedIndex = 0 };
        art.SelectionChanged += (_, _) => kalender.SelectionMode = CalendarSeiteHilfe.Auswahlart(art.SelectedIndex);

        var wochennummern = new CheckBox { Content = "Wochennummern zeigen" };
        wochennummern.Click += (_, _) => kalender.ShowWeekNumbers = wochennummern.IsChecked == true;
        var vergangenheit = new CheckBox { Content = "Vergangenheit sperren" };
        vergangenheit.Click += (_, _) => CalendarSeiteHilfe.SperreVergangenheit(kalender, vergangenheit.IsChecked == true);
        var wochenenden = new CheckBox { Content = "Wochenenden sperren" };
        wochenenden.Click += (_, _) => CalendarSeiteHilfe.SperreWochenenden(kalender, wochenenden.IsChecked == true);
        var anzeigeende = new CheckBox { Content = "Anzeige bis Ende 2026 begrenzen" };
        anzeigeende.Click += (_, _) => kalender.DisplayDateEnd = anzeigeende.IsChecked == true ? new DateTime(2026, 12, 31) : null;

        var erster = new ComboBox { Width = 180, ItemsSource = CalendarSeiteHilfe.ErsteTage, SelectedIndex = 0 };
        erster.SelectionChanged += (_, _) => CalendarSeiteHilfe.WaehleErstenTag(kalender, erster.SelectedIndex);
        var format = new ComboBox { Width = 180, ItemsSource = CalendarSeiteHilfe.Formate, SelectedIndex = 0 };
        format.SelectionChanged += (_, _) =>
        {
            kalender.Kalenderformat = Kalenderformat.Fuer(CalendarSeiteHilfe.Formate[format.SelectedIndex]);
            meldung.Text = CalendarSeiteHilfe.Meldung(kalender);
        };

        var optionen = new StackPanel { Spacing = 8 };
        optionen.Children.Add(new TextBlock { Text = "Auswahlart", FontWeight = 600 });
        optionen.Children.Add(art);
        optionen.Children.Add(wochennummern);
        optionen.Children.Add(vergangenheit);
        optionen.Children.Add(wochenenden);
        optionen.Children.Add(anzeigeende);
        optionen.Children.Add(new TextBlock { Text = "Erster Wochentag", FontWeight = 600 });
        optionen.Children.Add(erster);
        optionen.Children.Add(new TextBlock { Text = "Format", FontWeight = 600 });
        optionen.Children.Add(format);

        var oben = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 24 };
        oben.Children.Add(kalender);
        oben.Children.Add(optionen);
        var seite = new StackPanel { Spacing = 12 };
        seite.Children.Add(oben);
        seite.Children.Add(meldung);
        return seite;
    }
}

/// <summary>Die Wahlmöglichkeiten und Handgriffe der Seite (beide Fassungen nutzen sie).</summary>
public static class CalendarSeiteHilfe
{
    /// <summary>Die Auswahlarten in der Reihenfolge der Liste.</summary>
    public static readonly string[] Auswahlarten = ["Ein Tag", "Ein Bereich", "Mehrere Bereiche", "Keine"];

    /// <summary>Der erste Wochentag: wie das Format oder fest.</summary>
    public static readonly string[] ErsteTage = ["Wie das Format", "Montag", "Sonntag"];

    /// <summary>Die Formate (Eigenschaft <c>Kalenderformat</c> des Kalenders).</summary>
    public static readonly string[] Formate = ["de-CH", "fr-FR", "en-US"];

    private static readonly Func<DateTime, bool> Wochenende = tag => tag.DayOfWeek is DayOfWeek.Saturday or DayOfWeek.Sunday;

    public static CalendarSelectionMode Auswahlart(int index) => index switch
    {
        1 => CalendarSelectionMode.SingleRange,
        2 => CalendarSelectionMode.MultipleRange,
        3 => CalendarSelectionMode.None,
        _ => CalendarSelectionMode.SingleDate,
    };

    public static void SperreVergangenheit(Calendar kalender, bool sperren)
    {
        if (sperren)
        {
            kalender.SelectedDates.Clear(); // ein gewählter Tag in der Vergangenheit liesse sich nicht sperren
            kalender.BlackoutDates.AddDatesInPast();
            return;
        }
        for (int i = kalender.BlackoutDates.Count - 1; i >= 0; i--)
            if (kalender.BlackoutDates[i].Start == DateTime.MinValue) kalender.BlackoutDates.RemoveAt(i);
    }

    public static void SperreWochenenden(Calendar kalender, bool sperren) => kalender.IstGesperrt = sperren ? Wochenende : null;

    public static void WaehleErstenTag(Calendar kalender, int index)
    {
        if (index == 1) kalender.FirstDayOfWeek = DayOfWeek.Monday;
        else if (index == 2) kalender.FirstDayOfWeek = DayOfWeek.Sunday;
        else kalender.ClearValue(Calendar.FirstDayOfWeekProperty);
    }

    /// <summary>Die Meldungszeile: nichts, ein Tag oder die Zahl der Tage von … bis ….</summary>
    public static string Meldung(Calendar kalender)
    {
        var format = kalender.Kalenderformat ?? Kalenderformat.Aktuell;
        var tage = kalender.SelectedDates;
        if (tage.Count == 0) return "Gewählt: nichts.";
        if (tage.Count == 1) return $"Gewählt: {format.Formatiere(tage[0], format.KurzesDatum)}.";
        var erster = tage[0];
        var letzter = tage[0];
        foreach (var tag in tage)
        {
            if (tag < erster) erster = tag;
            if (tag > letzter) letzter = tag;
        }
        return $"Gewählt: {tage.Count} Tage, {format.Formatiere(erster, format.KurzesDatum)} bis {format.Formatiere(letzter, format.KurzesDatum)}.";
    }
}