diff --git a/Terminal.Gui/Views/NumericUpDown.cs b/Terminal.Gui/Views/NumericUpDown.cs index 44b43c0c2..c36534172 100644 --- a/Terminal.Gui/Views/NumericUpDown.cs +++ b/Terminal.Gui/Views/NumericUpDown.cs @@ -146,7 +146,8 @@ public class NumericUpDown : View where T : notnull /// /// /// and events are raised when the value changes. - /// The event can be canceled the change setting to + /// The event can be canceled the change setting + /// to /// . /// /// @@ -175,6 +176,17 @@ public class NumericUpDown : View where T : notnull } } + /// + /// Raised when the value is about to change. Set to true to prevent the + /// change. + /// + public event EventHandler>? ValueChanging; + + /// + /// Raised when the value has changed. + /// + public event EventHandler>? ValueChanged; + private string _format = "{0}"; /// @@ -191,30 +203,47 @@ public class NumericUpDown : View where T : notnull } _format = value; + + FormatChanged?.Invoke (this, new (value)); SetText (); } } + /// + /// Raised when has changed. + /// + public event EventHandler>? FormatChanged; + private void SetText () { _number.Text = string.Format (Format, _value); Text = _number.Text; } - /// - /// Raised when the value is about to change. Set to true to prevent the - /// change. - /// - public event EventHandler>? ValueChanging; - - /// - /// Raised when the value has changed. - /// - public event EventHandler>? ValueChanged; + private T _increment; /// /// - public T Increment { get; set; } + public T Increment + { + get => _increment; + set + { + if (_increment == (dynamic)value) + { + return; + } + + _increment = value; + + IncrementChanged?.Invoke (this, new (value)); + } + } + + /// + /// Raised when has changed. + /// + public event EventHandler>? IncrementChanged; } /// diff --git a/UICatalog/Scenarios/Buttons.cs b/UICatalog/Scenarios/Buttons.cs index 64da37ca2..8edcdf42b 100644 --- a/UICatalog/Scenarios/Buttons.cs +++ b/UICatalog/Scenarios/Buttons.cs @@ -388,16 +388,16 @@ public class Buttons : Scenario enableCB.Toggle += (s, e) => { repeatButton.Enabled = !repeatButton.Enabled; }; main.Add (label, repeatButton, enableCB); - //var decNumericUpDown = new NumericUpDown - //{ - // Value = 42.11m, - // Increment = 11.31m, - // Format = "{0:0%}", - // X = 0, - // Y = Pos.Bottom (enableCB) + 1, - //}; + var decNumericUpDown = new NumericUpDown + { + Value = 911, + Increment = 1, + Format = "{0:X}", + X = 0, + Y = Pos.Bottom (enableCB) + 1, + }; - //main.Add (decNumericUpDown); + main.Add (decNumericUpDown); main.Ready += (s, e) => { diff --git a/UICatalog/Scenarios/NumericUpDownDemo.cs b/UICatalog/Scenarios/NumericUpDownDemo.cs new file mode 100644 index 000000000..b514a041a --- /dev/null +++ b/UICatalog/Scenarios/NumericUpDownDemo.cs @@ -0,0 +1,291 @@ +#nullable enable +using System; +using Terminal.Gui; + +namespace UICatalog.Scenarios; + +[ScenarioMetadata ("NumericUpDown", "Demonstrates the NumericUpDown View")] +[ScenarioCategory ("Controls")] +public class NumericUpDownDemo : Scenario +{ + public override void Main () + { + Application.Init (); + + Window app = new () + { + Title = GetQuitKeyAndName (), + TabStop = TabBehavior.TabGroup + }; + + var editor = new AdornmentsEditor + { + X = 0, + Y = 0, + AutoSelectViewToEdit = true, + TabStop = TabBehavior.NoStop + }; + app.Add (editor); + + NumericUpDownEditor intEditor = new () + { + X = Pos.Right (editor), + Y = 0, + Title = "int", + }; + + app.Add (intEditor); + + NumericUpDownEditor floatEditor = new () + { + X = Pos.Right (intEditor), + Y = 0, + Title = "float", + }; + app.Add (floatEditor); + + app.Initialized += AppInitialized; + + void AppInitialized (object? sender, EventArgs e) + { + floatEditor!.NumericUpDown!.Increment = 0.1F; + floatEditor!.NumericUpDown!.Format = "{0:0.0}"; + + } + + Application.Run (app); + app.Dispose (); + Application.Shutdown (); + + } + +} + +internal class NumericUpDownEditor : View where T : notnull +{ + private NumericUpDown? _numericUpDown; + + internal NumericUpDown? NumericUpDown + { + get => _numericUpDown; + set + { + if (value == _numericUpDown) + { + return; + } + _numericUpDown = value; + + if (_numericUpDown is { } && _value is { }) + { + _value.Text = _numericUpDown.Text; + } + } + } + + private TextField? _value; + private TextField? _format; + private TextField? _increment; + + internal NumericUpDownEditor () + { + _numericUpDown = null; + Title = "NumericUpDownEditor"; + BorderStyle = LineStyle.Single; + Width = Dim.Auto (DimAutoStyle.Content); + Height = Dim.Auto (DimAutoStyle.Content); + TabStop = TabBehavior.TabGroup; + + Initialized += NumericUpDownEditorInitialized; + + return; + + void NumericUpDownEditorInitialized (object? sender, EventArgs e) + { + Label label = new () + { + Title = "_Value: ", + Width = 12, + }; + label.TextFormatter.Alignment = Alignment.End; + _value = new () + { + X = Pos.Right (label), + Y = Pos.Top (label), + Width = 8, + Title = "Value", + }; + _value.Accept += ValuedOnAccept; + + void ValuedOnAccept (object? sender, EventArgs e) + { + if (_numericUpDown is null) + { + return; + } + + try + { + if (string.IsNullOrEmpty (_value.Text)) + { + // Handle empty or null text if needed + _numericUpDown.Value = default!; + } + else + { + // Parse _value.Text and then convert to type T + _numericUpDown.Value = (T)Convert.ChangeType (_value.Text, typeof (T)); + } + + _value.ColorScheme = SuperView.ColorScheme; + + } + catch (System.FormatException) + { + _value.ColorScheme = Colors.ColorSchemes ["Error"]; + } + catch (InvalidCastException) + { + _value.ColorScheme = Colors.ColorSchemes ["Error"]; + } + finally + { + } + + } + Add (label, _value); + + label = new () + { + Y = Pos.Bottom (_value), + Width = 12, + Title = "_Format: ", + }; + label.TextFormatter.Alignment = Alignment.End; + + _format = new () + { + X = Pos.Right (label), + Y = Pos.Top (label), + Title = "Format", + Width = Dim.Width (_value), + }; + _format.Accept += FormatOnAccept; + + void FormatOnAccept (object? o, EventArgs eventArgs) + { + if (_numericUpDown is null) + { + return; + } + + try + { + // Test format to ensure it's valid + _ = string.Format (_format.Text, _value); + _numericUpDown.Format = _format.Text; + + _format.ColorScheme = SuperView.ColorScheme; + + } + catch (System.FormatException) + { + _format.ColorScheme = Colors.ColorSchemes ["Error"]; + } + catch (InvalidCastException) + { + _format.ColorScheme = Colors.ColorSchemes ["Error"]; + } + finally + { + } + } + + Add (label, _format); + + label = new () + { + Y = Pos.Bottom (_format), + Width = 12, + Title = "_Increment: ", + }; + label.TextFormatter.Alignment = Alignment.End; + _increment = new () + { + X = Pos.Right (label), + Y = Pos.Top (label), + Title = "Increment", + Width = Dim.Width (_value), + }; + + _increment.Accept += IncrementOnAccept; + + void IncrementOnAccept (object? o, EventArgs eventArgs) + { + if (_numericUpDown is null) + { + return; + } + + try + { + if (string.IsNullOrEmpty (_value.Text)) + { + // Handle empty or null text if needed + _numericUpDown.Increment = default!; + } + else + { + // Parse _value.Text and then convert to type T + _numericUpDown.Increment = (T)Convert.ChangeType (_increment.Text, typeof (T)); + } + + _increment.ColorScheme = SuperView.ColorScheme; + + } + catch (System.FormatException) + { + _increment.ColorScheme = Colors.ColorSchemes ["Error"]; + } + catch (InvalidCastException) + { + _increment.ColorScheme = Colors.ColorSchemes ["Error"]; + } + finally + { + } + } + + Add (label, _increment); + + _numericUpDown = new () + { + X = Pos.Center (), + Y = Pos.Bottom (_increment) + 1, + Increment = (dynamic)1, + }; + + _numericUpDown.ValueChanged += NumericUpDownOnValueChanged; + + void NumericUpDownOnValueChanged (object? o, EventArgs eventArgs) + { + _value.Text = _numericUpDown.Text; + } + + _numericUpDown.IncrementChanged += NumericUpDownOnIncrementChanged; + + void NumericUpDownOnIncrementChanged (object? o, EventArgs eventArgs) + { + _increment.Text = _numericUpDown.Increment.ToString (); + } + + Add (_numericUpDown); + + _value.Text = _numericUpDown.Text; + _format.Text = _numericUpDown.Format; + _increment.Text = _numericUpDown.Increment.ToString (); + } + } + + +} diff --git a/UnitTests/Views/NumericUpDownTests.cs b/UnitTests/Views/NumericUpDownTests.cs index 3f9ab4051..103371f19 100644 --- a/UnitTests/Views/NumericUpDownTests.cs +++ b/UnitTests/Views/NumericUpDownTests.cs @@ -1,3 +1,4 @@ +using System.Globalization; using Xunit.Abstractions; namespace Terminal.Gui.ViewsTests; @@ -184,12 +185,39 @@ public class NumericUpDownTests (ITestOutputHelper _output) [InlineData (.75F, "{0:0%}", "75%")] public void Format_decimal (float value, string format, string expectedText) { + CultureInfo currentCulture = CultureInfo.CurrentCulture; + CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; + NumericUpDown numericUpDown = new NumericUpDown (); numericUpDown.Format = format; numericUpDown.Value = value; Assert.Equal (expectedText, numericUpDown.Text); + + CultureInfo.CurrentCulture = currentCulture; + } + + [Theory] + [InlineData (0, "{0}", "0")] + [InlineData (11, "{0}", "11")] + [InlineData (-1, "{0}", "-1")] + [InlineData (911, "{0:X}", "38F")] + [InlineData (911, "0x{0:X04}", "0x038F")] + + public void Format_int (int value, string format, string expectedText) + { + CultureInfo currentCulture = CultureInfo.CurrentCulture; + CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; + + NumericUpDown numericUpDown = new NumericUpDown (); + + numericUpDown.Format = format; + numericUpDown.Value = value; + + Assert.Equal (expectedText, numericUpDown.Text); + + CultureInfo.CurrentCulture = currentCulture; } [Fact] @@ -211,4 +239,5 @@ public class NumericUpDownTests (ITestOutputHelper _output) Assert.Equal (-1, numericUpDown.Value); } + }