12

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 时,如果我在TextBox其中按 enter 会垂直扩展,但我希望它自动执行此操作。

4

5 回答 5

46

尽管 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元素内,这意味着它的宽度不受限制 - 你无意中告诉文本框它可以自由增长到任何宽度,无论实际可用空间有多少。

AStackPanel将始终执行在堆叠方向上不受约束的布局。因此,当涉及到布局时TextBox,它将水平大小传递double.PositiveInfinityTextBox. 所以TextBox总是会认为它有比它需要的更多的空间。此外,当一个孩子StackPanel要求比实际可用的空间更多时,StackPanel撒谎,并假装给它那么多空间,但随后将其裁剪。

(这是您为极其简单而付出的代价StackPanel- 它简单到让人头疼,因为它会很高兴地构建实际上不适合的布局。只有StackPanel当您确实有无限空间时才应该使用,因为你在 a 里面ScrollViewer,或者你确定你有足够少的项目,你不会用完空间,或者如果你不关心当它们变得太大时从面板末端跑出来的项目,并且您不希望布局系统尝试做任何比简单地裁剪内容更聪明的事情。)

因此,在这里打开文本换行将无济于事,因为它StackPanel总是会假装文本有足够的空间。

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

这是一个简单的示例,大致可以满足您的要求:

<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>

如果您创建一个全新的 WPF 应用程序并将其粘贴为主窗口的内容,您应该会发现它可以满足您的需求 -TextBox从一行开始,填充可用宽度,如果您输入文本,它会当您添加更多文本时,将一次增长一行。

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

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

于 2011-03-11T16:46:51.633 回答
8

或者,您可以通过将其绑定到父级来限制您TextBlock的 ' ,例如:WidthActualWidth

<TextBlock Width="{Binding ElementName=*ParentElement*, Path=ActualWidth}" 
           Height="Auto" />

这也将迫使它自动调整其高度。

于 2012-05-02T13:19:50.853 回答
2

使用MaxWidthTextWrapping="WrapWithOverflow"

于 2014-02-06T00:16:46.807 回答
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);
            }
        }
    }
于 2013-10-15T09:05:46.193 回答
0

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

Width主要思想是在控件开始更改之前不要设置控件。对于TextBoxes,我处理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;
}
于 2011-12-18T10:35:33.573 回答