diff --git a/Terminal.Gui/Resources/Strings.Designer.cs b/Terminal.Gui/Resources/Strings.Designer.cs
index 3a77922a0..408e14395 100644
--- a/Terminal.Gui/Resources/Strings.Designer.cs
+++ b/Terminal.Gui/Resources/Strings.Designer.cs
@@ -186,6 +186,15 @@ namespace Terminal.Gui.Resources {
}
}
+ ///
+ /// Looks up a localized string similar to Date Picker.
+ ///
+ internal static string dpTitle {
+ get {
+ return ResourceManager.GetString("dpTitle", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Any Files.
///
diff --git a/Terminal.Gui/Resources/Strings.fr-FR.resx b/Terminal.Gui/Resources/Strings.fr-FR.resx
index e6dcfcaea..746c45499 100644
--- a/Terminal.Gui/Resources/Strings.fr-FR.resx
+++ b/Terminal.Gui/Resources/Strings.fr-FR.resx
@@ -177,4 +177,7 @@
Ouvrir
+
+ Sélecteur de Date
+
\ No newline at end of file
diff --git a/Terminal.Gui/Resources/Strings.ja-JP.resx b/Terminal.Gui/Resources/Strings.ja-JP.resx
index 14e5ac1de..4a825c51c 100644
--- a/Terminal.Gui/Resources/Strings.ja-JP.resx
+++ b/Terminal.Gui/Resources/Strings.ja-JP.resx
@@ -273,4 +273,7 @@
{0}で降順ソート (_S)
+
+ 日付ピッカー
+
\ No newline at end of file
diff --git a/Terminal.Gui/Resources/Strings.pt-PT.resx b/Terminal.Gui/Resources/Strings.pt-PT.resx
index bfd60ce15..95cdc8569 100644
--- a/Terminal.Gui/Resources/Strings.pt-PT.resx
+++ b/Terminal.Gui/Resources/Strings.pt-PT.resx
@@ -177,4 +177,7 @@
Abrir
+
+ Seletor de Data
+
\ No newline at end of file
diff --git a/Terminal.Gui/Resources/Strings.resx b/Terminal.Gui/Resources/Strings.resx
index d8159c6e0..1152cfb1d 100644
--- a/Terminal.Gui/Resources/Strings.resx
+++ b/Terminal.Gui/Resources/Strings.resx
@@ -277,4 +277,7 @@
_Sort {0} DESC
+
+ Date Picker
+
\ No newline at end of file
diff --git a/Terminal.Gui/Resources/Strings.zh-Hans.resx b/Terminal.Gui/Resources/Strings.zh-Hans.resx
index da767eaa3..009fdd479 100644
--- a/Terminal.Gui/Resources/Strings.zh-Hans.resx
+++ b/Terminal.Gui/Resources/Strings.zh-Hans.resx
@@ -273,4 +273,7 @@
{0}逆序排序 (_S)
+
+ 日期选择器
+
\ No newline at end of file
diff --git a/Terminal.Gui/Views/DatePicker.cs b/Terminal.Gui/Views/DatePicker.cs
new file mode 100644
index 000000000..109752a7a
--- /dev/null
+++ b/Terminal.Gui/Views/DatePicker.cs
@@ -0,0 +1,241 @@
+//
+// DatePicker.cs: DatePicker control
+//
+// Author: Maciej Winnik
+//
+using System;
+using System.Data;
+using System.Globalization;
+using System.Linq;
+
+namespace Terminal.Gui.Views;
+
+///
+/// The Date Picker.
+///
+public class DatePicker : View {
+
+ private DateField _dateField;
+ private Label _dateLabel;
+ private TableView _calendar;
+ private DataTable _table;
+ private Button _nextMonthButton;
+ private Button _previousMonthButton;
+
+ private DateTime _date = DateTime.Now;
+
+ ///
+ /// Format of date. The default is MM/dd/yyyy.
+ ///
+ public string Format { get; set; } = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern;
+
+ ///
+ /// Get or set the date.
+ ///
+ public DateTime Date {
+ get => _date;
+ set {
+ _date = value;
+ Text = _date.ToString (Format);
+ }
+ }
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ public DatePicker () => SetInitialProperties (_date);
+
+ ///
+ /// Initializes a new instance of with the specified date.
+ ///
+ public DatePicker (DateTime date)
+ {
+ SetInitialProperties (date);
+ }
+
+ ///
+ /// Initializes a new instance of with the specified date and format.
+ ///
+ public DatePicker (DateTime date, string format)
+ {
+ Format = format;
+ SetInitialProperties (date);
+ }
+
+ private void SetInitialProperties (DateTime date)
+ {
+ Title = "Date Picker";
+ BorderStyle = LineStyle.Single;
+ Date = date;
+ _dateLabel = new Label ("Date: ") {
+ X = 0,
+ Y = 0,
+ Height = 1,
+ };
+
+ _dateField = new DateField (DateTime.Now) {
+ X = Pos.Right (_dateLabel),
+ Y = 0,
+ Width = Dim.Fill (1),
+ Height = 1,
+ IsShortFormat = false
+ };
+
+ _calendar = new TableView () {
+ X = 0,
+ Y = Pos.Bottom (_dateLabel),
+ Height = 11,
+ Style = new TableStyle {
+ ShowHeaders = true,
+ ShowHorizontalBottomline = true,
+ ShowVerticalCellLines = true,
+ ExpandLastColumn = true,
+ }
+ };
+
+ _previousMonthButton = new Button (GetBackButtonText ()) {
+ X = Pos.Center () - 4,
+ Y = Pos.Bottom (_calendar) - 1,
+ Height = 1,
+ Width = CalculateCalendarWidth () / 2
+ };
+
+ _previousMonthButton.Clicked += (sender, e) => {
+ Date = _date.AddMonths (-1);
+ CreateCalendar ();
+ _dateField.Date = Date;
+ };
+
+ _nextMonthButton = new Button (GetForwardButtonText ()) {
+ X = Pos.Right (_previousMonthButton) + 2,
+ Y = Pos.Bottom (_calendar) - 1,
+ Height = 1,
+ Width = CalculateCalendarWidth () / 2
+ };
+
+ _nextMonthButton.Clicked += (sender, e) => {
+ Date = _date.AddMonths (1);
+ CreateCalendar ();
+ _dateField.Date = Date;
+ };
+
+ CreateCalendar ();
+ SelectDayOnCalendar (_date.Day);
+
+ _calendar.CellActivated += (sender, e) => {
+ var dayValue = _table.Rows [e.Row] [e.Col];
+ if (dayValue is null) {
+ return;
+ }
+ bool isDay = int.TryParse (dayValue.ToString (), out int day);
+ if (!isDay) {
+ return;
+ }
+ ChangeDayDate (day);
+ SelectDayOnCalendar (day);
+ Text = _date.ToString (Format);
+
+ };
+
+ Width = CalculateCalendarWidth () + 2;
+ Height = _calendar.Height + 3;
+
+ _dateField.DateChanged += DateField_DateChanged;
+
+ Add (_dateLabel, _dateField, _calendar, _previousMonthButton, _nextMonthButton);
+ }
+
+ private void DateField_DateChanged (object sender, DateTimeEventArgs e)
+ {
+ if (e.NewValue.Date.Day != _date.Day) {
+ SelectDayOnCalendar (e.NewValue.Day);
+ }
+ Date = e.NewValue;
+ CreateCalendar ();
+ SelectDayOnCalendar (_date.Day);
+ }
+
+ private void CreateCalendar ()
+ {
+ _calendar.Table = new DataTableSource (_table = CreateDataTable (_date.Month, _date.Year));
+ }
+
+ private void ChangeDayDate (int day)
+ {
+ _date = new DateTime (_date.Year, _date.Month, day);
+ _dateField.Date = _date;
+ CreateCalendar ();
+ }
+
+ private DataTable CreateDataTable (int month, int year)
+ {
+ _table = new DataTable ();
+ GenerateCalendarLabels ();
+ int amountOfDaysInMonth = DateTime.DaysInMonth (year, month);
+ DateTime dateValue = new DateTime (year, month, 1);
+ var dayOfWeek = dateValue.DayOfWeek;
+
+ _table.Rows.Add (new object [6]);
+ for (int i = 1; i <= amountOfDaysInMonth; i++) {
+ _table.Rows [^1] [(int)dayOfWeek] = i;
+ if (dayOfWeek == DayOfWeek.Saturday && i != amountOfDaysInMonth) {
+ _table.Rows.Add (new object [7]);
+ }
+ dayOfWeek = dayOfWeek == DayOfWeek.Saturday ? DayOfWeek.Sunday : dayOfWeek + 1;
+ }
+ int missingRows = 6 - _table.Rows.Count;
+ for (int i = 0; i < missingRows; i++) {
+ _table.Rows.Add (new object [7]);
+ }
+
+ return _table;
+ }
+
+ private void GenerateCalendarLabels ()
+ {
+ _calendar.Style.ColumnStyles.Clear ();
+ for (int i = 0; i < 7; i++) {
+ var abbreviatedDayName = CultureInfo.CurrentCulture.DateTimeFormat.GetAbbreviatedDayName ((DayOfWeek)i);
+ _calendar.Style.ColumnStyles.Add (i, new ColumnStyle () {
+ MaxWidth = abbreviatedDayName.Length,
+ MinWidth = abbreviatedDayName.Length,
+ MinAcceptableWidth = abbreviatedDayName.Length
+ });
+ _table.Columns.Add (abbreviatedDayName);
+ }
+ _calendar.Width = CalculateCalendarWidth ();
+ }
+
+ private int CalculateCalendarWidth ()
+ {
+ return _calendar.Style.ColumnStyles.Sum (c => c.Value.MinWidth) + 7;
+ }
+
+ private void SelectDayOnCalendar (int day)
+ {
+ for (int i = 0; i < _table.Rows.Count; i++) {
+ for (int j = 0; j < _table.Columns.Count; j++) {
+ if (_table.Rows [i] [j].ToString () == day.ToString ()) {
+ _calendar.SetSelection (j, i, false);
+ return;
+ }
+ }
+ }
+ }
+
+ private string GetForwardButtonText () => Glyphs.RightArrow.ToString () + Glyphs.RightArrow.ToString ();
+
+ private string GetBackButtonText () => Glyphs.LeftArrow.ToString () + Glyphs.LeftArrow.ToString ();
+
+ ///
+ protected override void Dispose (bool disposing)
+ {
+ _dateLabel.Dispose ();
+ _calendar.Dispose ();
+ _dateField.Dispose ();
+ _table.Dispose ();
+ _previousMonthButton.Dispose ();
+ _nextMonthButton.Dispose ();
+ base.Dispose (disposing);
+ }
+}
diff --git a/UICatalog/Scenarios/DatePickers.cs b/UICatalog/Scenarios/DatePickers.cs
new file mode 100644
index 000000000..5de075ef0
--- /dev/null
+++ b/UICatalog/Scenarios/DatePickers.cs
@@ -0,0 +1,22 @@
+using Terminal.Gui;
+using Terminal.Gui.Views;
+
+namespace UICatalog.Scenarios;
+[ScenarioMetadata (Name: "Date Picker", Description: "Demonstrates how to use DatePicker class")]
+[ScenarioCategory ("Controls")]
+[ScenarioCategory ("DateTime")]
+public class DatePickers : Scenario {
+
+
+ public override void Setup ()
+ {
+ var datePicker = new DatePicker () {
+ Y = Pos.Center (),
+ X = Pos.Center ()
+ };
+
+
+ Win.Add (datePicker);
+ }
+}
+
diff --git a/UnitTests/Views/AllViewsTests.cs b/UnitTests/Views/AllViewsTests.cs
index c743d276a..2b1229e6a 100644
--- a/UnitTests/Views/AllViewsTests.cs
+++ b/UnitTests/Views/AllViewsTests.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
+using Terminal.Gui.Views;
using Xunit;
using Xunit.Abstractions;
@@ -115,6 +116,10 @@ public class AllViewsTests {
if (vType is TextView) {
top.NewKeyDownEvent (new (KeyCode.Tab | KeyCode.CtrlMask));
+ } else if (vType is DatePicker) {
+ for (int i = 0; i < 4; i++) {
+ top.NewKeyDownEvent (new (KeyCode.Tab | KeyCode.CtrlMask));
+ }
} else {
top.NewKeyDownEvent (new (KeyCode.Tab));
}
diff --git a/UnitTests/Views/DatePickerTests.cs b/UnitTests/Views/DatePickerTests.cs
new file mode 100644
index 000000000..8685af417
--- /dev/null
+++ b/UnitTests/Views/DatePickerTests.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Globalization;
+using Terminal.Gui.Views;
+using Xunit;
+
+namespace Terminal.Gui.ViewsTests;
+
+public class DatePickerTests {
+
+ [Fact]
+ public void DatePicker_SetFormat_ShouldChangeFormat ()
+ {
+ var datePicker = new DatePicker {
+ Format = "dd/MM/yyyy"
+ };
+ Assert.Equal ("dd/MM/yyyy", datePicker.Format);
+ }
+
+ [Fact]
+ public void DatePicker_Initialize_ShouldSetCurrentDate ()
+ {
+ var datePicker = new DatePicker ();
+ var format = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern;
+ Assert.Equal (DateTime.Now.ToString (format), datePicker.Text);
+ }
+
+ [Fact]
+ public void DatePicker_SetDate_ShouldChangeText ()
+ {
+ var datePicker = new DatePicker ();
+ var newDate = new DateTime (2024, 1, 15);
+ var format = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern;
+
+ datePicker.Date = newDate;
+ Assert.Equal (newDate.ToString (format), datePicker.Text);
+ }
+
+ [Fact]
+ public void DatePicker_ShowDatePickerDialog_ShouldChangeDate ()
+ {
+ var datePicker = new DatePicker ();
+ var format = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern;
+ var originalDate = datePicker.Date;
+
+ datePicker.MouseEvent (new MouseEvent () { Flags = MouseFlags.Button1Clicked, X = 4, Y = 1 });
+
+ var newDate = new DateTime (2024, 2, 20);
+ datePicker.Date = newDate;
+
+ Assert.Equal (newDate.ToString (format), datePicker.Text);
+
+ datePicker.Date = originalDate;
+ }
+}