Zum Inhalt
Kuonen.UI

MonthCalendar

Der Monatskalender von WinForms: gewählt wird immer ein zusammenhängender Bereich (hier höchstens sieben Tage), mehrere Monate stehen neben- und untereinander, Zahltage (der 25. jedes Monats) und Feiertage (jedes Jahr) sind fett, unten steht die Zeile „Heute“. DateChanged meldet jede Änderung der Auswahl, auch beim Ziehen und durch Code; DateSelected schliesst eine Benutzerwahl ab (Maus loslassen, Eingabe, Klick auf „Heute“).

Vorführung: MonthCalendarVorführung: MonthCalendar

KXAML

MonthCalendarSeite.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.MonthCalendarVorfuehrung"
            Spacing="12">
  <StackPanel Orientation="Horizontal" Spacing="24">
    <!-- Zwei mal zwei Monate (die Anordnung als zwei Eigenschaften); das Format und die fetten Tage setzt das Code-Behind. -->
    <MonthCalendar x:Name="Kalender" MonateSpalten="2" MonateZeilen="2"
                   DateChanged="Kalender_DateChanged" DateSelected="Kalender_DateSelected" />
    <StackPanel Spacing="8">
      <TextBlock Text="Monate (Spalten × Zeilen)" FontWeight="SemiBold" />
      <!-- Erst die Einträge, dann die Wahl, dann die Handler: der Startzustand meldet nichts. -->
      <ComboBox x:Name="Monate" Width="140" ItemsSource="{x:Static local:MonthCalendarSeiteHilfe.Monate}" SelectedIndex="2"
                SelectionChanged="Monate_SelectionChanged" />
      <TextBlock Text="Höchstens gewählte Tage" FontWeight="SemiBold" />
      <ComboBox x:Name="Tage" Width="140" ItemsSource="{x:Static local:MonthCalendarSeiteHilfe.Tage}" SelectedIndex="1"
                SelectionChanged="Tage_SelectionChanged" />
      <CheckBox x:Name="Heute" Content="Zeile „Heute“ zeigen" IsChecked="True" Click="Heute_Click" />
      <CheckBox x:Name="Wochennummern" Content="Wochennummern zeigen" Click="Wochennummern_Click" />
    </StackPanel>
  </StackPanel>
  <StackPanel Spacing="2">
    <TextBlock Text="Ereignisse (das neueste oben)" FontWeight="SemiBold" />
    <TextBlock x:Name="Zeile1" Text="Noch keine Ereignisse." />
    <TextBlock x:Name="Zeile2" Text="–" />
    <TextBlock x:Name="Zeile3" Text="–" />
    <TextBlock x:Name="Zeile4" Text="–" />
    <TextBlock x:Name="Zeile5" Text="–" />
  </StackPanel>
</StackPanel>
MonthCalendarSeite.kxaml.cs
using Kuonen.UI;
using Kuonen.UI.Controls;
using Kuonen.UI.Ereignisse;

namespace Kuonen.UI.Galerie.Seiten;

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

/// <summary>KXAML-Fassung der MonthCalendar-Seite: der Baum steht in MonthCalendarSeite.kxaml, hier das Format, die fetten Tage und das Verhalten.</summary>
public sealed partial class MonthCalendarVorfuehrung
{
    private readonly TextBlock[] _zeilen;

    public MonthCalendarVorfuehrung()
    {
        InitializeComponent();
        _zeilen = [Zeile1, Zeile2, Zeile3, Zeile4, Zeile5];
        // Das Format gilt nur für diesen Kalender (nicht für die ganze Anwendung).
        Kalender.Kalenderformat = Kalenderformat.Fuer("de-CH");
        MonthCalendarSeiteHilfe.Hervorheben(Kalender);
    }

    private void Kalender_DateChanged(object? sender, DateRangeEventArgs e) => MonthCalendarSeiteHilfe.Protokolliere(Kalender, _zeilen, "Geändert", e);

    private void Kalender_DateSelected(object? sender, DateRangeEventArgs e) => MonthCalendarSeiteHilfe.Protokolliere(Kalender, _zeilen, "Abgeschlossen", e);

    private void Monate_SelectionChanged(object sender, SelectionChangedEventArgs e) => MonthCalendarSeiteHilfe.WaehleMonate(Kalender, Monate.SelectedIndex);

    private void Tage_SelectionChanged(object sender, SelectionChangedEventArgs e) => MonthCalendarSeiteHilfe.WaehleMaxTage(Kalender, Tage.SelectedIndex);

    private void Heute_Click(object sender, RoutedEventArgs e) => Kalender.ShowToday = Heute.IsChecked == true;

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

C#

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

namespace Kuonen.UI.Galerie.Seiten;

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

    public override string Beschreibung =>
        "Der Monatskalender von WinForms: gewählt wird immer ein zusammenhängender Bereich (hier höchstens sieben Tage), mehrere Monate stehen neben- und untereinander, "
        + "Zahltage (der 25. jedes Monats) und Feiertage (jedes Jahr) sind fett, unten steht die Zeile „Heute“. DateChanged meldet jede Änderung der Auswahl, auch beim Ziehen und durch Code; "
        + "DateSelected schliesst eine Benutzerwahl ab (Maus loslassen, Eingabe, Klick auf „Heute“).";

    public override string Stichworte => "Kalender WinForms MonthCalendar Datum Bereich SelectionRange MaxSelectionCount BoldedDates AnnuallyBoldedDates CalendarDimensions ShowToday ScrollChange DateChanged DateSelected";

    public override string Datei => nameof(MonthCalendarSeite);

