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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/5275249/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 22:39:08  来源:igfitidea点击:

How do I configure a TextBox control to automatically resize itself vertically when text no longer fits on one line?

wpftextboxautosize

提问by Klaus Nji

How do I configure a TextBoxcontrol to automatically resize itself vertically when text no longer fits on one line?

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

For example, in the following XAML:

例如,在以下 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>

The TextBoxcontrol named "VerticallyExpandMe" needs to automatically expand vertically when the text bound to it does not fit on one line. With AcceptsReturnset to true, TextBoxexpands vertically if I press enter within it, but I want it do do this automatically.

TextBox名为“VerticallyExpandMe”的控件需要在绑定到它的文本不适合一行时自动垂直扩展。随着AcceptsReturn设置为true,TextBox垂直展开,如果我按在它进入,但我想它不自动执行此操作。

回答by Ian Griffiths

Although Andre Luus's suggestion is basically correct, it won't actually work here, because your layout will defeat text wrapping. I'll explain why.

尽管 Andre Luus 的建议基本上是正确的,但它在这里实际上不起作用,因为您的布局将打败文本换行。我会解释为什么。

Fundamentally, the problem is this: text wrapping only does anything when an element's width is constrained, but your TextBoxhas unconstrained width because it's a descendant of a horizontal StackPanel. (Well, two horizontal stack panels. Possibly more, depending on the context from which you took your example.) Since the width is unconstrained, the TextBoxhas no idea when it is supposed to start wrapping, and so it will never wrap, even if you enable wrapping. You need to do two things: constrain its width and enable wrapping.

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

Here's a more detailed explanation.

这里有更详细的解释。

Your example contains a lot of detail irrelevant to the problem. Here's a version I've trimmed down somewhat to make it easier to explain what's wrong:

您的示例包含许多与问题无关的细节。这是我稍微精简的版本,以便更容易解释问题所在:

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

So I've removed your containing DockPaneland the two nested Borderelements inside of that, because they're neither part of the problem nor relevant to the solution. So I'm starting at the pair of nested StackPanelelements in your example. And I've also removed most of the attributes because most of them are also not relevant to the layout.

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

This looks a bit weird - having two nested horizontal stack panels like this looks redundant, but it does actually make sense in your original if you need to make the nested one visible or invisible at runtime. But it makes it easier to see the problem.

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

(The empty TextBlocktag is also weird, but that's exactly as it appears in your original. That doesn't appear to be doing anything useful.)

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

And here's the problem: your TextBoxis inside some horizontal StackPanelelements, meaning its width is unconstrained - you have inadvertently told the text box that it is free to grow to any width, regardless of how much space is actually available.

这就是问题所在:您TextBox位于一些水平StackPanel元素内,这意味着它的宽度不受限制——您无意中告诉文本框它可以自由增长到任何宽度,而不管实际有多少空间可用。

A StackPanelwill always perform layout that is unconstrained in the direction of stacking. So when it comes to lay out that TextBox, it'll pass in a horizontal size of double.PositiveInfinityto the TextBox. So the TextBoxwill always think it has more space than it needs. Moreover, when a child of a StackPanelasks for more space than is actually available, the StackPanellies, and pretends to give it that much space, but then crops it.

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

(This is the price you pay for the extreme simplicity of StackPanel- it's simple to the point of being bone-headed, because it will happily construct layouts that don't actually fit. You should only use StackPanelif either you really do have unlimited space because you're inside a ScrollViewer, or you are certain that you have sufficiently few items that you're not going to run out of space, or if you don't care about items running off the end of the panel when they get too large and you don't want the layout system to try to do anything more clever than simply cropping the content.)

(这是您为极端简单性付出的代价StackPanel- 它很简单,以至于令人头疼,因为它会很高兴地构建实际上并不适合的布局。只有StackPanel在您确实拥有无限空间时才应该使用,因为你里面ScrollViewer,或者你确信你有足够少的项目,你不会的空间用完,或者如果你不关心流失面板的最终产品时,他们得到过大,您不希望布局系统尝试做任何比简单裁剪内容更聪明的事情。)

So turning on text wrapping won't help here, because the StackPanelwill always pretend that there's more than enough space for the text.

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

You need a different layout structure. Stack panels are the wrong thing to use because they will not impose the layout constraint you need to get text wrapping to kick in.

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

Here's a simple example that does roughly what you want:

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

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

If you create a brand new WPF application and paste that in as the content of the main window, you should find it does what you want - the TextBoxstarts out one line tall, fills the available width, and if you type text in, it'll grow one line at a time as you add more text.

如果您创建一个全新的 WPF 应用程序并将其粘贴为主窗口的内容,您应该会发现它符合您的要求 -TextBox开始一行高,填充可用宽度,如果您输入文本,它'随着您添加更多文本,将一次增加一行。

Of course, layout behaviour is always sensitive to context, so it may not be enough to just throw that into the middle of your existing application. That will work if pasted into a fixed-size space (e.g. as the body of a window), but will not work correctly if you paste it into a context where width is unconstrained. (E.g., inside a ScrollViewer, or inside a horizontal StackPanel.)

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

So if this doesn't work for you, it'll be because of other things wrong elsewhere in your layout - possibly yet more StackPanelelements elsewhere. From the look of your example, it's probably worth spending some time thinking about what you really need in your layout and simplifying it - the presence of negative margins, and elements that don't appear to do anything like that empty TextBlockare usually indicative of an over-complicated layout. And unnecessary complexity in a layout makes it much hard to achieve the effects you're looking for.

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

回答by Erik Vullings

Alternatively, you could constrain your TextBlock's Widthby binding it to a parent's ActualWidth, for example:

或者,您可以通过将TextBlock'sWidth绑定到 parent来限制您的's ActualWidth,例如:

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

This will force it to resize its height automatically too.

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

回答by Andreas

Use MaxWidthand TextWrapping="WrapWithOverflow".

使用MaxWidthTextWrapping="WrapWithOverflow"

回答by Uri Abramson

You can use this class which extends TextBlock. It does auto-shrinking and takes MaxHeight / MaxWidth into consideration:

您可以使用扩展 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);
            }
        }
    }

回答by Denis Kucherov

I'm using another simple approach that allows me not to change the document layout.

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

The main idea is not to set the control Widthbefore it starts changing. For TextBoxes, I handle the SizeChangedevent:

主要思想是不要在控件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;
}