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“).


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)}";
}
}