Added DateTimeEventArgs and improved data and time validation.

This commit is contained in:
BDisp
2020-06-06 00:31:28 +01:00
parent ba0fb43156
commit f75a511057
3 changed files with 205 additions and 14 deletions

View File

@@ -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 EventHandler<DateTimeEventArgs> DateChanged;
/// <summary>
/// Initializes a new instance of <see cref="DateField"/> using <see cref="LayoutStyle.Absolute"/> layout.
/// </summary>
@@ -64,14 +76,19 @@ namespace Terminal.Gui {
longFormat = GetLongFormat (cultureInfo.DateTimeFormat.ShortDatePattern);
shortFormat = GetShortFormat (longFormat);
CursorPosition = 1;
Date = date;
this.date = date;
Text = date.ToString (Format);
Changed += DateField_Changed;
}
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 +122,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 (oldData, value, Format);
if (oldData != value) {
OnDateChanged (args);
}
}
}
@@ -145,6 +170,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 +203,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 +224,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 +301,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 +341,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 +369,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 args)
{
DateChanged?.Invoke (this, args);
}
}
/// <summary>
/// Handled the <see cref="EventArgs"/> for <see cref="DateField"/> or <see cref="TimeField"/> events.
/// </summary>
public class DateTimeEventArgs : EventArgs {
/// <summary>
/// The old <see cref="DateField"/> or <see cref="TimeField"/> value.
/// </summary>
public DateTime OldValue {get;}
/// <summary>
/// The new <see cref="DateField"/> or <see cref="TimeField"/> value.
/// </summary>
public DateTime 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 (DateTime oldValue, DateTime newValue, string format)
{
OldValue = oldValue;
NewValue = newValue;
Format = format;
}
}
}

View File

@@ -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 {
DateTime 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 EventHandler<DateTimeEventArgs> TimeChanged;
/// <summary>
/// Initializes a new instance of <see cref="TimeField"/> using <see cref="LayoutStyle.Absolute"/> positioning.
@@ -65,14 +76,19 @@ namespace Terminal.Gui {
longFormat = $" HH{sepChar}mm{sepChar}ss";
shortFormat = $" HH{sepChar}mm";
CursorPosition = 1;
Time = time;
this.time = time;
Text = time.ToString (Format);
Changed += TimeField_Changed;
}
void TimeField_Changed (object sender, ustring e)
{
if (!DateTime.TryParseExact (Text.ToString (), Format, CultureInfo.CurrentCulture, DateTimeStyles.None, out DateTime result))
try {
if (!DateTime.TryParseExact (Text.ToString (), Format, CultureInfo.CurrentCulture, DateTimeStyles.None, out DateTime result))
Text = e;
} catch (Exception) {
Text = e;
}
}
/// <summary>
@@ -82,11 +98,19 @@ namespace Terminal.Gui {
/// </remarks>
public DateTime Time {
get {
if (!DateTime.TryParseExact (Text.ToString (), Format, CultureInfo.CurrentCulture, DateTimeStyles.None, out DateTime result)) return new DateTime ();
return result;
return time;
}
set {
if (ReadOnly)
return;
var oldTime = time;
time = value;
this.Text = value.ToString (Format);
var args = new DateTimeEventArgs (oldTime, value, Format);
if (oldTime != value) {
OnTimeChanged (args);
}
}
}
@@ -122,6 +146,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 +182,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 (!DateTime.TryParseExact (t, Format, CultureInfo.CurrentCulture, DateTimeStyles.None, out DateTime result) ||
!isValidTime)
return false;
Time = result;
return true;
}
@@ -191,11 +219,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 +259,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 +287,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 args)
{
TimeChanged?.Invoke (this, args);
}
}
}

View File

@@ -6,6 +6,13 @@ 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) {
@@ -14,6 +21,7 @@ namespace UICatalog {
IsShortFormat = false,
ReadOnly = false,
};
longTime.TimeChanged += TimeChanged;
Win.Add (longTime);
var shortTime = new TimeField (DateTime.Now) {
@@ -22,6 +30,7 @@ namespace UICatalog {
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 (object sender, DateTimeEventArgs e)
{
lblOldTime.Text = $"Old Time: {e.OldValue}";
lblNewTime.Text = $"New Time: {e.NewValue}";
lblTimeFmt.Text = $"Time Format: {e.Format}";
}
private void DateChanged (object sender, DateTimeEventArgs e)
{
lblOldDate.Text = $"Old Date: {e.OldValue}";
lblNewDate.Text = $"New Date: {e.NewValue}";
lblDateFmt.Text = $"Date Format: {e.Format}";
}
}
}