Как настроить элемент управления TextBox для автоматического изменения размера по вертикали, когда текст больше не помещается в одну строку?

как настроить 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>

на TextBox элемент управления с именем "VerticallyExpandMe" должен автоматически расширяться по вертикали, когда связанный с ним текст не помещается в одну строку. С AcceptsReturn значение true, TextBox расширяется вертикально, если я нажимаю enter внутри него, но я хочу, чтобы он делал это автоматически.

5 ответов


хотя предложение Андре Лууса в основном правильно, оно на самом деле не будет работать здесь, потому что ваш макет победит перенос текста. Я объясню, почему.

в принципе, проблема заключается в следующем: перенос текста делает что-либо только тогда, когда ширина элемента ограничена, но ваш 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 элементы, то есть его ширина не ограничена - вы непреднамеренно сказали текстовое поле, что оно свободно расти до любой ширины, независимо от того, сколько пространство действительно доступно.

A StackPanel всегда будет выполнять макет, который не ограничен в направлении укладки. Поэтому, когда дело доходит до того, чтобы выложить это TextBox Она пройдет в горизонтальном размере double.PositiveInfinity до TextBox. Так что TextBox всегда будет думать, что у него больше места, чем ему нужно. Более того, когда ребенок StackPanel просит больше места, чем на самом деле имеется,StackPanel лжет, и притворяется, что дает ему столько места, но затем обрезает его.

(эта это цена, которую вы платите за крайнюю простоту StackPanel - это просто до такой степени, чтобы быть костлявым, потому что он будет счастливо строить макеты, которые на самом деле не подходят. Вы должны использовать только StackPanel Если у вас действительно есть неограниченное пространство, потому что ты внутри 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 обычно указывают на чрезмерно сложный макет. И ненужная сложность в макете значительно затрудняет достижение эффектов, которые вы ищете.


кроме того, вы можете ограничить свой TextBlock ' s Width привязав его к родительскому ActualWidth, например:

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

Это заставит его автоматически изменять свою высоту.


использовать MaxWidth и TextWrapping="WrapWithOverflow".


Я использую другой простой подход, который позволяет мне не изменять макет документа.

основная идея не установить контроль 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;
}

вы можете использовать этот класс, который расширяет 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);
            }
        }
    }