diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_TextBox.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_TextBox.cs index 7dad622a507e..b769234751b8 100644 --- a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_TextBox.cs +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_TextBox.cs @@ -694,5 +694,63 @@ public async Task When_TextBox_ImeAction_Enter() act.Should().NotThrow(); } #endif + + [TestMethod] +#if __SKIA__ + [Ignore("Fails on Skia")] +#endif + public async Task When_TextBox_Wrap_Custom_Style() + { + var page = new TextBox_Wrapping(); + await UITestHelper.Load(page); + + page.SUT.Text = "Short"; + await WindowHelper.WaitForIdle(); + var height1 = page.SUT.ActualHeight; + + page.SUT.Text = "This is a very very very much longer text. This TextBox should now wrap and have a larger height."; + await WindowHelper.WaitForIdle(); + var height2 = page.SUT.ActualHeight; + + page.SUT.Text = "Short"; + await WindowHelper.WaitForIdle(); + var height3 = page.SUT.ActualHeight; + + Assert.AreEqual(height1, height3); + height2.Should().BeGreaterThan(height1); + } + + [TestMethod] +#if __SKIA__ || __IOS__ + [Ignore("Fails on Skia and iOS")] + // On iOS, the failure is: AssertFailedException: Expected value to be greater than 1199.0, but found 1199.0. + // Since the number is large, it looks like the TextBox is taking the full height. +#endif + public async Task When_TextBox_Wrap_Fluent() + { + var SUT = new TextBox() + { + Width = 200, + TextWrapping = TextWrapping.Wrap, + AcceptsReturn = true, + }; + + await UITestHelper.Load(SUT); + + SUT.Text = "Short"; + await WindowHelper.WaitForIdle(); + var height1 = SUT.ActualHeight; + + SUT.Text = "This is a very very very much longer text. This TextBox should now wrap and have a larger height."; + await WindowHelper.WaitForIdle(); + var height2 = SUT.ActualHeight; + + SUT.Text = "Short"; + await WindowHelper.WaitForIdle(); + var height3 = SUT.ActualHeight; + + Assert.AreEqual(height1, height3); + height2.Should().BeGreaterThan(height1); + } } } diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/TextBox_Wrapping.xaml b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/TextBox_Wrapping.xaml new file mode 100644 index 000000000000..42142fc16f13 --- /dev/null +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/TextBox_Wrapping.xaml @@ -0,0 +1,62 @@ + + + + + + + + + + + diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/TextBox_Wrapping.xaml.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/TextBox_Wrapping.xaml.cs new file mode 100644 index 000000000000..9a6ee2a58ddd --- /dev/null +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/TextBox_Wrapping.xaml.cs @@ -0,0 +1,11 @@ +using Windows.UI.Xaml.Controls; + +namespace Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Controls; + +public sealed partial class TextBox_Wrapping : Page +{ + public TextBox_Wrapping() + { + this.InitializeComponent(); + } +} diff --git a/src/Uno.UI/UI/Xaml/Controls/TextBox/TextBox.Android.cs b/src/Uno.UI/UI/Xaml/Controls/TextBox/TextBox.Android.cs index f327f5249712..f9e5328e5444 100644 --- a/src/Uno.UI/UI/Xaml/Controls/TextBox/TextBox.Android.cs +++ b/src/Uno.UI/UI/Xaml/Controls/TextBox/TextBox.Android.cs @@ -87,12 +87,6 @@ private protected override void OnUnloaded() } } - private protected override void OnLoaded() - { - base.OnLoaded(); - SetupTextBoxView(); - } - partial void InitializePropertiesPartial() { OnImeOptionsChanged(ImeOptions); diff --git a/src/Uno.UI/UI/Xaml/Controls/TextBox/TextBox.cs b/src/Uno.UI/UI/Xaml/Controls/TextBox/TextBox.cs index bc44a19e2d0f..db702244d402 100644 --- a/src/Uno.UI/UI/Xaml/Controls/TextBox/TextBox.cs +++ b/src/Uno.UI/UI/Xaml/Controls/TextBox/TextBox.cs @@ -105,6 +105,46 @@ public TextBox() SizeChanged += OnSizeChanged; } + private protected override void OnLoaded() + { + base.OnLoaded(); + +#if __ANDROID__ + SetupTextBoxView(); +#endif + + // This workaround is added in OnLoaded rather than OnApplyTemplate. + // Apparently, sometimes (e.g, Material style), the TextBox style setters are executed after OnApplyTemplate + // So, the style setters would override what the workaround does. + // OnLoaded appears to be executed after both OnApplyTemplate and after the style setters, making sure the values set here are not modified after. + if (_contentElement is ScrollViewer scrollViewer) + { +#if __IOS__ || __MACOS__ + // We disable scrolling because the inner ITextBoxView provides its own scrolling + scrollViewer.HorizontalScrollMode = ScrollMode.Disabled; + scrollViewer.VerticalScrollMode = ScrollMode.Disabled; + scrollViewer.HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled; + scrollViewer.VerticalScrollBarVisibility = ScrollBarVisibility.Disabled; +#else + // The template of TextBox contains the following: + /* + HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}" + HorizontalScrollMode="{TemplateBinding ScrollViewer.HorizontalScrollMode}" + VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}" + VerticalScrollMode="{TemplateBinding ScrollViewer.VerticalScrollMode}" + */ + // Historically, TemplateBinding for attached DPs wasn't supported, and TextBox worked perfectly fine. + // When support for TemplateBinding for attached DPs was added, TextBox broke (test: TextBox_AutoGrow_Vertically_Wrapping_Test) because of + // change in the values of these properties. The following code serves as a workaround to set the values to what they used to be + // before the support for TemplateBinding for attached DPs. + scrollViewer.HorizontalScrollMode = ScrollMode.Enabled; // The template sets this to Auto + scrollViewer.VerticalScrollMode = ScrollMode.Enabled; // The template sets this to Auto + scrollViewer.HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled; // The template sets this to Hidden + scrollViewer.VerticalScrollBarVisibility = ScrollBarVisibility.Auto; // The template sets this to Hidden +#endif + } + } + internal bool IsUserModifying => _isInputModifyingText || _isInputClearingText; private void OnSizeChanged(object sender, SizeChangedEventArgs args) @@ -156,33 +196,6 @@ protected override void OnApplyTemplate() _contentElement = GetTemplateChild(TextBoxConstants.ContentElementPartName) as ContentControl; _header = GetTemplateChild(TextBoxConstants.HeaderContentPartName) as ContentPresenter; - if (_contentElement is ScrollViewer scrollViewer) - { -#if __IOS__ || __MACOS__ - // We disable scrolling because the inner ITextBoxView provides its own scrolling - scrollViewer.HorizontalScrollMode = ScrollMode.Disabled; - scrollViewer.VerticalScrollMode = ScrollMode.Disabled; - scrollViewer.HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled; - scrollViewer.VerticalScrollBarVisibility = ScrollBarVisibility.Disabled; -#else - // The template of TextBox contains the following: - /* - HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}" - HorizontalScrollMode="{TemplateBinding ScrollViewer.HorizontalScrollMode}" - VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}" - VerticalScrollMode="{TemplateBinding ScrollViewer.VerticalScrollMode}" - */ - // Historically, TemplateBinding for attached DPs wasn't supported, and TextBox worked perfectly fine. - // When support for TemplateBinding for attached DPs was added, TextBox broke (test: TextBox_AutoGrow_Vertically_Wrapping_Test) because of - // change in the values of these properties. The following code serves as a workaround to set the values to what they used to be - // before the support for TemplateBinding for attached DPs. - scrollViewer.HorizontalScrollMode = ScrollMode.Enabled; // The template sets this to Auto - scrollViewer.VerticalScrollMode = ScrollMode.Enabled; // The template sets this to Auto - scrollViewer.HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled; // The template sets this to Hidden - scrollViewer.VerticalScrollBarVisibility = ScrollBarVisibility.Auto; // The template sets this to Hidden -#endif - } - if (GetTemplateChild(TextBoxConstants.DeleteButtonPartName) is Button button) { _deleteButton = new WeakReference