diff --git a/Terminal.Gui/Views/Selectors/OptionSelector.cs b/Terminal.Gui/Views/Selectors/OptionSelector.cs index a90c8c45d0..45edda196f 100644 --- a/Terminal.Gui/Views/Selectors/OptionSelector.cs +++ b/Terminal.Gui/Views/Selectors/OptionSelector.cs @@ -135,8 +135,14 @@ private void Cycle () /// Updates the checked state of all checkbox subviews so that only the checkbox corresponding /// to the current is checked. /// + /// + /// If doesn't exist in the list of checkbox values, then the first checkbox will be checked by default + /// and will raise the / events. + /// public override void UpdateChecked () { + Dictionary checkBoxValueMap = SubViews.OfType ().ToDictionary (cb => cb, GetCheckBoxValue); + foreach (CheckBox cb in SubViews.OfType ()) { int value = GetCheckBoxValue (cb); @@ -144,6 +150,27 @@ public override void UpdateChecked () cb.Value = value == Value ? CheckState.Checked : CheckState.UnChecked; } + // If Value doesn't exist in any checkbox, use the first checkbox's value + if (Value is not null && checkBoxValueMap.Count > 0 && Values!.All (v => v != Value) && checkBoxValueMap.Values.All (v => v != Value)) + { + Value = checkBoxValueMap.Values.First (); + + foreach (KeyValuePair kvp in checkBoxValueMap) + { + if (kvp.Value != Value) + { + continue; + } + kvp.Key.Value = CheckState.Checked; + + break; + } + + // Sanity checks to verify the assumptions above + Debug.Assert (checkBoxValueMap.Values.First () != (int)SubViews.OfType ().First ().Value); + Debug.Assert (checkBoxValueMap.Values.First () == Values! [0]); + } + // Verify at most one is checked Debug.Assert (SubViews.OfType ().Count (cb => cb.Value == CheckState.Checked) <= 1); } diff --git a/Terminal.Gui/Views/Selectors/SelectorBase.cs b/Terminal.Gui/Views/Selectors/SelectorBase.cs index 49ce2e74c6..1fc8423991 100644 --- a/Terminal.Gui/Views/Selectors/SelectorBase.cs +++ b/Terminal.Gui/Views/Selectors/SelectorBase.cs @@ -156,7 +156,6 @@ public SelectorStyles Styles field = value; CreateSubViews (); - UpdateChecked (); } } @@ -316,7 +315,6 @@ public virtual IReadOnlyList? Values } CreateSubViews (); - UpdateChecked (); } } @@ -331,7 +329,6 @@ public IReadOnlyList? Labels field = value; CreateSubViews (); - UpdateChecked (); } } @@ -436,6 +433,9 @@ public virtual void CreateSubViews () // Note: Hotkey assignment is now handled automatically by the base class // when SubViews are added via Add(). No need to call AssignUniqueHotKeys() here. SetLayout (); + + // Ensure the checked state of the checkboxes is correct after recreating subviews + UpdateChecked (); } /// diff --git a/Tests/UnitTestsParallelizable/Views/SelectorBaseTests.cs b/Tests/UnitTestsParallelizable/Views/SelectorBaseTests.cs index 6d9c6acba0..611032e1f1 100644 --- a/Tests/UnitTestsParallelizable/Views/SelectorBaseTests.cs +++ b/Tests/UnitTestsParallelizable/Views/SelectorBaseTests.cs @@ -598,6 +598,61 @@ public void CreateSubViews_SetsCheckBoxProperties () Assert.True (checkBox.CanFocus); } + [Fact] + public void CreateSubViews_ResetsValue_WhenCurrentValueNotInNewValues () + { + OptionSelector selector = new (); + + selector.Labels = ["Option1", "Option2"]; + selector.Values = [10, 20]; + + // Verify initial value is set to first value (10) + Assert.Equal (10, selector.Value); + + // Change to new labels/values where current value (10) is not present + selector.Labels = ["New1", "New2"]; + selector.Values = [30, 40]; + + // Value should reset to first value (30) + Assert.Equal (30, selector.Value); + } + + [Fact] + public void Setting_TabBehavior_AfterSubViewsAreCreated_DoesNotLoseCheckedState () + { + OptionSelector selector = new () + { + Labels = ["Option1", "Option2"], Values = [10, 20], Orientation = Orientation.Vertical, TabBehavior = TabBehavior.NoStop + }; + + List checkBoxes = [.. selector.SubViews.OfType ()]; + + Assert.Equal (2, checkBoxes.Count); + Assert.Equal (CheckState.Checked, checkBoxes [0].Value); + Assert.Equal (CheckState.UnChecked, checkBoxes [1].Value); + Assert.Equal (10, selector.Value); + Assert.Equal (TabBehavior.NoStop, selector.TabBehavior); + } + + [Fact] + public void Setting_TabBehavior_AfterSubViewsAreCreated_DoesNotLoseCheckedState_With_Enum () + { + OptionSelector selector = new () + { + Orientation = Orientation.Vertical, TabBehavior = TabBehavior.NoStop + }; + + List checkBoxes = [.. selector.SubViews.OfType ()]; + + Assert.Equal (4, checkBoxes.Count); + Assert.Equal (CheckState.Checked, checkBoxes [0].Value); + Assert.Equal (CheckState.UnChecked, checkBoxes [1].Value); + Assert.Equal (CheckState.UnChecked, checkBoxes [2].Value); + Assert.Equal (CheckState.UnChecked, checkBoxes [3].Value); + Assert.Equal (Side.Left, selector.Value); + Assert.Equal (TabBehavior.NoStop, selector.TabBehavior); + } + #endregion #region HotKey Command Tests