当文本不再适合一行时,如何配置TextBox控件以自动垂直自动调整大小?

时间:2011-03-11 16:00:07

标签: wpf textbox autosize

如何将TextBox控件配置为在文本不再适合一行时自动垂直调整自身大小?

例如,在以下XAML中:

<DockPanel LastChildFill="True" Margin="0,0,0,0">
  <Border Name="dataGridHeader" 
    DataContext="{Binding Descriptor.Filter}"
    DockPanel.Dock="Top"                         
    BorderThickness="1"
    Style="{StaticResource ChamelionBorder}">
  <Border
    Padding="5"
    BorderThickness="1,1,0,0"                            
    BorderBrush="{DynamicResource {ComponentResourceKey TypeInTargetAssembly=dc:NavigationPane, 
    ResourceId={x:Static dc:NavigationPaneColors.NavPaneTitleBorder}}}">
    <StackPanel Orientation="Horizontal">
      <TextBlock                                
        Name="DataGridTitle"                                                                                                
        FontSize="14"
        FontWeight="Bold"                                    
        Foreground="{DynamicResource {ComponentResourceKey 
        TypeInTargetAssembly=dc:NavigationPane, 
        ResourceId={x:Static dc:NavigationPaneColors.NavPaneTitleForeground}}}"/>
      <StackPanel Margin="5,0"  Orientation="Horizontal" 
              Visibility="{Binding IsFilterEnabled, FallbackValue=Collapsed, Mode=OneWay, Converter={StaticResource BooleanToVisibility}}"
              IsEnabled="{Binding IsFilterEnabled, FallbackValue=false}"  >                                    
          <TextBlock  />                                                                
          <TextBox    
            Name="VerticallyExpandMe"
            Padding="0, 0, 0, 0"  
            Margin="10,2,10,-1"                                                                                                                                                                                                                     
            AcceptsReturn="True"
            VerticalAlignment="Center"                                    
            Text="{Binding QueryString}"
            Foreground="{DynamicResource {ComponentResourceKey 
            TypeInTargetAssembly=dc:NavigationPane, 
            ResourceId={x:Static dc:NavigationPaneColors.NavPaneTitleForeground}}}">
          </TextBox>
        </StackPanel>                               
    </StackPanel>
  </Border>              
  </Border>
</DockPanel>

名为“VerticallyExpandMe”的TextBox控件需要在绑定到它的文本不适合一行时自动垂直展开。将AcceptsReturn设置为true后,如果我在其中按Enter键,TextBox会垂直展开,但我希望它自动执行此操作。

5 个答案:

答案 0 :(得分:43)

尽管Andre Luus的建议基本上是正确的,但它实际上并不适用,因为你的布局会打败文本包装。我会解释原因。

从根本上说,问题在于:文本换行仅在元素的宽度受约束时执行任何操作,但是TextBox具有不受约束的宽度,因为它是水平StackPanel的后代。 (好吧,两个水平堆栈面板。可能更多,取决于您从中获取示例的上下文。)由于宽度不受约束,TextBox不知道何时应该开始包装,因此它将永远不会换行,即使你启用换行。你需要做两件事:约束它的宽度并启用包装。

这是一个更详细的解释。

您的示例包含许多与问题无关的详细信息。这是一个我稍微减少的版本,以便更容易解释错误:

<StackPanel Orientation="Horizontal">
    <TextBlock Name="DataGridTitle" />
    <StackPanel
        Margin="5,0"
        Orientation="Horizontal"
        >
        <TextBlock />
        <TextBox
            Name="VerticallyExpandMe"
            Margin="10,2,10,-1"
            AcceptsReturn="True"
            VerticalAlignment="Center"
            Text="{Binding QueryString}"
            >
        </TextBox>
    </StackPanel>
</StackPanel>

所以我删除了你的内容DockPanel和两个嵌套的Border元素,因为它们既不是问题的一部分,也不是解决方案的相关内容。所以我从你的例子中的一对嵌套StackPanel元素开始。而且我还删除了大部分属性,因为大多数属性也与布局无关。

这看起来有点奇怪 - 有两个嵌套的水平堆栈面板看起来多余,但如果您需要在运行时使嵌套的可见或不可见,它确实在您的原始中有意义。但它更容易看到问题。

(空的TextBlock标签也很奇怪,但这与原始标签完全一样。这似乎没有做任何有用的事情。)

问题在于:你的TextBox位于一些水平StackPanel元素内,意味着它的宽度不受约束 - 你无意中告诉文本框它可以自由地增长到任何宽度,无论实际可用空间多少。

StackPanel将始终执行在堆叠方向上不受约束的布局。因此,当列出TextBox时,它会以double.PositiveInfinity的水平尺寸传递给TextBox。所以TextBox总会认为它有更多的空间而不是它需要的空间。此外,当一个StackPanel的孩子要求的空间超出实际可用空间时,StackPanel就会出现,并假装给它那么大的空间,然后将其裁剪掉。

(这是你为StackPanel的极端简单所付出的代价 - 这很简单到了骨头,因为它会愉快地构建实际上不适合的布局。你应该只使用{ {1}}如果你确实拥有无限空间,因为你在StackPanel内,或者你确定你的物品足够少,你就不会用完空间,或者你没有当它们变得太大而你不想让布局系统尝试做任何比简单裁剪内容更聪明的事情时,要关心在面板末端运行的物品。)

