diff --git a/src/Eto.Wpf/Forms/Controls/TextBoxHandler.cs b/src/Eto.Wpf/Forms/Controls/TextBoxHandler.cs index 8a0ebc1d4..6c62a59d3 100755 --- a/src/Eto.Wpf/Forms/Controls/TextBoxHandler.cs +++ b/src/Eto.Wpf/Forms/Controls/TextBoxHandler.cs @@ -1,3 +1,4 @@ +using System.Runtime; using swd = System.Windows.Documents; namespace Eto.Wpf.Forms.Controls { @@ -16,6 +17,14 @@ public class TextBoxHandler : TextBoxHandler + /// Gets or sets the default value indicating to use GC.TryStartNoGCRegion() when setting TextBox.Text + /// to avoid performance issues with WPF. + /// See https://github.com/dotnet/wpf/issues/5887 + /// + public static bool EnableNoGCRegionDefault = true; protected override swc.TextBox TextBox => Control; @@ -58,6 +67,18 @@ public override bool ShowBorder } } + /// + /// Gets or sets a value indicating to use GC.TryStartNoGCRegion() when setting TextBox.Text + /// to avoid performance issues with WPF. + /// See https://github.com/dotnet/wpf/issues/5887 + /// + /// + public bool EnableNoGCRegion + { + get => Widget.Properties.Get(TextBoxHandler.EnableNoGCRegion_Key) ?? TextBoxHandler.EnableNoGCRegionDefault; + set => Widget.Properties.Set(TextBoxHandler.EnableNoGCRegion_Key, value); + } + public TextAlignment TextAlignment { get { return TextBox.TextAlignment.ToEto(); } @@ -285,7 +306,17 @@ public string Text if (args.Cancel) return; var needsTextChanged = TextBox.Text == newText; + + // Improve performance when setting text often + // See https://github.com/dotnet/wpf/issues/5887#issuecomment-1604577981 + if (EnableNoGCRegion) + GC.TryStartNoGCRegion(1000000); // is this magic number reasonable?? + TextBox.Text = newText; + + if (EnableNoGCRegion && GCSettings.LatencyMode == GCLatencyMode.NoGCRegion) + GC.EndNoGCRegion(); + if (needsTextChanged) { Callback.OnTextChanged(Widget, EventArgs.Empty); diff --git a/test/Eto.Test/UnitTests/Forms/Controls/TextBoxTests.cs b/test/Eto.Test/UnitTests/Forms/Controls/TextBoxTests.cs index f3bbe63aa..6b6f0373a 100644 --- a/test/Eto.Test/UnitTests/Forms/Controls/TextBoxTests.cs +++ b/test/Eto.Test/UnitTests/Forms/Controls/TextBoxTests.cs @@ -192,6 +192,55 @@ public void InsertingTextShouldFireTextChanging(string oldText, string newText, Assert.AreEqual(text, args.Text, "#2.4"); Assert.AreEqual(Range.FromLength(rangeStart, rangeLength), args.Range, "#2.5"); } + + [Test, ManualTest] + public void ManyUpdatesShouldNotCauseHangs() + { + TimeSpan maxElapsed = TimeSpan.MinValue; + ManualForm( + "There should not be any pausing", + form => + { + var textBoxes = new List(); + var layout = new DynamicLayout(); + for (int x = 0; x < 10; x++) + { + layout.BeginHorizontal(); + for (int y = 0; y < 10; y++) + { + var textBox = new T(); + textBoxes.Add(textBox); + layout.Add(textBox, true); + } + layout.EndHorizontal(); + } + var sw = new Stopwatch(); + var timer = new UITimer { Interval = 0.01 }; + timer.Elapsed += (sender, e) => + { + var elapsed = sw.Elapsed; + if (elapsed > maxElapsed) + { + maxElapsed = elapsed; + } + sw.Restart(); + var rnd = new Random(); + foreach (var tb in textBoxes) + { + tb.Text = rnd.Next(int.MaxValue).ToString(); + } + }; + form.Shown += (sender, e) => + { + timer.Start(); + sw.Start(); + }; + form.Closed += (sender, e) => timer.Stop(); + + return layout; + }); + Assert.Less(maxElapsed, TimeSpan.FromSeconds(1), "There were long pauses in the UI"); + } }