mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-31 02:08:03 +01:00
Merge pull request #628 from BDisp/date-time-field
Nicely done. Added DateTimeEventArgs and improved data and time validation.
This commit is contained in:
@@ -183,8 +183,8 @@ static class Demo {
|
||||
scrollView2,
|
||||
tf,
|
||||
new Button (10, 19, "Cancel"),
|
||||
new TimeField (3, 20, DateTime.Now),
|
||||
new TimeField (23, 20, DateTime.Now, true),
|
||||
new TimeField (3, 20, DateTime.Now.TimeOfDay),
|
||||
new TimeField (23, 20, DateTime.Now.TimeOfDay, true),
|
||||
new DateField (3, 22, DateTime.Now),
|
||||
new DateField (23, 22, DateTime.Now, true),
|
||||
progress,
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace Terminal.Gui {
|
||||
/// The <see cref="DateField"/> <see cref="View"/> provides date editing functionality with mouse support.
|
||||
/// </remarks>
|
||||
public class DateField : TextField {
|
||||
DateTime date;
|
||||
bool isShort;
|
||||
int longFieldLen = 10;
|
||||
int shortFieldLen = 8;
|
||||
@@ -28,6 +29,17 @@ namespace Terminal.Gui {
|
||||
int FieldLen { get { return isShort ? shortFieldLen : longFieldLen; } }
|
||||
string Format { get { return isShort ? shortFormat : longFormat; } }
|
||||
|
||||
/// <summary>
|
||||
/// DateChanged event, raised when the Date has changed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This event is raised when the <see cref="Date"/> changes.
|
||||
/// </remarks>
|
||||
/// <remarks>
|
||||
/// The passed <see cref="EventArgs"/> is a <see cref="DateTimeEventArgs"/> containing the old, new value and format.
|
||||
/// </remarks>
|
||||
public event Action<DateTimeEventArgs<DateTime>> DateChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="DateField"/> using <see cref="LayoutStyle.Absolute"/> layout.
|
||||
/// </summary>
|
||||
@@ -70,8 +82,12 @@ namespace Terminal.Gui {
|
||||
|
||||
void DateField_Changed (object sender, ustring e)
|
||||
{
|
||||
if (!DateTime.TryParseExact (GetDate (Text).ToString (), GetInvarianteFormat (), CultureInfo.CurrentCulture, DateTimeStyles.None, out DateTime result))
|
||||
try {
|
||||
if (!DateTime.TryParseExact (GetDate (Text).ToString (), GetInvarianteFormat (), CultureInfo.CurrentCulture, DateTimeStyles.None, out DateTime result))
|
||||
Text = e;
|
||||
} catch (Exception) {
|
||||
Text = e;
|
||||
}
|
||||
}
|
||||
|
||||
string GetInvarianteFormat ()
|
||||
@@ -105,11 +121,19 @@ namespace Terminal.Gui {
|
||||
/// </remarks>
|
||||
public DateTime Date {
|
||||
get {
|
||||
if (!DateTime.TryParseExact (Text.ToString (), Format, CultureInfo.CurrentCulture, DateTimeStyles.None, out DateTime result)) return new DateTime ();
|
||||
return result;
|
||||
return date;
|
||||
}
|
||||
set {
|
||||
if (ReadOnly)
|
||||
return;
|
||||
|
||||
var oldData = date;
|
||||
date = value;
|
||||
this.Text = value.ToString (Format);
|
||||
var args = new DateTimeEventArgs<DateTime> (oldData, value, Format);
|
||||
if (oldData != value) {
|
||||
OnDateChanged (args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,6 +169,10 @@ namespace Terminal.Gui {
|
||||
|
||||
bool SetText (ustring text)
|
||||
{
|
||||
if (text.IsEmpty) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ustring [] vals = text.Split (ustring.Make (sepChar));
|
||||
ustring [] frm = ustring.Make (Format).Split (ustring.Make (sepChar));
|
||||
bool isValidDate = true;
|
||||
@@ -174,12 +202,12 @@ namespace Terminal.Gui {
|
||||
vals [idx] = day.ToString ();
|
||||
} else
|
||||
day = Int32.Parse (vals [idx].ToString ());
|
||||
string date = GetDate (month, day, year, frm);
|
||||
Text = date;
|
||||
string d = GetDate (month, day, year, frm);
|
||||
|
||||
if (!DateTime.TryParseExact (date, Format, CultureInfo.CurrentCulture, DateTimeStyles.None, out DateTime result) ||
|
||||
if (!DateTime.TryParseExact (d, Format, CultureInfo.CurrentCulture, DateTimeStyles.None, out DateTime result) ||
|
||||
!isValidDate)
|
||||
return false;
|
||||
Date = result;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -195,6 +223,8 @@ namespace Terminal.Gui {
|
||||
if (!isShort && year.ToString ().Length == 2) {
|
||||
var y = DateTime.Now.Year.ToString ();
|
||||
date += y.Substring (0, 2) + year.ToString ();
|
||||
} else if (isShort && year.ToString ().Length == 4) {
|
||||
date += $"{year.ToString ().Substring (2, 2)}";
|
||||
} else {
|
||||
date += $"{year,2:00}";
|
||||
}
|
||||
@@ -270,11 +300,17 @@ namespace Terminal.Gui {
|
||||
switch (kb.Key) {
|
||||
case Key.DeleteChar:
|
||||
case Key.ControlD:
|
||||
if (ReadOnly)
|
||||
return true;
|
||||
|
||||
SetText ('0');
|
||||
break;
|
||||
|
||||
case Key.Delete:
|
||||
case Key.Backspace:
|
||||
if (ReadOnly)
|
||||
return true;
|
||||
|
||||
SetText ('0');
|
||||
DecCursorPosition ();
|
||||
break;
|
||||
@@ -304,6 +340,10 @@ namespace Terminal.Gui {
|
||||
// Ignore non-numeric characters.
|
||||
if (kb.Key < (Key)((int)'0') || kb.Key > (Key)((int)'9'))
|
||||
return false;
|
||||
|
||||
if (ReadOnly)
|
||||
return true;
|
||||
|
||||
if (SetText (TextModel.ToRunes (ustring.Make ((uint)kb.Key)).First ()))
|
||||
IncCursorPosition ();
|
||||
return true;
|
||||
@@ -328,5 +368,47 @@ namespace Terminal.Gui {
|
||||
AdjCursorPosition ();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Virtual method that will invoke the <see cref="DateChanged"/> with a <see cref="DateTimeEventArgs"/>.
|
||||
/// </summary>
|
||||
/// <param name="args">The arguments of the <see cref="DateTimeEventArgs"/></param>
|
||||
public virtual void OnDateChanged (DateTimeEventArgs<DateTime> args)
|
||||
{
|
||||
DateChanged?.Invoke (args);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handled the <see cref="EventArgs"/> for <see cref="DateField"/> or <see cref="TimeField"/> events.
|
||||
/// </summary>
|
||||
public class DateTimeEventArgs<T> : EventArgs {
|
||||
/// <summary>
|
||||
/// The old <see cref="DateField"/> or <see cref="TimeField"/> value.
|
||||
/// </summary>
|
||||
public T OldValue {get;}
|
||||
|
||||
/// <summary>
|
||||
/// The new <see cref="DateField"/> or <see cref="TimeField"/> value.
|
||||
/// </summary>
|
||||
public T NewValue { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="DateField"/> or <see cref="TimeField"/> format.
|
||||
/// </summary>
|
||||
public string Format { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="DateTimeEventArgs"/>
|
||||
/// </summary>
|
||||
/// <param name="oldValue">The old <see cref="DateField"/> or <see cref="TimeField"/> value.</param>
|
||||
/// <param name="newValue">The new <see cref="DateField"/> or <see cref="TimeField"/> value.</param>
|
||||
/// <param name="format">The <see cref="DateField"/> or <see cref="TimeField"/> format.</param>
|
||||
public DateTimeEventArgs (T oldValue, T newValue, string format)
|
||||
{
|
||||
OldValue = oldValue;
|
||||
NewValue = newValue;
|
||||
Format = format;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -132,6 +132,10 @@ namespace Terminal.Gui {
|
||||
return;
|
||||
|
||||
var oldText = ustring.Make (text);
|
||||
|
||||
if (oldText == value)
|
||||
return;
|
||||
|
||||
text = TextModel.ToRunes (value);
|
||||
if (!Secret && !isFromHistory) {
|
||||
if (historyText == null)
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace Terminal.Gui {
|
||||
/// The <see cref="TimeField"/> <see cref="View"/> provides time editing functionality with mouse support.
|
||||
/// </remarks>
|
||||
public class TimeField : TextField {
|
||||
TimeSpan time;
|
||||
bool isShort;
|
||||
|
||||
int longFieldLen = 8;
|
||||
@@ -28,6 +29,16 @@ namespace Terminal.Gui {
|
||||
int FieldLen { get { return isShort ? shortFieldLen : longFieldLen; } }
|
||||
string Format { get { return isShort ? shortFormat : longFormat; } }
|
||||
|
||||
/// <summary>
|
||||
/// TimeChanged event, raised when the Date has changed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This event is raised when the <see cref="Time"/> changes.
|
||||
/// </remarks>
|
||||
/// <remarks>
|
||||
/// The passed <see cref="EventArgs"/> is a <see cref="DateTimeEventArgs"/> containing the old, new value and format.
|
||||
/// </remarks>
|
||||
public event Action<DateTimeEventArgs<TimeSpan>> TimeChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="TimeField"/> using <see cref="LayoutStyle.Absolute"/> positioning.
|
||||
@@ -36,7 +47,7 @@ namespace Terminal.Gui {
|
||||
/// <param name="y">The y coordinate.</param>
|
||||
/// <param name="time">Initial time.</param>
|
||||
/// <param name="isShort">If true, the seconds are hidden. Sets the <see cref="IsShortFormat"/> property.</param>
|
||||
public TimeField (int x, int y, DateTime time, bool isShort = false) : base (x, y, isShort ? 7 : 10, "")
|
||||
public TimeField (int x, int y, TimeSpan time, bool isShort = false) : base (x, y, isShort ? 7 : 10, "")
|
||||
{
|
||||
this.isShort = isShort;
|
||||
Initialize (time);
|
||||
@@ -46,7 +57,7 @@ namespace Terminal.Gui {
|
||||
/// Initializes a new instance of <see cref="TimeField"/> using <see cref="LayoutStyle.Computed"/> positioning.
|
||||
/// </summary>
|
||||
/// <param name="time">Initial time</param>
|
||||
public TimeField (DateTime time) : base (string.Empty)
|
||||
public TimeField (TimeSpan time) : base (string.Empty)
|
||||
{
|
||||
this.isShort = true;
|
||||
Width = FieldLen + 2;
|
||||
@@ -56,14 +67,14 @@ namespace Terminal.Gui {
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="TimeField"/> using <see cref="LayoutStyle.Computed"/> positioning.
|
||||
/// </summary>
|
||||
public TimeField () : this (time: DateTime.MinValue) { }
|
||||
public TimeField () : this (time: TimeSpan.MinValue) { }
|
||||
|
||||
void Initialize (DateTime time)
|
||||
void Initialize (TimeSpan time)
|
||||
{
|
||||
CultureInfo cultureInfo = CultureInfo.CurrentCulture;
|
||||
sepChar = cultureInfo.DateTimeFormat.TimeSeparator;
|
||||
longFormat = $" HH{sepChar}mm{sepChar}ss";
|
||||
shortFormat = $" HH{sepChar}mm";
|
||||
longFormat = $" hh\\{sepChar}mm\\{sepChar}ss";
|
||||
shortFormat = $" hh\\{sepChar}mm";
|
||||
CursorPosition = 1;
|
||||
Time = time;
|
||||
Changed += TimeField_Changed;
|
||||
@@ -71,8 +82,12 @@ namespace Terminal.Gui {
|
||||
|
||||
void TimeField_Changed (object sender, ustring e)
|
||||
{
|
||||
if (!DateTime.TryParseExact (Text.ToString (), Format, CultureInfo.CurrentCulture, DateTimeStyles.None, out DateTime result))
|
||||
try {
|
||||
if (!TimeSpan.TryParseExact (Text.ToString ().Trim (), Format.Trim (), CultureInfo.CurrentCulture, TimeSpanStyles.None, out TimeSpan result))
|
||||
Text = e;
|
||||
} catch (Exception) {
|
||||
Text = e;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -80,13 +95,21 @@ namespace Terminal.Gui {
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// </remarks>
|
||||
public DateTime Time {
|
||||
public TimeSpan Time {
|
||||
get {
|
||||
if (!DateTime.TryParseExact (Text.ToString (), Format, CultureInfo.CurrentCulture, DateTimeStyles.None, out DateTime result)) return new DateTime ();
|
||||
return result;
|
||||
return time;
|
||||
}
|
||||
set {
|
||||
this.Text = value.ToString (Format);
|
||||
if (ReadOnly)
|
||||
return;
|
||||
|
||||
var oldTime = time;
|
||||
time = value;
|
||||
this.Text = " " + value.ToString (Format.Trim ());
|
||||
var args = new DateTimeEventArgs<TimeSpan> (oldTime, value, Format);
|
||||
if (oldTime != value) {
|
||||
OnTimeChanged (args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,6 +145,10 @@ namespace Terminal.Gui {
|
||||
|
||||
bool SetText (ustring text)
|
||||
{
|
||||
if (text.IsEmpty) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ustring [] vals = text.Split (ustring.Make (sepChar));
|
||||
bool isValidTime = true;
|
||||
int hour = Int32.Parse (vals [0].ToString ());
|
||||
@@ -154,12 +181,12 @@ namespace Terminal.Gui {
|
||||
second = 59;
|
||||
vals [2] = "59";
|
||||
}
|
||||
string time = isShort ? $" {hour,2:00}{sepChar}{minute,2:00}" : $" {hour,2:00}{sepChar}{minute,2:00}{sepChar}{second,2:00}";
|
||||
Text = time;
|
||||
string t = isShort ? $" {hour,2:00}{sepChar}{minute,2:00}" : $" {hour,2:00}{sepChar}{minute,2:00}{sepChar}{second,2:00}";
|
||||
|
||||
if (!DateTime.TryParseExact (text.ToString (), Format, CultureInfo.CurrentCulture, DateTimeStyles.None, out DateTime result) ||
|
||||
if (!TimeSpan.TryParseExact (t.Trim (), Format.Trim (), CultureInfo.CurrentCulture, TimeSpanStyles.None, out TimeSpan result) ||
|
||||
!isValidTime)
|
||||
return false;
|
||||
Time = result;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -191,11 +218,17 @@ namespace Terminal.Gui {
|
||||
switch (kb.Key) {
|
||||
case Key.DeleteChar:
|
||||
case Key.ControlD:
|
||||
if (ReadOnly)
|
||||
return true;
|
||||
|
||||
SetText ('0');
|
||||
break;
|
||||
|
||||
case Key.Delete:
|
||||
case Key.Backspace:
|
||||
if (ReadOnly)
|
||||
return true;
|
||||
|
||||
SetText ('0');
|
||||
DecCursorPosition ();
|
||||
break;
|
||||
@@ -225,6 +258,10 @@ namespace Terminal.Gui {
|
||||
// Ignore non-numeric characters.
|
||||
if (kb.Key < (Key)((int)'0') || kb.Key > (Key)((int)'9'))
|
||||
return false;
|
||||
|
||||
if (ReadOnly)
|
||||
return true;
|
||||
|
||||
if (SetText (TextModel.ToRunes (ustring.Make ((uint)kb.Key)).First ()))
|
||||
IncCursorPosition ();
|
||||
return true;
|
||||
@@ -249,5 +286,14 @@ namespace Terminal.Gui {
|
||||
AdjCursorPosition ();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Virtual method that will invoke the <see cref="TimeChanged"/> with a <see cref="DateTimeEventArgs"/>.
|
||||
/// </summary>
|
||||
/// <param name="args">The arguments of the <see cref="DateTimeEventArgs"/></param>
|
||||
public virtual void OnTimeChanged (DateTimeEventArgs<TimeSpan> args)
|
||||
{
|
||||
TimeChanged?.Invoke (args);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Text;
|
||||
using System;
|
||||
using System.Text;
|
||||
using Terminal.Gui;
|
||||
|
||||
namespace UICatalog {
|
||||
@@ -45,8 +46,8 @@ namespace UICatalog {
|
||||
};
|
||||
Win.Add (dateField);
|
||||
|
||||
var timeField = new TimeField (System.DateTime.Now) {
|
||||
X = Pos.Right(dateField) + 5,
|
||||
var timeField = new TimeField (DateTime.Now.TimeOfDay) {
|
||||
X = Pos.Right (dateField) + 5,
|
||||
Y = Pos.Bottom (hexView) + 1,
|
||||
Width = Dim.Percent (40),
|
||||
ColorScheme = Colors.Dialog,
|
||||
|
||||
@@ -6,22 +6,31 @@ namespace UICatalog {
|
||||
[ScenarioCategory ("Controls")]
|
||||
[ScenarioCategory ("Bug Repro")] // Issue #246
|
||||
class TimeAndDate : Scenario {
|
||||
Label lblOldTime;
|
||||
Label lblNewTime;
|
||||
Label lblTimeFmt;
|
||||
Label lblOldDate;
|
||||
Label lblNewDate;
|
||||
Label lblDateFmt;
|
||||
|
||||
public override void Setup ()
|
||||
{
|
||||
var longTime = new TimeField (DateTime.Now) {
|
||||
var longTime = new TimeField (DateTime.Now.TimeOfDay) {
|
||||
X = Pos.Center (),
|
||||
Y = 2,
|
||||
IsShortFormat = false,
|
||||
ReadOnly = false,
|
||||
};
|
||||
longTime.TimeChanged += TimeChanged;
|
||||
Win.Add (longTime);
|
||||
|
||||
var shortTime = new TimeField (DateTime.Now) {
|
||||
var shortTime = new TimeField (DateTime.Now.TimeOfDay) {
|
||||
X = Pos.Center (),
|
||||
Y = Pos.Bottom(longTime) + 1,
|
||||
Y = Pos.Bottom (longTime) + 1,
|
||||
IsShortFormat = true,
|
||||
ReadOnly = false,
|
||||
};
|
||||
shortTime.TimeChanged += TimeChanged;
|
||||
Win.Add (shortTime);
|
||||
|
||||
var shortDate = new DateField (DateTime.Now) {
|
||||
@@ -30,6 +39,7 @@ namespace UICatalog {
|
||||
IsShortFormat = true,
|
||||
ReadOnly = true,
|
||||
};
|
||||
shortDate.DateChanged += DateChanged;
|
||||
Win.Add (shortDate);
|
||||
|
||||
var longDate = new DateField (DateTime.Now) {
|
||||
@@ -38,8 +48,45 @@ namespace UICatalog {
|
||||
IsShortFormat = false,
|
||||
ReadOnly = true,
|
||||
};
|
||||
longDate.DateChanged += DateChanged;
|
||||
Win.Add (longDate);
|
||||
|
||||
lblOldTime = new Label ("Old Time: ") {
|
||||
X = Pos.Center (),
|
||||
Y = Pos.Bottom (longDate) + 1
|
||||
};
|
||||
Win.Add (lblOldTime);
|
||||
|
||||
lblNewTime = new Label ("New Time: ") {
|
||||
X = Pos.Center (),
|
||||
Y = Pos.Bottom (lblOldTime) + 1
|
||||
};
|
||||
Win.Add (lblNewTime);
|
||||
|
||||
lblTimeFmt = new Label ("Time Format: ") {
|
||||
X = Pos.Center (),
|
||||
Y = Pos.Bottom (lblNewTime) + 1
|
||||
};
|
||||
Win.Add (lblTimeFmt);
|
||||
|
||||
lblOldDate = new Label ("Old Date: ") {
|
||||
X = Pos.Center (),
|
||||
Y = Pos.Bottom (lblTimeFmt) + 2
|
||||
};
|
||||
Win.Add (lblOldDate);
|
||||
|
||||
lblNewDate = new Label ("New Date: ") {
|
||||
X = Pos.Center (),
|
||||
Y = Pos.Bottom (lblOldDate) + 1
|
||||
};
|
||||
Win.Add (lblNewDate);
|
||||
|
||||
lblDateFmt = new Label ("Date Format: ") {
|
||||
X = Pos.Center (),
|
||||
Y = Pos.Bottom (lblNewDate) + 1
|
||||
};
|
||||
Win.Add (lblDateFmt);
|
||||
|
||||
Win.Add (new Button ("Swap Long/Short & Read/Read Only") {
|
||||
X = Pos.Center (),
|
||||
Y = Pos.Bottom (Win) - 5,
|
||||
@@ -58,5 +105,19 @@ namespace UICatalog {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void TimeChanged (DateTimeEventArgs<TimeSpan> e)
|
||||
{
|
||||
lblOldTime.Text = $"Old Time: {e.OldValue}";
|
||||
lblNewTime.Text = $"New Time: {e.NewValue}";
|
||||
lblTimeFmt.Text = $"Time Format: {e.Format}";
|
||||
}
|
||||
|
||||
private void DateChanged (DateTimeEventArgs<DateTime> e)
|
||||
{
|
||||
lblOldDate.Text = $"Old Date: {e.OldValue}";
|
||||
lblNewDate.Text = $"New Date: {e.NewValue}";
|
||||
lblDateFmt.Text = $"Date Format: {e.Format}";
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user