浏览代码

Added MinLines property and related tests to the TextBox control. (#14313)

Co-authored-by: Andre118 <[email protected]>
Andre118 1 年之前
父节点
当前提交
82809b5220
共有 2 个文件被更改,包括 157 次插入10 次删除
  1. 45 10
      src/Avalonia.Controls/TextBox.cs
  2. 112 0
      tests/Avalonia.Controls.UnitTests/TextBoxTests.cs

+ 45 - 10
src/Avalonia.Controls/TextBox.cs

@@ -124,6 +124,12 @@ namespace Avalonia.Controls
         public static readonly StyledProperty<int> MaxLinesProperty =
             AvaloniaProperty.Register<TextBox, int>(nameof(MaxLines));
 
+        /// <summary>
+        /// Defines the <see cref="MinLines"/> property
+        /// </summary>
+        public static readonly StyledProperty<int> MinLinesProperty =
+            AvaloniaProperty.Register<TextBox, int>(nameof(MinLines));
+
         /// <summary>
         /// Defines the <see cref="Text"/> property
         /// </summary>
@@ -519,6 +525,15 @@ namespace Avalonia.Controls
             set => SetValue(MaxLinesProperty, value);
         }
 
+        /// <summary>
+        /// Gets or sets the minimum number of visible lines to size to.
+        /// </summary>
+        public int MinLines
+        {
+            get => GetValue(MinLinesProperty);
+            set => SetValue(MinLinesProperty, value);
+        }
+
         /// <summary>
         /// Gets or sets the spacing between characters
         /// </summary>
@@ -913,6 +928,10 @@ namespace Avalonia.Controls
             {
                 InvalidateMeasure();
             }
+            else if (change.Property == MinLinesProperty)
+            {
+                InvalidateMeasure();
+            }
             else if (change.Property == UndoLimitProperty)
             {
                 OnUndoLimitChanged(change.GetNewValue<int>());
@@ -1836,7 +1855,7 @@ namespace Avalonia.Controls
                 }
 
                 SetCurrentValue(SelectionEndProperty, SelectionEnd + offset);
-                
+
                 if (moveCaretPosition)
                 {
                     _presenter.MoveCaretToTextPosition(SelectionEnd);
@@ -2034,7 +2053,7 @@ namespace Avalonia.Controls
 
                     var margin = visual.GetValue<Thickness>(Layoutable.MarginProperty);
                     var padding = visual.GetValue<Thickness>(Decorator.PaddingProperty);
-                    
+
                     verticalSpace += margin.Top + padding.Top + padding.Bottom + margin.Bottom;
 
                     visual = visual.VisualParent;
@@ -2073,8 +2092,8 @@ namespace Avalonia.Controls
             var selectionStart = CaretIndex;
 
             MoveHorizontal(-1, true, false, false);
-            
-            if (SelectionEnd > 0 && 
+
+            if (SelectionEnd > 0 &&
                 selectionStart < text.Length && text[selectionStart] == ' ')
             {
                 SetCurrentValue(SelectionEndProperty, SelectionEnd - 1);
@@ -2203,30 +2222,46 @@ namespace Avalonia.Controls
                     var fontSize = FontSize;
                     var typeface = new Typeface(FontFamily, FontStyle, FontWeight, FontStretch);
                     var paragraphProperties = TextLayout.CreateTextParagraphProperties(typeface, fontSize, null, default, default, null, default, LineHeight, default);
-                    var textLayout = new TextLayout(new MaxLinesTextSource(MaxLines), paragraphProperties);
+                    var textLayout = new TextLayout(new LineTextSource(MaxLines), paragraphProperties);
                     var verticalSpace = GetVerticalSpaceBetweenScrollViewerAndPresenter();
 
                     maxHeight = Math.Ceiling(textLayout.Height + verticalSpace);
                 }
 
                 _scrollViewer.SetCurrentValue(MaxHeightProperty, maxHeight);
+
+
+                var minHeight = 0.0;
+
+                if (MinLines > 0 && double.IsNaN(Height))
+                {
+                    var fontSize = FontSize;
+                    var typeface = new Typeface(FontFamily, FontStyle, FontWeight, FontStretch);
+                    var paragraphProperties = TextLayout.CreateTextParagraphProperties(typeface, fontSize, null, default, default, null, default, LineHeight, default);
+                    var textLayout = new TextLayout(new LineTextSource(MinLines), paragraphProperties);
+                    var verticalSpace = GetVerticalSpaceBetweenScrollViewerAndPresenter();
+
+                    minHeight = Math.Ceiling(textLayout.Height + verticalSpace);
+                }
+
+                _scrollViewer.SetCurrentValue(MinHeightProperty, minHeight);
             }
 
             return base.MeasureOverride(availableSize);
         }
 
