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 f857d521d..b2f7df16d 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 Action> DateChanged; + /// /// Initializes a new instance of using layout. /// @@ -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 { /// 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 +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; } + + /// + /// Virtual method that will invoke the with a . + /// + /// The arguments of the + public virtual void OnDateChanged (DateTimeEventArgs args) + { + DateChanged?.Invoke (args); + } + } + + /// + /// Handled the for or events. + /// + public class DateTimeEventArgs : EventArgs { + /// + /// The old or value. + /// + public T OldValue {get;} + + /// + /// The new or value. + /// + public T 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 (T oldValue, T newValue, string format) + { + OldValue = oldValue; + NewValue = newValue; + Format = format; + } } } \ No newline at end of file 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 3ac52aecb..af5141fd5 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 { + 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; } } + /// + /// 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 Action> TimeChanged; /// /// Initializes a new instance of using positioning. @@ -36,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); @@ -46,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; @@ -56,14 +67,14 @@ 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; 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; + } } /// @@ -80,13 +95,21 @@ namespace Terminal.Gui { /// /// /// - 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 (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; } + + /// + /// Virtual method that will invoke the with a . + /// + /// The arguments of the + public virtual void OnTimeChanged (DateTimeEventArgs 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 d24aab005..26cd87edd 100644 --- a/UICatalog/Scenarios/TimeAndDate.cs +++ b/UICatalog/Scenarios/TimeAndDate.cs @@ -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 e) + { + lblOldTime.Text = $"Old Time: {e.OldValue}"; + lblNewTime.Text = $"New Time: {e.NewValue}"; + lblTimeFmt.Text = $"Time Format: {e.Format}"; + } + + private void DateChanged (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