    public override UIElement Baue()
    {
        // 2 × 2 Monate, Format nur für diesen Kalender; Zahltage und Feiertage fett.
        var kalender = new MonthCalendar { Kalenderformat = Kalenderformat.Fuer("de-CH") };
        kalender.SetCalendarDimensions(2, 2);
        MonthCalendarSeiteHilfe.Hervorheben(kalender);

        var zeilen = MonthCalendarSeiteHilfe.NeueZeilen();
        kalender.DateChanged += (_, e) => MonthCalendarSeiteHilfe.Protokolliere(kalender, zeilen, "Geändert", e);
        kalender.DateSelected += (_, e) => MonthCalendarSeiteHilfe.Protokolliere(kalender, zeilen, "Abgeschlossen", e);

        var heute = new CheckBox { Content = "Zeile „Heute“ zeigen", IsChecked = true };
        heute.Click += (_, _) => kalender.ShowToday = heute.IsChecked == true;
        var wochennummern = new CheckBox { Content = "Wochennummern zeigen" };
        wochennummern.Click += (_, _) => kalender.ShowWeekNumbers = wochennummern.IsChecked == true;
        var monate = new ComboBox { Width = 140, ItemsSource = MonthCalendarSeiteHilfe.Monate, SelectedIndex = 2 };
        monate.SelectionChanged += (_, _) => MonthCalendarSeiteHilfe.WaehleMonate(kalender, monate.SelectedIndex);
        var tage = new ComboBox { Width = 140, ItemsSource = MonthCalendarSeiteHilfe.Tage, SelectedIndex = 1 };
        tage.SelectionChanged += (_, _) => MonthCalendarSeiteHilfe.WaehleMaxTage(kalender, tage.SelectedIndex);

        var optionen = new StackPanel { Spacing = 8 };
        optionen.Children.Add(new TextBlock { Text = "Monate (Spalten × Zeilen)", FontWeight = 600 });
        optionen.Children.Add(monate);
        optionen.Children.Add(new TextBlock { Text = "Höchstens gewählte Tage", FontWeight = 600 });
        optionen.Children.Add(tage);
        optionen.Children.Add(heute);
        optionen.Children.Add(wochennummern);

        var oben = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 24 };
        oben.Children.Add(kalender);
        oben.Children.Add(optionen);
        var ereignisse = new StackPanel { Spacing = 2 };
        ereignisse.Children.Add(new TextBlock { Text = "Ereignisse (das neueste oben)", FontWeight = 600 });
        foreach (var zeile in zeilen) ereignisse.Children.Add(zeile);
        var seite = new StackPanel { Spacing = 12 };
        seite.Children.Add(oben);
        seite.Children.Add(ereignisse);
        return seite;
    }
}

/// <summary>Die Wahlmöglichkeiten und Handgriffe der Seite (beide Fassungen nutzen sie).</summary>
public static class MonthCalendarSeiteHilfe
{
    /// <summary>Die Anordnungen der Monate in der Reihenfolge der Liste (Spalten × Zeilen).</summary>
    public static readonly string[] Monate = ["1 × 1", "2 × 1", "2 × 2", "3 × 2"];

    /// <summary>Die Werte für <c>MaxSelectionCount</c> in der Reihenfolge der Liste.</summary>
    public static readonly string[] Tage = ["1", "7", "14"];

    /// <summary>Wie viele Ereignisse die Seite zeigt.</summary>
    public const int ZeilenZahl = 5;

    /// <summary>Hebt die Feiertage jedes Jahres (1. Januar, 1. August, 25. Dezember) und den 25. jedes Monats (Zahltag) fett hervor.</summary>
    public static void Hervorheben(MonthCalendar kalender)
    {
        kalender.AnnuallyBoldedDates = [new DateTime(2000, 1, 1), new DateTime(2000, 8, 1), new DateTime(2000, 12, 25)];
        kalender.MonthlyBoldedDates = [new DateTime(2000, 1, 25)];
    }

    /// <summary>Die Zeilen der Ereignisliste: die erste sagt, dass noch nichts geschah.</summary>
    public static TextBlock[] NeueZeilen()
    {
        var zeilen = new TextBlock[ZeilenZahl];
        for (int i = 0; i < zeilen.Length; i++) zeilen[i] = new TextBlock { Text = i == 0 ? "Noch keine Ereignisse." : "–" };
        return zeilen;
    }

    public static void WaehleMonate(MonthCalendar kalender, int index)
    {
        var (spalten, zeilen) = index switch { 1 => (2, 1), 2 => (2, 2), 3 => (3, 2), _ => (1, 1) };
        kalender.SetCalendarDimensions(spalten, zeilen);
    }

    public static void WaehleMaxTage(MonthCalendar kalender, int index) => kalender.MaxSelectionCount = index switch { 0 => 1, 2 => 14, _ => 7 };

    /// <summary>Schreibt ein Ereignis an den Anfang der Liste; die älteren rücken nach unten.</summary>
    public static void Protokolliere(MonthCalendar kalender, TextBlock[] zeilen, string art, DateRangeEventArgs e)
    {
        for (int i = zeilen.Length - 1; i > 0; i--) zeilen[i].Text = zeilen[i - 1].Text;
        var format = kalender.Kalenderformat ?? Kalenderformat.Aktuell;
        string von = format.Formatiere(e.Start, format.KurzesDatum);
        zeilen[0].Text = e.Start == e.End ? $"{art}: {von}" : $"{art}: {von} bis {format.Formatiere(e.End, format.KurzesDatum)}";
    }
}