-        private class MaxLinesTextSource : ITextSource
+        private class LineTextSource : ITextSource
         {
-            private readonly int _maxLines;
+            private readonly int _lines;
 
-            public MaxLinesTextSource(int maxLines)
+            public LineTextSource(int lines)
             {
-                _maxLines = maxLines;
+                _lines = lines;
             }
 
             public TextRun? GetTextRun(int textSourceIndex)
             {
-                if (textSourceIndex >= _maxLines)
+                if (textSourceIndex >= _lines)
                 {
                     return null;
                 }

+ 112 - 0
tests/Avalonia.Controls.UnitTests/TextBoxTests.cs

@@ -1066,6 +1066,118 @@ namespace Avalonia.Controls.UnitTests
             }
         }
 
+        [Fact]
+        public void Should_Fullfill_MinLines_Contraint()
+        {
+            using (UnitTestApplication.Start(Services))
+            {
+                var target = new TextBox
+                {
+                    Template = CreateTemplate(),
+                    Text = "ABC \n DEF \n GHI",
+                    MinLines = 3,
+                    AcceptsReturn = true
+                };
+
+                var impl = CreateMockTopLevelImpl();
+                var topLevel = new TestTopLevel(impl.Object)
+                {
+                    Template = CreateTopLevelTemplate()
+                };
+                topLevel.Content = target;
+                topLevel.ApplyTemplate();
+                topLevel.LayoutManager.ExecuteInitialLayoutPass();
+
+                target.ApplyTemplate();
+                target.Measure(Size.Infinity);
+
+                var initialHeight = target.DesiredSize.Height;
+
+                target.Text = "";
+
+                target.InvalidateMeasure();
+                target.Measure(Size.Infinity);
+
+                Assert.Equal(initialHeight, target.DesiredSize.Height);
+            }
+        }
+
+        [Theory]
+        [InlineData(1)]
+        [InlineData(2)]
+        [InlineData(3)]
+        public void MinLines_Sets_ScrollViewer_MinHeight(int minLines)
+        {
+            using (UnitTestApplication.Start(Services))
+            {
+                var target = new TextBox
+                {
+                    Template = CreateTemplate(),
+                    MinLines = minLines,
+
+                    // Define explicit whole number line height for predictable calculations
+                    LineHeight = 20
+                };
+
+                var impl = CreateMockTopLevelImpl();
+                var topLevel = new TestTopLevel(impl.Object)
+                {
+                    Template = CreateTopLevelTemplate(),
+                    Content = target
+                };
+                topLevel.ApplyTemplate();
+                topLevel.LayoutManager.ExecuteInitialLayoutPass();
+
+                var textPresenter = target.FindDescendantOfType<TextPresenter>();
+                Assert.Equal("PART_TextPresenter", textPresenter.Name);
+                Assert.Equal(new Thickness(0), textPresenter.Margin); // Test assumes no margin on TextPresenter
+
+                var scrollViewer = target.FindDescendantOfType<ScrollViewer>();
+                Assert.Equal("PART_ScrollViewer", scrollViewer.Name);
+                Assert.Equal(minLines * target.LineHeight, scrollViewer.MinHeight);
+            }
+        }
+
+        [Theory]
+        [InlineData(1)]
+        [InlineData(2)]
+        [InlineData(3)]
+        public void MinLines_Sets_ScrollViewer_MinHeight_With_TextPresenter_Margin(int minLines)
+        {
+            using (UnitTestApplication.Start(Services))
+            {
+                var target = new TextBox
+                {
+                    Template = CreateTemplate(),
+                    MinLines = minLines,
+
+                    // Define explicit whole number line height for predictable calculations
+                    LineHeight = 20
+                };
+
+                var impl = CreateMockTopLevelImpl();
+                var topLevel = new TestTopLevel(impl.Object)
+                {
+                    Template = CreateTopLevelTemplate(),
+                    Content = target
+                };
+                topLevel.ApplyTemplate();
+                topLevel.LayoutManager.ExecuteInitialLayoutPass();
+
+                var textPresenter = target.FindDescendantOfType<TextPresenter>();
+                Assert.Equal("PART_TextPresenter", textPresenter.Name);
+                var textPresenterMargin = new Thickness(horizontal: 0, vertical: 3);
+                textPresenter.Margin = textPresenterMargin;
+
+                target.InvalidateMeasure();
+                target.Measure(Size.Infinity);
+
+                var scrollViewer = target.FindDescendantOfType<ScrollViewer>();
+                Assert.Equal("PART_ScrollViewer", scrollViewer.Name);
+                Assert.Equal((minLines * target.LineHeight) + textPresenterMargin.Top + textPresenterMargin.Bottom, scrollViewer.MinHeight);
+            }
+        }
+
         [Fact]
         public void CanUndo_CanRedo_Is_False_When_Initialized()
         {