因此,打开文本包装在这里无济于事,因为ScrollViewer将始终假装文本的空间足够。

您需要不同的布局结构。堆栈面板使用是错误的,因为它们不会强制执行文本换行所需的布局约束。

这是一个简单的例子,大致可以做到你想要的:

StackPanel

如果您创建一个全新的WPF应用程序并将其作为主窗口的内容粘贴,您应该会发现它符合您的要求 - <Grid VerticalAlignment="Top"> <DockPanel> <TextBlock x:Name="DataGridTitle" VerticalAlignment="Top" DockPanel.Dock="Left" /> <TextBox Name="VerticallyExpandMe" AcceptsReturn="True" TextWrapping="Wrap" Text="{Binding QueryString}" > </TextBox> </DockPanel> </Grid> 开始一行高,填充可用宽度,以及如果你输入文字,当你添加更多文字时,它会一次增长一行。

当然,布局行为始终对上下文敏感,因此将其放入现有应用程序的中间可能还不够。如果将其粘贴到固定大小的空间(例如,作为窗口的主体),则会起作用,但如果将其粘贴到宽度不受约束的上下文中,则无法正常工作。 (例如,在TextBox内,或在水平ScrollViewer内。)

因此,如果这对您不起作用,那将是因为您的布局中其他地方出现了其他问题 - 其他地方可能还有更多StackPanel元素。从你的例子来看,可能值得花一些时间考虑你在布局中真正需要的东西并简化它 - 负边距的存在,以及似乎没有像空StackPanel那样做任何事情的元素。通常表示过于复杂的布局。布局中不必要的复杂性使得很难实现您正在寻找的效果。

答案 1 :(得分:6)

或者,您可以通过将TextBlock的{​​{1}}绑定到父级Width来约束ActualWidth <TextBlock Width="{Binding ElementName=*ParentElement*, Path=ActualWidth}" Height="Auto" /> ,例如:

{{1}}

这将强制它自动调整其高度。

答案 2 :(得分:2)

使用MaxWidthTextWrapping="WrapWithOverflow"

答案 3 :(得分:0)

我使用另一种简单方法,不允许我更改文档布局。

主要思想是在控件Width开始更改之前不要设置它。对于TextBox es,我处理SizeChanged事件:

<TextBox TextWrapping="Wrap" SizeChanged="TextBox_SizeChanged" />

private void TextBox_SizeChanged(object sender, SizeChangedEventArgs e)
{
    FrameworkElement box = (FrameworkElement)sender;
    if (e.PreviousSize.Width == 0 || box.Width < e.PreviousSize.Width)
        return;
    box.Width = e.PreviousSize.Width;
}

答案 4 :(得分:0)

您可以使用此类扩展TextBlock。它会自动缩小并考虑MaxHeight / MaxWidth:

public class TextBlockAutoShrink : TextBlock
    {
        private double _defaultMargin = 6;
        private Typeface _typeface;

        static TextBlockAutoShrink()
        {
            TextBlock.TextProperty.OverrideMetadata(typeof(TextBlockAutoShrink), new FrameworkPropertyMetadata(new PropertyChangedCallback(TextPropertyChanged)));
        }

        public TextBlockAutoShrink() : base() 
        {
            _typeface = new Typeface(this.FontFamily, this.FontStyle, this.FontWeight, this.FontStretch, this.FontFamily);
            base.DataContextChanged += new DependencyPropertyChangedEventHandler(TextBlockAutoShrink_DataContextChanged);
        }

        private static void TextPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            var t = sender as TextBlockAutoShrink;
            if (t != null)
            {
                t.FitSize();
            }
        }

        void TextBlockAutoShrink_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            FitSize();
        }

        protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
        {
            FitSize();

            base.OnRenderSizeChanged(sizeInfo);
        }


        private void FitSize()
        {
            FrameworkElement parent = this.Parent as FrameworkElement;
            if (parent != null)
            {
                var targetWidthSize = this.FontSize;
                var targetHeightSize = this.FontSize;

                var maxWidth = double.IsInfinity(this.MaxWidth) ? parent.ActualWidth : this.MaxWidth;
                var maxHeight = double.IsInfinity(this.MaxHeight) ? parent.ActualHeight : this.MaxHeight;

                if (this.ActualWidth > maxWidth)
                {
                    targetWidthSize = (double)(this.FontSize * (maxWidth / (this.ActualWidth + _defaultMargin)));
                }

                if (this.ActualHeight > maxHeight)
                {
                    var ratio = maxHeight / (this.ActualHeight);

                    // Normalize due to Height miscalculation. We do it step by step repeatedly until the requested height is reached. Once the fontsize is changed, this event is re-raised
                    // And the ActualHeight is lowered a bit more until it doesnt enter the enclosing If block.
                    ratio = (1 - ratio > 0.04) ? Math.Sqrt(ratio) : ratio;

                    targetHeightSize = (double)(this.FontSize *  ratio);
                }

                this.FontSize = Math.Min(targetWidthSize, targetHeightSize);
            }
        }
    }