From f75a511057f04363501cbf9e5926e79227e0f443 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 6 Jun 2020 00:31:28 +0100 Subject: [PATCH 1/2] Added DateTimeEventArgs and improved data and time validation. --- Terminal.Gui/Views/DateField.cs | 97 +++++++++++++++++++++++++++--- Terminal.Gui/Views/TimeField.cs | 61 ++++++++++++++++--- UICatalog/Scenarios/TimeAndDate.cs | 61 +++++++++++++++++++ 3 files changed, 205 insertions(+), 14 deletions(-) diff --git a/Terminal.Gui/Views/DateField.cs b/Terminal.Gui/Views/DateField.cs index f857d521d..05c06d72d 100644 --- a/Terminal.Gui/Views/DateField.cs +++ b/Terminal.Gui/Views/DateField.cs @@ -18,6 +18,7 @@ namespace Terminal.Gui { /// The provides date editing functionality with mouse support. /// 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; } } + /// + /// DateChanged event, raised when the Date has changed. + /// + /// + /// This event is raised when the changes. + /// + /// + /// The passed is a containing the old, new value and format. + /// + public event EventHandler DateChanged; + /// /// Initializes a new instance of using layout. /// @@ -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 { /// 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; } + + /// + /// Virtual method that will invoke the with a . + /// + /// The arguments of the + public virtual void OnDateChanged (DateTimeEventArgs args) + { + DateChanged?.Invoke (this, args); + } + } + + /// + /// Handled the for or events. + /// + public class DateTimeEventArgs : EventArgs { + /// + /// The old or value. + /// + public DateTime OldValue {get;} + + /// + /// The new or value. + /// + public DateTime NewValue { get; } + + /// + /// The or format. + /// + public string Format { get; } + + /// + /// Initializes a new instance of + /// + /// The old or value. + /// The new or value. + /// The or format. + public DateTimeEventArgs (DateTime oldValue, DateTime newValue, string format) + { + OldValue = oldValue; + NewValue = newValue; + Format = format; + } } } \ No newline at end of file diff --git a/Terminal.Gui/Views/TimeField.cs b/Terminal.Gui/Views/TimeField.cs index 3ac52aecb..a448d781e 100644 --- a/Terminal.Gui/Views/TimeField.cs +++ b/Terminal.Gui/Views/TimeField.cs @@ -17,6 +17,7 @@ namespace Terminal.Gui { /// The provides time editing functionality with mouse support. /// 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; } } + /// + /// TimeChanged event, raised when the Date has changed. + /// + /// + /// This event is raised when the changes. + /// + /// + /// The passed is a containing the old, new value and format. + /// + public event EventHandler TimeChanged; /// /// Initializes a new instance of using 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; + } } /// @@ -82,11 +98,19 @@ namespace Terminal.Gui { /// 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; } + + /// + /// Virtual method that will invoke the with a . + /// + /// The arguments of the + public virtual void OnTimeChanged (DateTimeEventArgs args) + { + TimeChanged?.Invoke (this, args); + } } } \ No newline at end of file diff --git a/UICatalog/Scenarios/TimeAndDate.cs b/UICatalog/Scenarios/TimeAndDate.cs index d24aab005..e47767be5 100644 --- a/UICatalog/Scenarios/TimeAndDate.cs +++ b/UICatalog/Scenarios/TimeAndDate.cs @@ -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}"; + } } } \ No newline at end of file From d32c631dfddc3c5892d3f83d1bd153331c24f165 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 6 Jun 2020 15:35:07 +0100 Subject: [PATCH 2/2] Changed from DateTime to TimeSpan. Changed from EventHandler to Action. --- Example/demo.cs | 4 ++-- Terminal.Gui/Views/DateField.cs | 19 ++++++++--------- Terminal.Gui/Views/TextField.cs | 4 ++++ Terminal.Gui/Views/TimeField.cs | 33 +++++++++++++++--------------- UICatalog/Scenarios/Text.cs | 7 ++++--- UICatalog/Scenarios/TimeAndDate.cs | 10 ++++----- 6 files changed, 40 insertions(+), 37 deletions(-) diff --git a/Example/demo.cs b/Example/demo.cs index 9d839b8d0..5c161a3ac 100644 --- a/Example/demo.cs +++ b/Example/demo.cs @@ -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, diff --git a/Terminal.Gui/Views/DateField.cs b/Terminal.Gui/Views/DateField.cs index 05c06d72d..b2f7df16d 100644 --- a/Terminal.Gui/Views/DateField.cs +++ b/Terminal.Gui/Views/DateField.cs @@ -38,7 +38,7 @@ namespace Terminal.Gui { /// /// The passed is a containing the old, new value and format. /// - public event EventHandler DateChanged; + public event Action> DateChanged; /// /// Initializes a new instance of using layout. @@ -76,8 +76,7 @@ namespace Terminal.Gui { longFormat = GetLongFormat (cultureInfo.DateTimeFormat.ShortDatePattern); shortFormat = GetShortFormat (longFormat); CursorPosition = 1; - this.date = date; - Text = date.ToString (Format); + Date = date; Changed += DateField_Changed; } @@ -131,7 +130,7 @@ namespace Terminal.Gui { var oldData = date; date = value; this.Text = value.ToString (Format); - var args = new DateTimeEventArgs (oldData, value, Format); + var args = new DateTimeEventArgs (oldData, value, Format); if (oldData != value) { OnDateChanged (args); } @@ -374,25 +373,25 @@ namespace Terminal.Gui { /// Virtual method that will invoke the with a . /// /// The arguments of the - public virtual void OnDateChanged (DateTimeEventArgs args) + public virtual void OnDateChanged (DateTimeEventArgs args) { - DateChanged?.Invoke (this, args); + DateChanged?.Invoke (args); } } /// /// Handled the for or events. /// - public class DateTimeEventArgs : EventArgs { + public class DateTimeEventArgs : EventArgs { /// /// The old or value. /// - public DateTime OldValue {get;} + public T OldValue {get;} /// /// The new or value. /// - public DateTime NewValue { get; } + public T NewValue { get; } /// /// The or format. @@ -405,7 +404,7 @@ namespace Terminal.Gui { /// The old or value. /// The new or value. /// The or format. - public DateTimeEventArgs (DateTime oldValue, DateTime newValue, string format) + public DateTimeEventArgs (T oldValue, T newValue, string format) { OldValue = oldValue; NewValue = newValue; diff --git a/Terminal.Gui/Views/TextField.cs b/Terminal.Gui/Views/TextField.cs index 6252115e3..f390e2a59 100644 --- a/Terminal.Gui/Views/TextField.cs +++ b/Terminal.Gui/Views/TextField.cs @@ -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) diff --git a/Terminal.Gui/Views/TimeField.cs b/Terminal.Gui/Views/TimeField.cs index a448d781e..af5141fd5 100644 --- a/Terminal.Gui/Views/TimeField.cs +++ b/Terminal.Gui/Views/TimeField.cs @@ -17,7 +17,7 @@ namespace Terminal.Gui { /// The provides time editing functionality with mouse support. /// public class TimeField : TextField { - DateTime time; + TimeSpan time; bool isShort; int longFieldLen = 8; @@ -38,7 +38,7 @@ namespace Terminal.Gui { /// /// The passed is a containing the old, new value and format. /// - public event EventHandler TimeChanged; + public event Action> TimeChanged; /// /// Initializes a new instance of using positioning. @@ -47,7 +47,7 @@ namespace Terminal.Gui { /// The y coordinate. /// Initial time. /// If true, the seconds are hidden. Sets the property. - 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); @@ -57,7 +57,7 @@ namespace Terminal.Gui { /// Initializes a new instance of using positioning. /// /// Initial time - public TimeField (DateTime time) : base (string.Empty) + public TimeField (TimeSpan time) : base (string.Empty) { this.isShort = true; Width = FieldLen + 2; @@ -67,24 +67,23 @@ namespace Terminal.Gui { /// /// Initializes a new instance of using positioning. /// - 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; - this.time = time; - Text = time.ToString (Format); + Time = time; Changed += TimeField_Changed; } void TimeField_Changed (object sender, ustring e) { try { - if (!DateTime.TryParseExact (Text.ToString (), Format, CultureInfo.CurrentCulture, DateTimeStyles.None, out DateTime result)) + if (!TimeSpan.TryParseExact (Text.ToString ().Trim (), Format.Trim (), CultureInfo.CurrentCulture, TimeSpanStyles.None, out TimeSpan result)) Text = e; } catch (Exception) { Text = e; @@ -96,7 +95,7 @@ namespace Terminal.Gui { /// /// /// - public DateTime Time { + public TimeSpan Time { get { return time; } @@ -106,8 +105,8 @@ namespace Terminal.Gui { var oldTime = time; time = value; - this.Text = value.ToString (Format); - var args = new DateTimeEventArgs (oldTime, value, Format); + this.Text = " " + value.ToString (Format.Trim ()); + var args = new DateTimeEventArgs (oldTime, value, Format); if (oldTime != value) { OnTimeChanged (args); } @@ -184,7 +183,7 @@ namespace Terminal.Gui { } string t = isShort ? $" {hour,2:00}{sepChar}{minute,2:00}" : $" {hour,2:00}{sepChar}{minute,2:00}{sepChar}{second,2:00}"; - if (!DateTime.TryParseExact (t, 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; @@ -292,9 +291,9 @@ namespace Terminal.Gui { /// Virtual method that will invoke the with a . /// /// The arguments of the - public virtual void OnTimeChanged (DateTimeEventArgs args) + public virtual void OnTimeChanged (DateTimeEventArgs args) { - TimeChanged?.Invoke (this, args); + TimeChanged?.Invoke (args); } } } \ No newline at end of file diff --git a/UICatalog/Scenarios/Text.cs b/UICatalog/Scenarios/Text.cs index a0940c5e2..902106c76 100644 --- a/UICatalog/Scenarios/Text.cs +++ b/UICatalog/Scenarios/Text.cs @@ -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, diff --git a/UICatalog/Scenarios/TimeAndDate.cs b/UICatalog/Scenarios/TimeAndDate.cs index e47767be5..26cd87edd 100644 --- a/UICatalog/Scenarios/TimeAndDate.cs +++ b/UICatalog/Scenarios/TimeAndDate.cs @@ -15,7 +15,7 @@ namespace UICatalog { public override void Setup () { - var longTime = new TimeField (DateTime.Now) { + var longTime = new TimeField (DateTime.Now.TimeOfDay) { X = Pos.Center (), Y = 2, IsShortFormat = false, @@ -24,9 +24,9 @@ namespace UICatalog { 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, }; @@ -106,14 +106,14 @@ namespace UICatalog { }); } - private void TimeChanged (object sender, DateTimeEventArgs e) + private void TimeChanged (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) + private void DateChanged (DateTimeEventArgs e) { lblOldDate.Text = $"Old Date: {e.OldValue}"; lblNewDate.Text = $"New Date: {e.NewValue}";